From 0cd81ff5001547647a8daefe334c9d695f9b62c8 Mon Sep 17 00:00:00 2001 From: Alchav <59858495+Alchav@users.noreply.github.com> Date: Sat, 9 May 2026 11:07:46 -0400 Subject: [PATCH] A Link to the Past: We Have Enemizer at Home (#6153) --- .dockerignore | 1 - Dockerfile | 27 - docs/deploy using containers.md | 9 - docs/running from source.md | 10 - inno_setup.iss | 4 +- settings.py | 5 - setup.py | 5 +- worlds/alttp/EnemizerPatches.py | 478 +++++ worlds/alttp/EnemyShuffle.py | 1707 +++++++++++++++++ worlds/alttp/ItemPool.py | 3 + worlds/alttp/PotShuffle.py | 125 ++ worlds/alttp/Rom.py | 282 +-- worlds/alttp/__init__.py | 37 +- worlds/alttp/enemizer_data/README.md | 27 + worlds/alttp/enemizer_data/__init__.py | 1 + worlds/alttp/enemizer_data/base_patch_data.py | 71 + .../enemizer_data/dungeon_sprite_addresses.py | 202 ++ .../enemizer_data/enemy_room_metadata.py | 106 + .../enemy_sprite_requirements.py | 295 +++ .../enemizer_data/overworld_enemy_metadata.py | 131 ++ .../alttp/enemizer_data/pot_shuffle_data.py | 107 ++ worlds/alttp/enemizer_data/symbols.py | 171 ++ worlds/alttp/test/TestEnemizerPatches.py | 308 +++ worlds/alttp/test/TestEnemyShuffle.py | 834 ++++++++ worlds/alttp/test/TestPotShuffle.py | 56 + 25 files changed, 4704 insertions(+), 298 deletions(-) create mode 100644 worlds/alttp/EnemizerPatches.py create mode 100644 worlds/alttp/EnemyShuffle.py create mode 100644 worlds/alttp/PotShuffle.py create mode 100644 worlds/alttp/enemizer_data/README.md create mode 100644 worlds/alttp/enemizer_data/__init__.py create mode 100644 worlds/alttp/enemizer_data/base_patch_data.py create mode 100644 worlds/alttp/enemizer_data/dungeon_sprite_addresses.py create mode 100644 worlds/alttp/enemizer_data/enemy_room_metadata.py create mode 100644 worlds/alttp/enemizer_data/enemy_sprite_requirements.py create mode 100644 worlds/alttp/enemizer_data/overworld_enemy_metadata.py create mode 100644 worlds/alttp/enemizer_data/pot_shuffle_data.py create mode 100644 worlds/alttp/enemizer_data/symbols.py create mode 100644 worlds/alttp/test/TestEnemizerPatches.py create mode 100644 worlds/alttp/test/TestEnemyShuffle.py create mode 100644 worlds/alttp/test/TestPotShuffle.py diff --git a/.dockerignore b/.dockerignore index 982e411032..0cc03e22e4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -46,7 +46,6 @@ dist /prof/ README.html .vs/ -EnemizerCLI/ /Players/ /SNI/ /sni-*/ diff --git a/Dockerfile b/Dockerfile index 363478988c..9740806565 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,5 @@ # hadolint global ignore=SC1090,SC1091 -# Source -FROM scratch AS release -WORKDIR /release -ADD https://github.com/Ijwu/Enemizer/releases/latest/download/ubuntu.16.04-x64.zip Enemizer.zip - -# Enemizer -FROM alpine:3.21 AS enemizer -ARG TARGETARCH -WORKDIR /release -COPY --from=release /release/Enemizer.zip . - -# No release for arm architecture. Skip. -RUN if [ "$TARGETARCH" = "amd64" ]; then \ - apk add unzip=6.0-r15 --no-cache && \ - unzip -u Enemizer.zip -d EnemizerCLI && \ - chmod -R 777 EnemizerCLI; \ - else touch EnemizerCLI; fi - # Cython builder stage FROM python:3.12 AS cython-builder @@ -81,15 +63,6 @@ RUN apt-get purge -y \ g++ && \ apt-get autoremove -y -# Copy necessary components -COPY --from=enemizer /release/EnemizerCLI /tmp/EnemizerCLI - -# No release for arm architecture. Skip. -RUN if [ "$TARGETARCH" = "amd64" ]; then \ - cp -r /tmp/EnemizerCLI EnemizerCLI; \ - fi; \ - rm -rf /tmp/EnemizerCLI - # Define health check HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ CMD curl -f http://localhost:${PORT:-80} || exit 1 diff --git a/docs/deploy using containers.md b/docs/deploy using containers.md index 6db38d443f..f2f86d4289 100644 --- a/docs/deploy using containers.md +++ b/docs/deploy using containers.md @@ -77,15 +77,6 @@ Changes made to `docker-compose.yaml` can be applied by running `docker compose It is possible to carry out these deployment steps on Windows under [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install). -## Optional: A Link to the Past Enemizer - -Only required to generate seeds that include A Link to the Past with certain options enabled. You will receive an -error if it is required. -Enemizer can be enabled on `x86_64` platform architecture, and is included in the image build process. Enemizer requires a version 1.0 Japanese "Zelda no Densetsu" `.sfc` rom file to be placed in the application directory: -`docker run archipelago -v "/path/to/zelda.sfc:/app/Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"`. -Enemizer is not currently available for `aarch64`. - - ## Optional: Git Building the image requires a local copy of the ArchipelagoMW source code. diff --git a/docs/running from source.md b/docs/running from source.md index dbb2516961..52cc492112 100644 --- a/docs/running from source.md +++ b/docs/running from source.md @@ -78,16 +78,6 @@ first generate the binary distribution and then run `python setup.py bdist_appim put an `appimagetool` into the directory you run the command from, rename it to `appimagetool` and make it executable. -## Optional: A Link to the Past Enemizer - -Only required to generate seeds that include A Link to the Past with certain options enabled. You will receive an -error if it is required. - -You can get the latest Enemizer release at [Enemizer Github releases](https://github.com/Ijwu/Enemizer/releases). -It should be dropped as "EnemizerCLI" into the root folder of the project. Alternatively, you can point the Enemizer -setting in host.yaml at your Enemizer executable. - - ## Optional: SNI [SNI](https://github.com/alttpo/sni/blob/main/README.md) is required to use SNIClient. If not integrated into the project, it has to be started manually. diff --git a/inno_setup.iss b/inno_setup.iss index a325d30f5c..50c400550b 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -57,9 +57,8 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-modify authusers-modify; [Files] -Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "{#source_path}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; -Source: "{#source_path}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall [Icons] @@ -83,7 +82,6 @@ Type: files; Name: "{app}\*.exe" Type: files; Name: "{app}\data\lua\connector_pkmn_rb.lua" Type: files; Name: "{app}\data\lua\connector_ff1.lua" Type: filesandordirs; Name: "{app}\SNI\lua*" -Type: filesandordirs; Name: "{app}\EnemizerCLI*" #include "installdelete.iss" [Registry] diff --git a/settings.py b/settings.py index 72ce53d92c..46377389aa 100644 --- a/settings.py +++ b/settings.py @@ -633,10 +633,6 @@ class ServerOptions(Group): class GeneratorOptions(Group): """Options for Generation""" - class EnemizerPath(LocalFilePath): - """Location of your Enemizer CLI, available here: https://github.com/Ijwu/Enemizer/releases""" - is_exe = True - class PlayerFilesPath(OptionalUserFolderPath): """Folder from which the player yaml files are pulled from""" # created on demand, so marked as optional @@ -693,7 +689,6 @@ class GeneratorOptions(Group): start_inventory -> Move remaining items to start_inventory, generate additional filler items to fill locations. """ - enemizer_path: EnemizerPath = EnemizerPath("EnemizerCLI/EnemizerCLI.Core") # + ".exe" is implied on Windows player_files_path: PlayerFilesPath = PlayerFilesPath("Players") players: Players = Players(0) allow_quantity: AllowQuantity | bool = False diff --git a/setup.py b/setup.py index 3c40eab59e..9aa240462b 100644 --- a/setup.py +++ b/setup.py @@ -201,7 +201,7 @@ if is_windows: icon=resolve_icon(c.icon), )) -extra_data = ["LICENSE", "data", "EnemizerCLI", "SNI"] +extra_data = ["LICENSE", "data", "SNI"] extra_libs = ["libssl.so", "libcrypto.so"] if is_linux else [] @@ -456,9 +456,8 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe): for world_directory in folders_to_remove) else: # make sure extra programs are executable - enemizer_exe = self.buildfolder / 'EnemizerCLI/EnemizerCLI.Core' sni_exe = self.buildfolder / 'SNI/sni' - extra_exes = (enemizer_exe, sni_exe) + extra_exes = (sni_exe,) for extra_exe in extra_exes: if extra_exe.is_file(): extra_exe.chmod(0o755) diff --git a/worlds/alttp/EnemizerPatches.py b/worlds/alttp/EnemizerPatches.py new file mode 100644 index 0000000000..9d7618c075 --- /dev/null +++ b/worlds/alttp/EnemizerPatches.py @@ -0,0 +1,478 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from functools import lru_cache +import hashlib +import random +from typing import TYPE_CHECKING, Optional + +from Utils import pc_to_snes, snes_to_pc +from .enemizer_data.base_patch_data import ENEMIZER_BASE_PATCHES +from .enemizer_data.symbols import ENEMIZER_SYMBOLS + +if TYPE_CHECKING: + from . import ALTTPWorld + from .Rom import LocalRom + + +@dataclass(frozen=True) +class BossPatchData: + pointer: tuple[int, int] + graphics: int + sprite_array: tuple[int, ...] + + +@dataclass(frozen=True) +class DungeonBossPatchData: + room_id: int + sprite_pointer_address: int + shell_x: int + shell_y: int + clear_layer2: bool = False + extra_sprites: tuple[int, ...] = () + gt_sprite_write_address: Optional[int] = None + + +@dataclass +class RoomObjectTable: + header_byte_0: int + header_byte_1: int + layer_1_objects: list[bytes] = field(default_factory=list) + layer_1_doors: list[bytes] = field(default_factory=list) + layer_2_objects: list[bytes] = field(default_factory=list) + layer_2_doors: list[bytes] = field(default_factory=list) + layer_3_objects: list[bytes] = field(default_factory=list) + layer_3_doors: list[bytes] = field(default_factory=list) + + @classmethod + def from_rom(cls, rom: "LocalRom", start_address: int) -> "RoomObjectTable": + table = cls(rom.read_byte(start_address), rom.read_byte(start_address + 1)) + layers = ( + (table.layer_1_objects, table.layer_1_doors), + (table.layer_2_objects, table.layer_2_doors), + (table.layer_3_objects, table.layer_3_doors), + ) + index = start_address + 2 + + for objects, doors in layers: + is_door = False + while True: + if rom.read_bytes(index, 2) == bytearray((0xF0, 0xFF)): + is_door = True + index += 2 + continue + if rom.read_bytes(index, 2) == bytearray((0xFF, 0xFF)): + index += 2 + break + if is_door: + doors.append(bytes(rom.read_bytes(index, 2))) + index += 2 + else: + objects.append(bytes(rom.read_bytes(index, 3))) + index += 3 + + return table + + def add_shell(self, x: int, y: int, clear_layer_2: bool, shell_id: int) -> None: + self.header_byte_0 = 0xF0 + if clear_layer_2: + self.layer_2_objects.clear() + self.layer_2_objects.append(_build_subtype_3_object(x, y, shell_id)) + + def remove_shell(self, shell_id: int) -> None: + self.layer_2_objects = [obj for obj in self.layer_2_objects if _object_id(obj) != shell_id] + + def to_bytes(self) -> bytes: + output = bytearray((self.header_byte_0, self.header_byte_1)) + output.extend(self._serialize_layer(self.layer_1_objects, self.layer_1_doors, is_last_layer=False)) + output.extend(self._serialize_layer(self.layer_2_objects, self.layer_2_doors, is_last_layer=False)) + output.extend(self._serialize_layer(self.layer_3_objects, self.layer_3_doors, is_last_layer=True)) + return bytes(output) + + @staticmethod + def _serialize_layer(objects: list[bytes], doors: list[bytes], is_last_layer: bool) -> bytes: + output = bytearray() + for obj in objects: + output.extend(obj) + if is_last_layer or doors: + output.extend((0xF0, 0xFF)) + for door in doors: + output.extend(door) + output.extend((0xFF, 0xFF)) + return bytes(output) + + +BOSS_PATCH_DATA: dict[str, BossPatchData] = { + "Armos": BossPatchData((0x87, 0xE8), 9, (0x05, 0x04, 0x53, 0x05, 0x07, 0x53, 0x05, 0x0A, 0x53, + 0x08, 0x0A, 0x53, 0x08, 0x07, 0x53, 0x08, 0x04, 0x53, + 0x08, 0xE7, 0x19)), + "Arrghus": BossPatchData((0x97, 0xD9), 20, (0x07, 0x07, 0x8C, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, + 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, + 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, + 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D, + 0x07, 0x07, 0x8D, 0x07, 0x07, 0x8D)), + "Blind": BossPatchData((0x54, 0xE6), 32, (0x05, 0x09, 0xCE)), + "Helmasaur": BossPatchData((0x49, 0xE0), 21, (0x06, 0x07, 0x92)), + "Kholdstare": BossPatchData((0x01, 0xEA), 22, (0x05, 0x07, 0xA3, 0x05, 0x07, 0xA4, 0x05, 0x07, 0xA2)), + "Lanmola": BossPatchData((0xCB, 0xDC), 11, (0x07, 0x06, 0x54, 0x07, 0x09, 0x54, 0x09, 0x07, 0x54)), + "Moldorm": BossPatchData((0xC3, 0xD9), 12, (0x09, 0x09, 0x09)), + "Mothula": BossPatchData((0x31, 0xDC), 26, (0x06, 0x08, 0x88)), + "Trinexx": BossPatchData((0xBA, 0xE5), 23, (0x05, 0x07, 0xCB, 0x05, 0x07, 0xCC, 0x05, 0x07, 0xCD)), + "Vitreous": BossPatchData((0x57, 0xE4), 22, (0x05, 0x07, 0xBD)), +} + +DUNGEON_BOSS_PATCH_DATA: dict[tuple[str, Optional[str]], DungeonBossPatchData] = { + ("Eastern Palace", None): DungeonBossPatchData(200, 0x04D7BE, 0x2B, 0x28), + ("Desert Palace", None): DungeonBossPatchData(51, 0x04D694, 0x0B, 0x28), + ("Tower of Hera", None): DungeonBossPatchData(7, 0x04D63C, 0x18, 0x16), + ("Palace of Darkness", None): DungeonBossPatchData(90, 0x04D6E2, 0x2B, 0x28), + ("Swamp Palace", None): DungeonBossPatchData(6, 0x04D63A, 0x0B, 0x28), + ("Skull Woods", None): DungeonBossPatchData(41, 0x04D680, 0x2B, 0x28), + ("Thieves Town", None): DungeonBossPatchData(172, 0x04D786, 0x2B, 0x28, clear_layer2=True), + ("Ice Palace", None): DungeonBossPatchData(222, 0x04D7EA, 0x2B, 0x08, clear_layer2=True), + ("Misery Mire", None): DungeonBossPatchData(144, 0x04D74E, 0x0B, 0x28, clear_layer2=True), + ("Turtle Rock", None): DungeonBossPatchData(164, 0x04D776, 0x0B, 0x28, clear_layer2=True), + ("Ganons Tower", "bottom"): DungeonBossPatchData( + 28, 0x04D666, 0x2B, 0x28, extra_sprites=(0x07, 0x07, 0xE3, 0x07, 0x08, 0xE3, 0x08, 0x07, 0xE3, 0x08, 0x08, 0xE3), + gt_sprite_write_address=0x04D87E, + ), + ("Ganons Tower", "middle"): DungeonBossPatchData( + 108, 0x04D706, 0x0B, 0x28, extra_sprites=(0x18, 0x17, 0xD1, 0x1C, 0x03, 0xC5), gt_sprite_write_address=0x04D8B6, + ), + ("Ganons Tower", "top"): DungeonBossPatchData(77, 0x04D6C8, 0x18, 0x16), +} + +TRINEXX_SHELL_OBJECT_ID = 0xFF2 +KHOLDSTARE_SHELL_OBJECT_ID = 0xF95 +TRINEXX_VANILLA_ROOM_ID = 164 +KHOLDSTARE_VANILLA_ROOM_ID = 222 +ENEMY_HP_TABLE_ADDRESS = 0x6B173 +ENEMY_DAMAGE_TABLE_ADDRESS = 0x6B266 +HIDDEN_ENEMY_CHANCE_POOL_ADDRESS = 0xD7BBB +DAMAGE_GROUP_TABLE_ADDRESS = 0x3742D +RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS = 0x301FC +RETRO_RUPEE_REPLACEMENT_SPRITE_ID = 0xDA +ARROW_REFILL_5_SPRITE_ID = 0xE1 +THIEF_SPRITE_ID = 0xC4 +THIEF_DEFAULT_HP = 4 +VANILLA_HIDDEN_ENEMY_CHANCE_POOL = ( + 0x01, 0x01, 0x01, 0x01, 0x0F, 0x01, 0x01, 0x12, + 0x10, 0x01, 0x01, 0x01, 0x11, 0x01, 0x01, 0x03, +) +RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL = ( + 0x01, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x12, + 0x0F, 0x01, 0x0F, 0x0F, 0x11, 0x0F, 0x0F, 0x03, +) +EXCLUDED_ENEMY_TABLE_SPRITE_IDS = frozenset({ + 0x09, 0x53, 0x54, 0x70, 0x7A, 0x7B, 0x88, 0x89, 0x8C, 0x8D, 0x92, + 0xA2, 0xA3, 0xA4, 0xBD, 0xBE, 0xBF, 0xCB, 0xCC, 0xCD, 0xCE, 0xD6, 0xD7, +}) +ENEMY_HEALTH_RANGE_BY_KEY = { + "easy": (1, 4), + "normal": (2, 15), + "hard": (2, 25), + "expert": (4, 50), +} + +_ENEMIZER_SYMBOLS: Optional[dict[str, int]] = None + +BOSS_GFX_SHEET_INDEXES = { + "Agahnim1": 0x8D, + "Agahnim2": 0xB5, + "Agahnim3": 0xC8, + "Agahnim4": 0xB6, + "ArmosKnight1": 0x90, + "Ganon1": 0x94, + "Ganon2": 0xA6, + "Ganon3": 0xB4, + "Ganon4": 0xB8, + "Moldorm1": 0xA3, + "Lanmola1": 0xA4, + "Arrghus1": 0xAC, + "Mothula1": 0xAB, + "Helmasaure1": 0xAD, + "Helmasaure2": 0xB1, + "Blind1": 0xAE, + "Kholdstare1": 0xAF, + "Vitreous1": 0xB0, + "Trinexx1": 0xB2, + "Trinexx2": 0xB3, +} + +BOSS_GFX_TABLE = { + "Agahnim1": (21, 190, 228), + "Agahnim2": (22, 255, 135), + "Agahnim3": (23, 220, 101), + "Agahnim4": (23, 132, 92), + "ArmosKnight1": (21, 206, 27), + "Ganon1": (21, 227, 160), + "Ganon2": (22, 186, 55), + "Ganon3": (22, 250, 199), + "Ganon4": (23, 142, 33), + "Moldorm1": (22, 175, 152), + "Lanmola1": (22, 180, 23), + "Arrghus1": (22, 214, 147), + "Mothula1": (22, 210, 84), + "Helmasaure1": (22, 219, 114), + "Helmasaure2": (22, 239, 177), + "Blind1": (22, 224, 90), + "Kholdstare1": (22, 230, 31), + "Vitreous1": (22, 235, 9), + "Trinexx1": (22, 243, 89), + "Trinexx2": (22, 246, 35), +} + +TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS = 0x04B37E +TRINEXX_ICE_PROJECTILE_TILE_ADDRESS = 0xE7A5 +TILE_TRAP_FLOOR_TILE_ADDRESS = 0xF3BED + + +def apply_enemizer_base_patch(rom: "LocalRom") -> None: + for address, patch_data in _load_enemizer_base_patches(): + rom.write_bytes(address, patch_data) + _apply_trinexx_room_fixes(rom) + +def patch_bosses(world: "ALTTPWorld", rom: "LocalRom") -> None: + dungeon_header_base = _get_enemizer_symbol("room_header_table") + moved_room_object_base = _get_enemizer_symbol("modified_room_object_table") + gt_dungeon_name = "Ganons Tower" if world.options.mode != "inverted" else "Inverted Ganons Tower" + gt_dungeon = world.dungeons[gt_dungeon_name] + + placements = ( + (world.dungeons["Eastern Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Eastern Palace", None)]), + (world.dungeons["Desert Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Desert Palace", None)]), + (world.dungeons["Tower of Hera"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Tower of Hera", None)]), + (world.dungeons["Palace of Darkness"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Palace of Darkness", None)]), + (world.dungeons["Swamp Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Swamp Palace", None)]), + (world.dungeons["Skull Woods"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Skull Woods", None)]), + (world.dungeons["Thieves Town"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Thieves Town", None)]), + (world.dungeons["Ice Palace"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ice Palace", None)]), + (world.dungeons["Misery Mire"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Misery Mire", None)]), + (world.dungeons["Turtle Rock"].boss.enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Turtle Rock", None)]), + (gt_dungeon.bosses["bottom"].enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ganons Tower", "bottom")]), + (gt_dungeon.bosses["middle"].enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ganons Tower", "middle")]), + (gt_dungeon.bosses["top"].enemizer_name, DUNGEON_BOSS_PATCH_DATA[("Ganons Tower", "top")]), + ) + + modified_room_tables: dict[int, RoomObjectTable] = {} + + for boss_name, dungeon_data in placements: + boss_data = BOSS_PATCH_DATA[boss_name] + rom.write_bytes(dungeon_data.sprite_pointer_address, boss_data.pointer) + rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14) + 3, boss_data.graphics) + + if boss_name == "Trinexx" and dungeon_data.room_id != TRINEXX_VANILLA_ROOM_ID: + room_table = _get_room_object_table(rom, modified_room_tables, dungeon_data.room_id) + room_table.add_shell( + dungeon_data.shell_x, + dungeon_data.shell_y - 2, + dungeon_data.clear_layer2, + TRINEXX_SHELL_OBJECT_ID, + ) + rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14), 0x60) + rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14) + 4, 0x04) + + if boss_name == "Kholdstare" and dungeon_data.room_id != KHOLDSTARE_VANILLA_ROOM_ID: + room_table = _get_room_object_table(rom, modified_room_tables, dungeon_data.room_id) + room_table.add_shell( + dungeon_data.shell_x, + dungeon_data.shell_y, + dungeon_data.clear_layer2, + KHOLDSTARE_SHELL_OBJECT_ID, + ) + rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14), 0xE0) + rom.write_byte(dungeon_header_base + (dungeon_data.room_id * 14) + 4, 0x01) + + if boss_name != "Trinexx" and dungeon_data.room_id == TRINEXX_VANILLA_ROOM_ID: + _get_room_object_table(rom, modified_room_tables, dungeon_data.room_id).remove_shell(TRINEXX_SHELL_OBJECT_ID) + + if boss_name != "Kholdstare" and dungeon_data.room_id == KHOLDSTARE_VANILLA_ROOM_ID: + _get_room_object_table(rom, modified_room_tables, dungeon_data.room_id).remove_shell(KHOLDSTARE_SHELL_OBJECT_ID) + + if dungeon_data.gt_sprite_write_address is not None: + _write_gt_boss_sprite_block(rom, dungeon_data, boss_data) + + write_address = moved_room_object_base + for room_id in sorted(modified_room_tables): + table_bytes = modified_room_tables[room_id].to_bytes() + _write_room_object_pointer(rom, room_id, write_address) + rom.write_bytes(write_address, table_bytes) + write_address += len(table_bytes) + + rom.write_byte(0x1B0101, 0x01) + rom.write_byte(0x04DE81, 0x00) + if world.dungeons["Thieves Town"].boss.enemizer_name == "Blind": + rom.write_byte(0x04DE81, 0x06) + rom.write_byte(0x1B0101, 0x00) + + +def _get_room_object_table(rom: "LocalRom", cache: dict[int, RoomObjectTable], room_id: int) -> RoomObjectTable: + room_table = cache.get(room_id) + if room_table is not None: + return room_table + + pointer_address = 0xF8000 + (room_id * 3) + snes_address_bytes = rom.read_bytes(pointer_address, 3) + snes_address = (snes_address_bytes[2] << 16) | (snes_address_bytes[1] << 8) | snes_address_bytes[0] + room_table = RoomObjectTable.from_rom(rom, snes_to_pc(snes_address)) + cache[room_id] = room_table + return room_table + + +def _write_gt_boss_sprite_block(rom: "LocalRom", dungeon_data: DungeonBossPatchData, boss_data: BossPatchData) -> None: + assert dungeon_data.gt_sprite_write_address is not None + rom.write_int16(dungeon_data.sprite_pointer_address, dungeon_data.gt_sprite_write_address) + + sprite_block = bytearray((0x00,)) + sprite_block.extend(boss_data.sprite_array) + if dungeon_data.room_id == 28 and boss_data.pointer == BOSS_PATCH_DATA["Arrghus"].pointer: + sprite_block.extend(dungeon_data.extra_sprites[:6]) + else: + sprite_block.extend(dungeon_data.extra_sprites) + sprite_block.append(0xFF) + rom.write_bytes(dungeon_data.gt_sprite_write_address, sprite_block) + + +def _write_room_object_pointer(rom: "LocalRom", room_id: int, pc_address: int) -> None: + snes_address = pc_to_snes(pc_address) + pointer_address = 0xF8000 + (room_id * 3) + rom.write_bytes(pointer_address, ( + snes_address & 0xFF, + (snes_address >> 8) & 0xFF, + (snes_address >> 16) & 0xFF, + )) + + +def _build_subtype_3_object(x: int, y: int, object_id: int) -> bytes: + return bytes(( + ((x << 2) & 0xFC) | (object_id & 0x03), + ((y << 2) & 0xFC) | ((object_id >> 2) & 0x03), + 0xF0 | ((object_id >> 4) & 0x0F), + )) + + +def _object_id(object_bytes: bytes) -> Optional[int]: + if len(object_bytes) != 3: + return None + if object_bytes[0] >= 0xFC: + return (object_bytes[2] & 0x3F) + 0x100 + if object_bytes[2] >= 0xF8: + return 0xF00 | ((object_bytes[2] & 0x0F) << 4) | ((object_bytes[1] & 0x03) << 2) | (object_bytes[0] & 0x03) + return object_bytes[2] + + +def _set_enemizer_flag(rom: "LocalRom", symbol_name: str, enabled: bool) -> None: + rom.write_byte(_get_enemizer_symbol(symbol_name), 0x01 if enabled else 0x00) + + +def _apply_killable_thief(rom: "LocalRom") -> None: + rom.write_byte(_get_enemizer_symbol("notItemSprite_Mimic") + 4, THIEF_SPRITE_ID) + thief_hp_address = ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID + if rom.read_byte(thief_hp_address) != 0xFF: + rom.write_byte(thief_hp_address, THIEF_DEFAULT_HP) + + +def _randomize_enemy_health(rom: "LocalRom", rng: random.Random, enemy_health_key: str) -> None: + min_hp, max_hp = ENEMY_HEALTH_RANGE_BY_KEY[enemy_health_key] + for sprite_id in range(0xF3): + hp_address = ENEMY_HP_TABLE_ADDRESS + sprite_id + if rom.read_byte(hp_address) == 0xFF or sprite_id in EXCLUDED_ENEMY_TABLE_SPRITE_IDS: + continue + rom.write_byte(hp_address, rng.randrange(min_hp, max_hp)) + + +def _randomize_enemy_damage(rom: "LocalRom", rng: random.Random, allow_zero_damage: bool) -> None: + for sprite_id in range(0xF3): + if sprite_id in EXCLUDED_ENEMY_TABLE_SPRITE_IDS: + continue + new_damage = rng.randrange(8) + if not allow_zero_damage and new_damage == 2: + continue + rom.write_byte(ENEMY_DAMAGE_TABLE_ADDRESS + sprite_id, new_damage) + + +def _shuffle_damage_groups( + rom: "LocalRom", + rng: random.Random, + *, + chaos_mode: bool, + allow_zero_damage: bool, +) -> None: + min_damage = 0 if allow_zero_damage else 4 + max_damage = 64 if chaos_mode else 32 + + for group_id in range(10): + green_mail_damage = rng.randrange(min_damage, max_damage) + if chaos_mode: + blue_mail_damage = rng.randrange(min_damage, max_damage) + red_mail_damage = rng.randrange(min_damage, max_damage) + else: + blue_mail_damage = green_mail_damage * 3 // 4 + red_mail_damage = green_mail_damage * 3 // 8 + group_address = DAMAGE_GROUP_TABLE_ADDRESS + (group_id * 3) + rom.write_bytes(group_address, (green_mail_damage, blue_mail_damage, red_mail_damage)) + + +def _update_hidden_enemy_item_table_for_retro_mode(rom: "LocalRom") -> None: + if rom.read_byte(RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS) != RETRO_RUPEE_REPLACEMENT_SPRITE_ID: + return + + item_table_address = _get_enemizer_symbol("sprite_bush_spawn_item_table") + for index in range(22): + if rom.read_byte(item_table_address + index) == ARROW_REFILL_5_SPRITE_ID: + rom.write_byte(item_table_address + index, RETRO_RUPEE_REPLACEMENT_SPRITE_ID) + + +def _apply_trinexx_room_fixes(rom: "LocalRom") -> None: + # Match original Enemizer's unconditional Trinexx ice-floor removal so + # blue-head projectiles do not create solid walls in non-vanilla rooms. + rom.write_bytes(TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS, (0xEA, 0xEA, 0xEA, 0xEA)) + + +def _apply_randomized_tile_trap_floor_tile(rom: "LocalRom") -> None: + # Original Enemizer's RandomizeTileTrapFloorTile option changes the tile + # left behind by flying floor tile traps. AP does not currently expose or + # call this option, so keep the implementation isolated and unused. + rom.write_bytes(TRINEXX_ICE_PROJECTILE_TILE_ADDRESS, (0x88, 0x01)) + rom.write_byte(TILE_TRAP_FLOOR_TILE_ADDRESS, 0x12) + + +def _make_native_enemizer_rng(world: "ALTTPWorld") -> random.Random: + seed_material = "|".join(( + str(world.multiworld.seed), + world.multiworld.seed_name, + str(world.player), + _option_key(world.options.enemy_health), + _option_key(world.options.enemy_damage), + str(int(bool(world.options.enemy_shuffle))), + str(int(bool(world.options.bush_shuffle))), + str(int(bool(world.options.killable_thieves))), + )) + seed = int.from_bytes(hashlib.sha256(seed_material.encode("utf-8")).digest()[:8], "big") + return random.Random(seed) + + +@lru_cache(maxsize=1) +def _load_enemizer_base_patches() -> tuple[tuple[int, bytes], ...]: + return tuple( + (entry.address, entry.patch_data) + for entry in ENEMIZER_BASE_PATCHES + ) + + +def _option_key(option: object) -> str: + return str(getattr(option, "current_key", option)) + + +def _get_enemizer_symbol(symbol_name: str) -> int: + global _ENEMIZER_SYMBOLS + if _ENEMIZER_SYMBOLS is None: + _ENEMIZER_SYMBOLS = _load_enemizer_symbols() + return _ENEMIZER_SYMBOLS[symbol_name] + + +def _load_enemizer_symbols() -> dict[str, int]: + return { + name: snes_to_pc(snes_address) + for name, snes_address in ENEMIZER_SYMBOLS.items() + } diff --git a/worlds/alttp/EnemyShuffle.py b/worlds/alttp/EnemyShuffle.py new file mode 100644 index 0000000000..3f09920169 --- /dev/null +++ b/worlds/alttp/EnemyShuffle.py @@ -0,0 +1,1707 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Optional, TYPE_CHECKING + +from Utils import snes_to_pc + +from .EnemizerPatches import apply_enemizer_base_patch +from .Rom import LocalRom, get_base_rom_path +from .enemizer_data.dungeon_sprite_addresses import DUNGEON_SPRITE_ADDRESSES, KEYED_SPRITE_ID_ADDRESSES +from .enemizer_data.enemy_room_metadata import ( + BOSS_ROOM_IDS, + DONT_RANDOMIZE_ROOM_IDS, + NO_SPECIAL_ENEMIES_STANDARD_ROOM_IDS, + ROOM_GROUP_REQUIREMENTS, + SHUTTER_ROOM_IDS, + WATER_ROOM_IDS, +) +from .enemizer_data.enemy_sprite_requirements import ENEMY_SPRITE_REQUIREMENTS +from .enemizer_data.overworld_enemy_metadata import ( + AREA_IDS, + DO_NOT_RANDOMIZE_AREA_IDS, + FORCED_GROUP_REQUIREMENTS, +) +from .enemizer_data.symbols import ENEMIZER_SYMBOLS + +if TYPE_CHECKING: + from . import ALTTPWorld + from .Rom import LocalRom + + +DUNGEON_HEADER_POINTER_TABLE_BASE = 0x271E2 +DUNGEON_SPRITE_POINTER_TABLE_BASE = 0x4D62E +OVERWORLD_SPRITE_POINTER_TABLE_BASE = 0x4C901 +OVERWORLD_AREA_GRAPHICS_BLOCK_BASE = 0x7A81 +ROOM_HEADER_BANK_LOCATION = 0xB5E7 +SPRITE_GROUP_BASE_ADDRESS = 0x5B97 +TOTAL_SPRITE_GROUPS = 144 +TOTAL_DUNGEON_ROOMS = 0x128 + +SPRITE_OVERLORD_MASK = 0xE0 +SPRITE_OVERLORD_REMOVE_MASK = 0x1F +SPRITE_SUBTYPE_BYTE_0_MASK = 0x60 +KEY_SPRITE_ID = 0xE4 +BIG_KEY_SPRITE_ID = 0xE5 +WALLMASTER_SPRITE_ID = 0x90 +STAL_SPRITE_ID = 0xD3 +FLOPPING_FISH_SPRITE_ID = 0xD2 +OW_FALLING_ROCKS_SPRITE_ID = 0xF4 +OW_WALLMASTER_TO_HOULIHAN_SPRITE_ID = 0xFB +WATER_TEKTITE_SPRITE_ID = 0x81 +POTENTIAL_SUBGROUP_0 = (22, 31, 47, 14) +POTENTIAL_SUBGROUP_1 = (44, 30, 32) +POTENTIAL_SUBGROUP_2 = (12, 18, 23, 24, 28, 46, 34, 35, 39, 40, 38, 41, 36, 37, 42) +POTENTIAL_SUBGROUP_3 = (17, 16, 27, 20, 82, 83) +GUARD_SUBGROUP_1_DUNGEON_GROUP_IDS = frozenset((1, 2, 3, 4)) +SELECTED_BOSS_GROUP_REQUIREMENTS = { + "Armos": (9, 83), + "Lanmola": (11, 84), + "Moldorm": (12, 9), + "Arrghus": (20, 140), + "Helmasaur": (21, 146), + "Kholdstare": (22, 162), + "Vitreous": (22, 189), + "Trinexx": (23, 203), + "Mothula": (26, 136), + "Blind": (32, 206), +} + +@dataclass(frozen=True) +class RoomGroupRequirement: + group_id: Optional[int] + subgroup_0: Optional[int] + subgroup_1: Optional[int] + subgroup_2: Optional[int] + subgroup_3: Optional[int] + rooms: tuple[int, ...] + + +@dataclass(frozen=True) +class OverworldGroupRequirement: + group_id: Optional[int] + subgroup_0: Optional[int] + subgroup_1: Optional[int] + subgroup_2: Optional[int] + subgroup_3: Optional[int] + areas: tuple[int, ...] + + +@dataclass +class DungeonSpriteGroup: + group_id: int + dungeon_group_id: int + subgroup_0: int + subgroup_1: int + subgroup_2: int + subgroup_3: int + preserve_subgroup_0: bool = False + preserve_subgroup_1: bool = False + preserve_subgroup_2: bool = False + preserve_subgroup_3: bool = False + + +@dataclass(frozen=True) +class EnemySpriteRequirement: + sprite_name: str + sprite_id: int + boss: bool + overlord: bool + do_not_randomize: bool + killable: bool + npc: bool + never_use_dungeon: bool + never_use_overworld: bool + cannot_have_key: bool + is_object: bool + absorbable: bool + is_water_sprite: bool + is_enemy_sprite: bool + group_ids: tuple[int, ...] + subgroup_0: tuple[int, ...] + subgroup_1: tuple[int, ...] + subgroup_2: tuple[int, ...] + subgroup_3: tuple[int, ...] + parameters: Optional[int] + special_glitched: bool + excluded_rooms: tuple[int, ...] + dont_randomize_rooms: tuple[int, ...] + spawnable_rooms: tuple[int, ...] + + +@dataclass(frozen=True) +class DungeonEnemySprite: + address: int + byte_0: int + byte_1: int + sprite_id: int + is_overlord: bool + has_key: bool + + @property + def is_on_bg2(self) -> bool: + return bool(self.byte_0 & 0x80) + + @property + def hm_param(self) -> int: + return ((self.byte_0 & 0x60) >> 2) | ((self.byte_1 & 0xE0) >> 5) + + @property + def y_coord_pixels(self) -> int: + return (self.byte_0 & 0x1F) * 16 + + @property + def x_coord_pixels(self) -> int: + return (self.byte_1 & 0x1F) * 16 + + +@dataclass(frozen=True) +class DungeonEnemyRoom: + room_id: int + room_header_address: int + sprite_table_address: int + graphics_block_id: int + tag_1: int + tag_2: int + sort_sprites_value: int + sprites: tuple[DungeonEnemySprite, ...] + required_group_id: Optional[int] + required_subgroup_0: tuple[int, ...] + required_subgroup_1: tuple[int, ...] + required_subgroup_2: tuple[int, ...] + required_subgroup_3: tuple[int, ...] + is_shutter_room: bool + is_water_room: bool + do_not_randomize: bool + no_special_enemies_standard: bool + + +@dataclass(frozen=True) +class RandomizedDungeonEnemySprite: + address: int + byte_0: int + byte_1: int + original_sprite_id: int + sprite_id: int + is_overlord: bool + has_key: bool + + +@dataclass(frozen=True) +class RandomizedDungeonEnemyRoom: + room_id: int + room_header_address: int + sprite_table_address: int + original_graphics_block_id: int + graphics_block_id: int + tag_1: int + tag_2: int + sort_sprites_value: int + sprites: tuple[RandomizedDungeonEnemySprite, ...] + skipped_randomization: bool + + +@dataclass(frozen=True) +class OverworldEnemySprite: + address: int + y_coord: int + x_coord: int + sprite_id: int + + +@dataclass(frozen=True) +class OverworldEnemyArea: + area_id: int + sprite_table_address: int + graphics_block_address: int + graphics_block_id: int + bush_sprite_id: int + sprites: tuple[OverworldEnemySprite, ...] + do_not_randomize: bool + + +@dataclass(frozen=True) +class RandomizedOverworldEnemySprite: + address: int + y_coord: int + x_coord: int + original_sprite_id: int + sprite_id: int + + +@dataclass(frozen=True) +class RandomizedOverworldEnemyArea: + area_id: int + sprite_table_address: int + graphics_block_address: int + original_graphics_block_id: int + graphics_block_id: int + original_bush_sprite_id: int + bush_sprite_id: int + sprites: tuple[RandomizedOverworldEnemySprite, ...] + skipped_randomization: bool + + +@dataclass(frozen=True) +class EnemyShuffleState: + dungeon_rooms: dict[int, DungeonEnemyRoom] + overworld_areas: dict[int, OverworldEnemyArea] + sprite_groups: dict[int, DungeonSpriteGroup] + sprite_requirements: tuple[EnemySpriteRequirement, ...] + room_group_requirements: tuple[RoomGroupRequirement, ...] + overworld_group_requirements: tuple[OverworldGroupRequirement, ...] + shutter_room_ids: frozenset[int] + water_room_ids: frozenset[int] + dont_randomize_room_ids: frozenset[int] + no_special_enemies_standard_room_ids: frozenset[int] + boss_room_ids: frozenset[int] + dont_randomize_overworld_area_ids: frozenset[int] + randomized_dungeon_rooms: dict[int, RandomizedDungeonEnemyRoom] + randomized_overworld_areas: dict[int, RandomizedOverworldEnemyArea] + + +def generate_enemy_shuffle_state(world: "ALTTPWorld") -> EnemyShuffleState: + rom_bytes = _get_base_patched_rom_bytes() + moved_header_bank = _get_enemizer_symbol("moved_room_header_bank_value_address") + bush_spawn_table_address = _get_enemizer_symbol("sprite_bush_spawn_table_overworld") + metadata = _load_enemy_room_metadata() + overworld_metadata = _load_overworld_enemy_metadata() + sprite_requirements = _load_enemy_sprite_requirements() + dungeon_rooms = { + room.room_id: room + for room in _read_dungeon_rooms(rom_bytes, moved_header_bank, metadata) + } + overworld_areas = { + area.area_id: area + for area in _read_overworld_areas(rom_bytes, bush_spawn_table_address, overworld_metadata) + } + sprite_groups = { + group.group_id: group + for group in _read_sprite_groups(rom_bytes) + } + _setup_required_dungeon_groups(world, sprite_groups, metadata["room_requirements"]) + _apply_selected_boss_group_requirements(world, sprite_groups, sprite_requirements) + _randomize_dungeon_groups(world, sprite_groups) + randomized_dungeon_rooms = _randomize_dungeon_rooms( + world, + dungeon_rooms, + sprite_groups, + sprite_requirements, + ) + _setup_required_overworld_groups(sprite_groups, overworld_metadata["forced_group_requirements"]) + _randomize_overworld_groups(world, sprite_groups) + randomized_overworld_areas = _randomize_overworld_areas( + world, + overworld_areas, + sprite_groups, + sprite_requirements, + overworld_metadata["forced_group_requirements"], + ) + state = EnemyShuffleState( + dungeon_rooms=dungeon_rooms, + overworld_areas=overworld_areas, + sprite_groups=sprite_groups, + sprite_requirements=sprite_requirements, + room_group_requirements=metadata["room_requirements"], + overworld_group_requirements=overworld_metadata["forced_group_requirements"], + shutter_room_ids=metadata["shutter_room_ids"], + water_room_ids=metadata["water_room_ids"], + dont_randomize_room_ids=metadata["dont_randomize_room_ids"], + no_special_enemies_standard_room_ids=metadata["no_special_enemies_standard_room_ids"], + boss_room_ids=metadata["boss_room_ids"], + dont_randomize_overworld_area_ids=overworld_metadata["do_not_randomize_area_ids"], + randomized_dungeon_rooms=randomized_dungeon_rooms, + randomized_overworld_areas=randomized_overworld_areas, + ) + validate_enemy_shuffle_state(state, is_standard_mode=world.options.mode == "standard") + return state + + +def _get_base_patched_rom_bytes() -> bytes: + patched_rom_bytes = getattr(_get_base_patched_rom_bytes, "patched_rom_bytes", None) + if patched_rom_bytes is None: + patched_rom = LocalRom(get_base_rom_path()) + apply_enemizer_base_patch(patched_rom) + patched_rom_bytes = bytes(patched_rom.buffer) + _get_base_patched_rom_bytes.patched_rom_bytes = patched_rom_bytes + return patched_rom_bytes + + +def _read_dungeon_rooms(rom_bytes: bytes, moved_header_bank_address: int, metadata: dict[str, object]) -> list[DungeonEnemyRoom]: + rooms: list[DungeonEnemyRoom] = [] + room_header_bank = _get_room_header_bank(rom_bytes, moved_header_bank_address) + dungeon_sprite_metadata = _load_dungeon_sprite_metadata() + shutter_room_ids = metadata["shutter_room_ids"] + water_room_ids = metadata["water_room_ids"] + dont_randomize_room_ids = metadata["dont_randomize_room_ids"] + no_special_enemies_standard_room_ids = metadata["no_special_enemies_standard_room_ids"] + room_requirements = metadata["room_requirements"] + + for room_id in range(TOTAL_DUNGEON_ROOMS): + room_header_address = _read_room_header_address(rom_bytes, room_id, room_header_bank) + sprite_table_address = _read_room_sprite_table_address(rom_bytes, room_id) + merged_requirement = _merge_room_requirements(room_id, room_requirements) + rooms.append( + DungeonEnemyRoom( + room_id=room_id, + room_header_address=room_header_address, + sprite_table_address=sprite_table_address, + graphics_block_id=rom_bytes[room_header_address + 3], + tag_1=rom_bytes[room_header_address + 5], + tag_2=rom_bytes[room_header_address + 6], + sort_sprites_value=rom_bytes[sprite_table_address], + sprites=_read_room_sprites(rom_bytes, room_id, sprite_table_address, dungeon_sprite_metadata), + required_group_id=merged_requirement.group_id, + required_subgroup_0=merged_requirement.subgroup_0, + required_subgroup_1=merged_requirement.subgroup_1, + required_subgroup_2=merged_requirement.subgroup_2, + required_subgroup_3=merged_requirement.subgroup_3, + is_shutter_room=room_id in shutter_room_ids, + is_water_room=room_id in water_room_ids, + do_not_randomize=room_id in dont_randomize_room_ids, + no_special_enemies_standard=room_id in no_special_enemies_standard_room_ids, + ) + ) + + return rooms + + +def _get_room_header_bank(rom_bytes: bytes, moved_header_bank_address: int) -> int: + if 0 <= moved_header_bank_address < len(rom_bytes): + moved_header_bank = rom_bytes[moved_header_bank_address] + if moved_header_bank: + return moved_header_bank + return rom_bytes[ROOM_HEADER_BANK_LOCATION] + + +def _read_sprite_groups(rom_bytes: bytes) -> tuple[DungeonSpriteGroup, ...]: + groups = [] + for group_id in range(TOTAL_SPRITE_GROUPS): + groups.append( + DungeonSpriteGroup( + group_id=group_id, + dungeon_group_id=group_id - 0x40, + subgroup_0=rom_bytes[SPRITE_GROUP_BASE_ADDRESS + (group_id * 4)], + subgroup_1=rom_bytes[SPRITE_GROUP_BASE_ADDRESS + (group_id * 4) + 1], + subgroup_2=rom_bytes[SPRITE_GROUP_BASE_ADDRESS + (group_id * 4) + 2], + subgroup_3=rom_bytes[SPRITE_GROUP_BASE_ADDRESS + (group_id * 4) + 3], + ) + ) + return tuple(groups) + + +def _setup_required_dungeon_groups( + world: "ALTTPWorld", + sprite_groups: dict[int, DungeonSpriteGroup], + room_requirements: tuple[RoomGroupRequirement, ...], +) -> None: + for requirement in room_requirements: + if requirement.group_id is None: + continue + group = sprite_groups.get(requirement.group_id + 0x40) + if group is None: + continue + _apply_required_subgroups(group, requirement) + + merged_room_requirements = { + room_id: _merge_room_requirements(room_id, room_requirements) + for requirement in room_requirements + for room_id in requirement.rooms + } + + for merged_requirement in merged_room_requirements.values(): + if merged_requirement.group_id is not None: + continue + if _has_preserved_group_for_room_requirement(sprite_groups, merged_requirement): + continue + + possible_groups = [ + group for group in sprite_groups.values() + if 0 < group.dungeon_group_id < 60 + and ( + not group.preserve_subgroup_0 + or not group.preserve_subgroup_1 + or not group.preserve_subgroup_2 + or not group.preserve_subgroup_3 + ) + and (not merged_requirement.subgroup_0 or not group.preserve_subgroup_0) + and (not merged_requirement.subgroup_1 or not group.preserve_subgroup_1) + and (not merged_requirement.subgroup_2 or not group.preserve_subgroup_2) + and (not merged_requirement.subgroup_3 or not group.preserve_subgroup_3) + ] + if not possible_groups: + continue + + selected_group = world.random.choice(possible_groups) + _apply_merged_room_requirement(selected_group, merged_requirement) + + +def _apply_selected_boss_group_requirements( + world: "ALTTPWorld", + sprite_groups: dict[int, DungeonSpriteGroup], + sprite_requirements: tuple[EnemySpriteRequirement, ...], +) -> None: + requirement_by_sprite_id = {requirement.sprite_id: requirement for requirement in sprite_requirements} + for boss_name in _get_selected_boss_names(world): + boss_group_data = SELECTED_BOSS_GROUP_REQUIREMENTS.get(boss_name) + if boss_group_data is None: + continue + dungeon_group_id, sprite_id = boss_group_data + group = sprite_groups.get(dungeon_group_id + 0x40) + requirement = requirement_by_sprite_id.get(sprite_id) + if group is None or requirement is None: + continue + _apply_selected_boss_requirement(group, requirement) + + +def _get_selected_boss_names(world: "ALTTPWorld") -> tuple[str, ...]: + dungeons = getattr(world, "dungeons", None) + if not dungeons: + return tuple() + + gt_dungeon_name = "Ganons Tower" if world.options.mode != "inverted" else "Inverted Ganons Tower" + gt_dungeon = dungeons.get(gt_dungeon_name) + gt_bosses = getattr(gt_dungeon, "bosses", {}) if gt_dungeon is not None else {} + + selected_bosses = [ + dungeons["Eastern Palace"].boss.enemizer_name, + dungeons["Desert Palace"].boss.enemizer_name, + dungeons["Tower of Hera"].boss.enemizer_name, + dungeons["Palace of Darkness"].boss.enemizer_name, + dungeons["Swamp Palace"].boss.enemizer_name, + dungeons["Skull Woods"].boss.enemizer_name, + dungeons["Thieves Town"].boss.enemizer_name, + dungeons["Ice Palace"].boss.enemizer_name, + dungeons["Misery Mire"].boss.enemizer_name, + dungeons["Turtle Rock"].boss.enemizer_name, + ] + for gt_slot in ("bottom", "middle", "top"): + if gt_slot in gt_bosses: + selected_bosses.append(gt_bosses[gt_slot].enemizer_name) + return tuple(selected_bosses) + + +def _apply_selected_boss_requirement(group: DungeonSpriteGroup, requirement: EnemySpriteRequirement) -> None: + if requirement.subgroup_0: + group.subgroup_0 = requirement.subgroup_0[0] + group.preserve_subgroup_0 = True + if requirement.subgroup_1: + group.subgroup_1 = requirement.subgroup_1[0] + group.preserve_subgroup_1 = True + if requirement.subgroup_2: + group.subgroup_2 = requirement.subgroup_2[0] + group.preserve_subgroup_2 = True + if requirement.subgroup_3: + group.subgroup_3 = requirement.subgroup_3[0] + group.preserve_subgroup_3 = True + + +def _setup_required_overworld_groups( + sprite_groups: dict[int, DungeonSpriteGroup], + overworld_group_requirements: tuple[OverworldGroupRequirement, ...], +) -> None: + for requirement in overworld_group_requirements: + if requirement.group_id is None: + continue + group = sprite_groups.get(requirement.group_id) + if group is None: + continue + if ( + requirement.subgroup_0 is None + and requirement.subgroup_1 is None + and requirement.subgroup_2 is None + and requirement.subgroup_3 is None + ): + group.preserve_subgroup_0 = True + group.preserve_subgroup_1 = True + group.preserve_subgroup_2 = True + group.preserve_subgroup_3 = True + continue + _apply_required_subgroups(group, requirement) + + +def _apply_required_subgroups(group: DungeonSpriteGroup, requirement: RoomGroupRequirement | OverworldGroupRequirement) -> None: + if requirement.subgroup_0 is not None: + group.subgroup_0 = requirement.subgroup_0 + group.preserve_subgroup_0 = True + if requirement.subgroup_1 is not None: + group.subgroup_1 = requirement.subgroup_1 + group.preserve_subgroup_1 = True + if requirement.subgroup_2 is not None: + group.subgroup_2 = requirement.subgroup_2 + group.preserve_subgroup_2 = True + if requirement.subgroup_3 is not None: + group.subgroup_3 = requirement.subgroup_3 + group.preserve_subgroup_3 = True + + +def _apply_merged_room_requirement(group: DungeonSpriteGroup, requirement: MergedRoomRequirement) -> None: + if requirement.subgroup_0: + group.subgroup_0 = requirement.subgroup_0[0] + group.preserve_subgroup_0 = True + if requirement.subgroup_1: + group.subgroup_1 = requirement.subgroup_1[0] + group.preserve_subgroup_1 = True + if requirement.subgroup_2: + group.subgroup_2 = requirement.subgroup_2[0] + group.preserve_subgroup_2 = True + if requirement.subgroup_3: + group.subgroup_3 = requirement.subgroup_3[0] + group.preserve_subgroup_3 = True + + +def _has_preserved_group_for_room_requirement( + sprite_groups: dict[int, DungeonSpriteGroup], + requirement: MergedRoomRequirement, +) -> bool: + for group in sprite_groups.values(): + if not (0 < group.dungeon_group_id < 60): + continue + if requirement.subgroup_0 and (group.subgroup_0 != requirement.subgroup_0[0] or not group.preserve_subgroup_0): + continue + if requirement.subgroup_1 and (group.subgroup_1 != requirement.subgroup_1[0] or not group.preserve_subgroup_1): + continue + if requirement.subgroup_2 and (group.subgroup_2 != requirement.subgroup_2[0] or not group.preserve_subgroup_2): + continue + if requirement.subgroup_3 and (group.subgroup_3 != requirement.subgroup_3[0] or not group.preserve_subgroup_3): + continue + return True + return False + + +def _randomize_dungeon_groups(world: "ALTTPWorld", sprite_groups: dict[int, DungeonSpriteGroup]) -> None: + for group in sprite_groups.values(): + if not (0 < group.dungeon_group_id < 60): + continue + if not group.preserve_subgroup_1 and group.dungeon_group_id in GUARD_SUBGROUP_1_DUNGEON_GROUP_IDS: + group.preserve_subgroup_1 = True + group.subgroup_1 = world.random.choice((73, 13)) + if not group.preserve_subgroup_0: + group.subgroup_0 = world.random.choice(POTENTIAL_SUBGROUP_0) + if not group.preserve_subgroup_1: + group.subgroup_1 = world.random.choice(POTENTIAL_SUBGROUP_1) + if not group.preserve_subgroup_2: + group.subgroup_2 = world.random.choice(POTENTIAL_SUBGROUP_2) + if not group.preserve_subgroup_3: + group.subgroup_3 = world.random.choice(POTENTIAL_SUBGROUP_3) + + +def _randomize_overworld_groups(world: "ALTTPWorld", sprite_groups: dict[int, DungeonSpriteGroup]) -> None: + for group in sprite_groups.values(): + if not (0 < group.group_id < 0x40): + continue + if not group.preserve_subgroup_0: + group.subgroup_0 = world.random.choice(POTENTIAL_SUBGROUP_0) + if not group.preserve_subgroup_1: + group.subgroup_1 = world.random.choice(POTENTIAL_SUBGROUP_1) + if not group.preserve_subgroup_2: + group.subgroup_2 = world.random.choice(POTENTIAL_SUBGROUP_2) + if not group.preserve_subgroup_3: + group.subgroup_3 = world.random.choice(POTENTIAL_SUBGROUP_3) + + +def _read_room_header_address(rom_bytes: bytes, room_id: int, room_header_bank: int) -> int: + pointer_address = DUNGEON_HEADER_POINTER_TABLE_BASE + (room_id * 2) + snes_address = ( + rom_bytes[pointer_address] + | (rom_bytes[pointer_address + 1] << 8) + | (room_header_bank << 16) + ) + return snes_to_pc(snes_address) + + +def _read_room_sprite_table_address(rom_bytes: bytes, room_id: int) -> int: + pointer_address = DUNGEON_SPRITE_POINTER_TABLE_BASE + (room_id * 2) + snes_address = ( + rom_bytes[pointer_address] + | (rom_bytes[pointer_address + 1] << 8) + | (0x09 << 16) + ) + return snes_to_pc(snes_address) + + +def _read_overworld_areas( + rom_bytes: bytes, + bush_spawn_table_address: int, + metadata: dict[str, object], +) -> list[OverworldEnemyArea]: + areas: list[OverworldEnemyArea] = [] + do_not_randomize_area_ids = metadata["do_not_randomize_area_ids"] + + for area_id in metadata["area_ids"]: + sprite_table_address = _read_overworld_sprite_table_address(rom_bytes, area_id) + graphics_block_address = _get_overworld_graphics_block_address(area_id) + areas.append( + OverworldEnemyArea( + area_id=area_id, + sprite_table_address=sprite_table_address, + graphics_block_address=graphics_block_address, + graphics_block_id=rom_bytes[graphics_block_address], + bush_sprite_id=rom_bytes[bush_spawn_table_address + area_id], + sprites=_read_overworld_sprites(rom_bytes, sprite_table_address), + do_not_randomize=area_id in do_not_randomize_area_ids, + ) + ) + + return areas + + +def _read_overworld_sprite_table_address(rom_bytes: bytes, area_id: int) -> int: + pointer_address = OVERWORLD_SPRITE_POINTER_TABLE_BASE + (area_id * 2) + snes_address = ( + rom_bytes[pointer_address] + | (rom_bytes[pointer_address + 1] << 8) + | (0x09 << 16) + ) + return snes_to_pc(snes_address) + + +def _get_overworld_graphics_block_address(area_id: int) -> int: + if area_id in {0x80, 0x81}: + return 0x16576 + (area_id - 0x80) + if area_id in {0x110, 0x111}: + return 0x16576 + (area_id - 0x110) + + address = OVERWORLD_AREA_GRAPHICS_BLOCK_BASE + area_id + if 0x40 <= area_id < 0x80: + address += 0x40 + if 0x90 <= area_id < 0x110: + address -= 0x50 + return address + + +def _read_overworld_sprites(rom_bytes: bytes, sprite_table_address: int) -> tuple[OverworldEnemySprite, ...]: + sprites: list[OverworldEnemySprite] = [] + index = sprite_table_address + while rom_bytes[index] != 0xFF: + sprites.append( + OverworldEnemySprite( + address=index, + y_coord=rom_bytes[index], + x_coord=rom_bytes[index + 1], + sprite_id=rom_bytes[index + 2], + ) + ) + index += 3 + return tuple(sprites) + + +def _read_room_sprites( + rom_bytes: bytes, + room_id: int, + sprite_table_address: int, + dungeon_sprite_metadata: dict[str, object], +) -> tuple[DungeonEnemySprite, ...]: + sprites: list[DungeonEnemySprite] = [] + keyed_sprite_id_addresses = dungeon_sprite_metadata["keyed_sprite_id_addresses"] + editable_sprite_id_addresses = dungeon_sprite_metadata["room_sprite_id_addresses"].get(room_id) + + if editable_sprite_id_addresses is None: + sprite_addresses = [] + index = sprite_table_address + 1 # byte 0 is sort-sprites metadata + while rom_bytes[index] != 0xFF: + sprite_addresses.append(index) + index += 3 + else: + sprite_addresses = [sprite_id_address - 2 for sprite_id_address in editable_sprite_id_addresses] + + seen_sprite_addresses: set[int] = set() + unique_sprite_addresses = [] + for address in sprite_addresses: + if address in seen_sprite_addresses: + continue + seen_sprite_addresses.add(address) + unique_sprite_addresses.append(address) + + for index in unique_sprite_addresses: + byte_0 = rom_bytes[index] + byte_1 = rom_bytes[index + 1] + sprite_id = rom_bytes[index + 2] + is_overlord = (byte_1 & SPRITE_OVERLORD_MASK) == SPRITE_OVERLORD_MASK and ( + (byte_0 & SPRITE_SUBTYPE_BYTE_0_MASK) != SPRITE_SUBTYPE_BYTE_0_MASK + ) + if not is_overlord and sprite_id not in {KEY_SPRITE_ID, WALLMASTER_SPRITE_ID}: + byte_0 &= 0x9F + byte_1 &= SPRITE_OVERLORD_REMOVE_MASK + has_key = (index + 2) in keyed_sprite_id_addresses + sprites.append( + DungeonEnemySprite( + address=index, + byte_0=byte_0, + byte_1=byte_1, + sprite_id=sprite_id + (0x100 if is_overlord else 0), + is_overlord=is_overlord, + has_key=has_key, + ) + ) + + return tuple(sprites) + + +def _get_enemizer_symbol(symbol_name: str) -> int: + return snes_to_pc(ENEMIZER_SYMBOLS[symbol_name]) + + +def _load_enemy_room_metadata() -> dict[str, object]: + return { + "shutter_room_ids": SHUTTER_ROOM_IDS, + "water_room_ids": WATER_ROOM_IDS, + "dont_randomize_room_ids": DONT_RANDOMIZE_ROOM_IDS, + "no_special_enemies_standard_room_ids": NO_SPECIAL_ENEMIES_STANDARD_ROOM_IDS, + "boss_room_ids": BOSS_ROOM_IDS, + "room_requirements": tuple( + RoomGroupRequirement( + group_id=requirement.group_id, + subgroup_0=requirement.subgroup_0, + subgroup_1=requirement.subgroup_1, + subgroup_2=requirement.subgroup_2, + subgroup_3=requirement.subgroup_3, + rooms=requirement.rooms, + ) + for requirement in ROOM_GROUP_REQUIREMENTS + ), + } + + +def _load_dungeon_sprite_metadata() -> dict[str, object]: + return { + "room_sprite_id_addresses": { + room.room_id: room.sprite_id_addresses + for room in DUNGEON_SPRITE_ADDRESSES + }, + "keyed_sprite_id_addresses": KEYED_SPRITE_ID_ADDRESSES, + } + + +def _load_enemy_sprite_requirements() -> tuple[EnemySpriteRequirement, ...]: + return tuple( + EnemySpriteRequirement( + sprite_name=entry.sprite_name, + sprite_id=entry.sprite_id, + boss=entry.boss, + overlord=entry.overlord, + do_not_randomize=entry.do_not_randomize, + killable=entry.killable, + npc=entry.npc, + never_use_dungeon=entry.never_use_dungeon, + never_use_overworld=entry.never_use_overworld, + cannot_have_key=entry.cannot_have_key, + is_object=entry.is_object, + absorbable=entry.absorbable, + is_water_sprite=entry.is_water_sprite, + is_enemy_sprite=entry.is_enemy_sprite, + group_ids=entry.group_ids, + subgroup_0=entry.subgroup_0, + subgroup_1=entry.subgroup_1, + subgroup_2=entry.subgroup_2, + subgroup_3=entry.subgroup_3, + parameters=entry.parameters, + special_glitched=entry.special_glitched, + excluded_rooms=entry.excluded_rooms, + dont_randomize_rooms=entry.dont_randomize_rooms, + spawnable_rooms=entry.spawnable_rooms, + ) + for entry in ENEMY_SPRITE_REQUIREMENTS + ) + + +def _load_overworld_enemy_metadata() -> dict[str, object]: + return { + "area_ids": AREA_IDS, + "do_not_randomize_area_ids": DO_NOT_RANDOMIZE_AREA_IDS, + "forced_group_requirements": tuple( + OverworldGroupRequirement( + group_id=requirement.group_id, + subgroup_0=requirement.subgroup_0, + subgroup_1=requirement.subgroup_1, + subgroup_2=requirement.subgroup_2, + subgroup_3=requirement.subgroup_3, + areas=requirement.areas, + ) + for requirement in FORCED_GROUP_REQUIREMENTS + ), + } + + +@dataclass(frozen=True) +class MergedRoomRequirement: + group_id: Optional[int] + subgroup_0: tuple[int, ...] + subgroup_1: tuple[int, ...] + subgroup_2: tuple[int, ...] + subgroup_3: tuple[int, ...] + + +def _merge_room_requirements(room_id: int, room_requirements: tuple[RoomGroupRequirement, ...]) -> MergedRoomRequirement: + group_id: Optional[int] = None + subgroup_0: list[int] = [] + subgroup_1: list[int] = [] + subgroup_2: list[int] = [] + subgroup_3: list[int] = [] + + for requirement in room_requirements: + if room_id not in requirement.rooms: + continue + if requirement.group_id is not None: + group_id = requirement.group_id + if requirement.subgroup_0 is not None: + subgroup_0.append(requirement.subgroup_0) + if requirement.subgroup_1 is not None: + subgroup_1.append(requirement.subgroup_1) + if requirement.subgroup_2 is not None: + subgroup_2.append(requirement.subgroup_2) + if requirement.subgroup_3 is not None: + subgroup_3.append(requirement.subgroup_3) + + return MergedRoomRequirement( + group_id=group_id, + subgroup_0=tuple(subgroup_0), + subgroup_1=tuple(subgroup_1), + subgroup_2=tuple(subgroup_2), + subgroup_3=tuple(subgroup_3), + ) + + +def get_room_do_not_update_requirements(state: EnemyShuffleState, room: DungeonEnemyRoom) -> tuple[EnemySpriteRequirement, ...]: + room_sprite_ids = {sprite.sprite_id for sprite in room.sprites} + return tuple( + requirement for requirement in state.sprite_requirements + if (requirement.do_not_randomize or room.room_id in requirement.dont_randomize_rooms) + and requirement.sprite_id in room_sprite_ids + and can_spawn_in_room(requirement, room) + ) + + +def get_possible_dungeon_sprite_groups(state: EnemyShuffleState, room: DungeonEnemyRoom) -> tuple[DungeonSpriteGroup, ...]: + do_not_update = get_room_do_not_update_requirements(state, room) + usable_groups = tuple( + group for group in state.sprite_groups.values() + if 0 < group.dungeon_group_id < 60 + and _get_possible_enemy_requirements_for_group(state, room, group) + ) + needs_key = any(sprite.has_key for sprite in room.sprites) + needs_killable = room.is_shutter_room + needs_water = room.is_water_room + room_requirements = _get_requirements_for_usable_dungeon_enemies(state) + water_requirements = tuple(requirement for requirement in room_requirements if requirement.is_water_sprite) + killable_requirements = tuple( + requirement for requirement in state.sprite_requirements + if _is_effectively_killable(requirement) and requirement.sprite_id != STAL_SPRITE_ID + ) + key_requirements = tuple(requirement for requirement in killable_requirements if not requirement.cannot_have_key) + + if ( + not needs_key and not needs_killable and not needs_water + and not do_not_update + and room.required_group_id is None + and not room.required_subgroup_0 + and not room.required_subgroup_1 + and not room.required_subgroup_2 + and not room.required_subgroup_3 + ): + return _get_unconstrained_possible_dungeon_sprite_groups(usable_groups, room_requirements, water_requirements) + + return tuple( + group for group in usable_groups + if ( + (not do_not_update or _build_requirement_group_matcher(do_not_update)(group)) + and _group_matches_room_requirement(group, room) + and ( + lambda possible_requirements: ( + (not needs_killable or any( + _is_effectively_killable(requirement) and requirement.sprite_id != STAL_SPRITE_ID + for requirement in _filter_requirements_for_room_water_state(room, possible_requirements) + )) + and (not needs_key or any( + _is_effectively_killable(requirement) + and not requirement.cannot_have_key + and requirement.sprite_id != STAL_SPRITE_ID + for requirement in _filter_requirements_for_room_water_state(room, possible_requirements) + )) + and (not needs_water or any( + requirement.is_water_sprite + for requirement in _filter_requirements_for_room_water_state(room, possible_requirements) + )) + ) + )(_get_possible_enemy_requirements_for_group(state, room, group)) + ) + ) + + +def can_spawn_in_room(requirement: EnemySpriteRequirement, room: DungeonEnemyRoom) -> bool: + return ( + room.room_id not in requirement.excluded_rooms + and (not requirement.spawnable_rooms or room.room_id in requirement.spawnable_rooms) + and (requirement.sprite_id != WALLMASTER_SPRITE_ID or room.room_id < 0x100) + ) + + +def _get_requirements_for_usable_dungeon_enemies(state: EnemyShuffleState) -> tuple[EnemySpriteRequirement, ...]: + return tuple( + requirement for requirement in state.sprite_requirements + if not requirement.npc + and requirement.is_enemy_sprite + and not requirement.boss + and not requirement.overlord + and not requirement.is_object + and not requirement.absorbable + and not requirement.never_use_dungeon + ) + + +def _get_requirements_for_usable_overworld_enemies(state: EnemyShuffleState) -> tuple[EnemySpriteRequirement, ...]: + return tuple( + requirement for requirement in state.sprite_requirements + if not requirement.npc + and requirement.is_enemy_sprite + and not requirement.boss + and not requirement.overlord + and not requirement.is_object + and not requirement.absorbable + and not requirement.never_use_overworld + ) + + +def _filter_requirements_for_room_water_state( + room: DungeonEnemyRoom, + requirements: tuple[EnemySpriteRequirement, ...], +) -> tuple[EnemySpriteRequirement, ...]: + if room.is_water_room: + return tuple(requirement for requirement in requirements if requirement.is_water_sprite) + return tuple(requirement for requirement in requirements if not requirement.is_water_sprite) + + +def _is_effectively_killable(requirement: EnemySpriteRequirement) -> bool: + return requirement.killable or requirement.sprite_id == WATER_TEKTITE_SPRITE_ID + + +def _get_effectively_killable_sprite_ids(requirements: tuple[EnemySpriteRequirement, ...]) -> set[int]: + return { + requirement.sprite_id for requirement in requirements + if _is_effectively_killable(requirement) and requirement.sprite_id != STAL_SPRITE_ID + } + + +def _get_unconstrained_possible_dungeon_sprite_groups( + usable_groups: tuple[DungeonSpriteGroup, ...], + room_requirements: tuple[EnemySpriteRequirement, ...], + water_requirements: tuple[EnemySpriteRequirement, ...], +) -> tuple[DungeonSpriteGroup, ...]: + water_subgroup_3 = set(_flatten_requirement_values(water_requirements, "subgroup_3")) + included_group_ids = set(_flatten_requirement_values(room_requirements, "group_ids")) + included_subgroup_0 = set(_flatten_requirement_values(room_requirements, "subgroup_0")) + included_subgroup_1 = set(_flatten_requirement_values(room_requirements, "subgroup_1")) + included_subgroup_2 = set(_flatten_requirement_values(room_requirements, "subgroup_2")) + included_subgroup_3 = { + subgroup for subgroup in _flatten_requirement_values(room_requirements, "subgroup_3") + if subgroup not in water_subgroup_3 and subgroup not in {54, 80} + } + + return tuple( + group for group in usable_groups + if group.group_id in included_group_ids + or group.subgroup_0 in included_subgroup_0 + or group.subgroup_1 in included_subgroup_1 + or group.subgroup_2 in included_subgroup_2 + or group.subgroup_3 in included_subgroup_3 + ) + + +def _build_requirement_group_matcher(requirements: tuple[EnemySpriteRequirement, ...]): + allowed_group_ids = set(_flatten_requirement_values(requirements, "group_ids")) + allowed_subgroup_0 = set(_flatten_requirement_values(requirements, "subgroup_0")) + allowed_subgroup_1 = set(_flatten_requirement_values(requirements, "subgroup_1")) + allowed_subgroup_2 = set(_flatten_requirement_values(requirements, "subgroup_2")) + allowed_subgroup_3 = set(_flatten_requirement_values(requirements, "subgroup_3")) + + def matches(group: DungeonSpriteGroup) -> bool: + return ( + not allowed_group_ids or group.group_id in allowed_group_ids + ) and ( + not allowed_subgroup_0 or group.subgroup_0 in allowed_subgroup_0 + ) and ( + not allowed_subgroup_1 or group.subgroup_1 in allowed_subgroup_1 + ) and ( + not allowed_subgroup_2 or group.subgroup_2 in allowed_subgroup_2 + ) and ( + not allowed_subgroup_3 or group.subgroup_3 in allowed_subgroup_3 + ) + + return matches + + +def _build_overworld_requirement_group_matcher(requirements: tuple[EnemySpriteRequirement, ...]): + allowed_group_ids = set(_flatten_requirement_values(requirements, "group_ids")) + allowed_subgroup_0 = set(_flatten_requirement_values(requirements, "subgroup_0")) + allowed_subgroup_1 = set(_flatten_requirement_values(requirements, "subgroup_1")) + allowed_subgroup_2 = set(_flatten_requirement_values(requirements, "subgroup_2")) + allowed_subgroup_3 = set(_flatten_requirement_values(requirements, "subgroup_3")) + + def matches(group: DungeonSpriteGroup) -> bool: + return ( + not allowed_group_ids or group.group_id in allowed_group_ids + ) and ( + not allowed_subgroup_0 or group.subgroup_0 in allowed_subgroup_0 + ) and ( + not allowed_subgroup_1 or group.subgroup_1 in allowed_subgroup_1 + ) and ( + not allowed_subgroup_2 or group.subgroup_2 in allowed_subgroup_2 + ) and ( + not allowed_subgroup_3 or group.subgroup_3 in allowed_subgroup_3 + ) + + return matches + + +def _build_requirement_group_presence_matcher(requirements: tuple[EnemySpriteRequirement, ...]): + allowed_group_ids = set(_flatten_requirement_values(requirements, "group_ids")) + allowed_subgroup_0 = set(_flatten_requirement_values(requirements, "subgroup_0")) + allowed_subgroup_1 = set(_flatten_requirement_values(requirements, "subgroup_1")) + allowed_subgroup_2 = set(_flatten_requirement_values(requirements, "subgroup_2")) + allowed_subgroup_3 = set(_flatten_requirement_values(requirements, "subgroup_3")) + + def matches(group: DungeonSpriteGroup) -> bool: + return ( + group.group_id in allowed_group_ids + or group.subgroup_0 in allowed_subgroup_0 + or group.subgroup_1 in allowed_subgroup_1 + or group.subgroup_2 in allowed_subgroup_2 + or group.subgroup_3 in allowed_subgroup_3 + ) + + return matches + + +def _flatten_requirement_values(requirements: tuple[EnemySpriteRequirement, ...], attribute: str) -> tuple[int, ...]: + return tuple( + value + for requirement in requirements + for value in getattr(requirement, attribute) + ) + + +def _group_matches_room_requirement(group: DungeonSpriteGroup, room: DungeonEnemyRoom) -> bool: + return ( + (room.required_group_id is None or room.required_group_id == group.dungeon_group_id) + and (not room.required_subgroup_0 or group.subgroup_0 in room.required_subgroup_0) + and (not room.required_subgroup_1 or group.subgroup_1 in room.required_subgroup_1) + and (not room.required_subgroup_2 or group.subgroup_2 in room.required_subgroup_2) + and (not room.required_subgroup_3 or group.subgroup_3 in room.required_subgroup_3) + ) + + +def get_overworld_do_not_update_requirements( + state: EnemyShuffleState, + area: OverworldEnemyArea, +) -> tuple[EnemySpriteRequirement, ...]: + area_sprite_ids = {sprite.sprite_id for sprite in area.sprites} + return tuple( + requirement for requirement in state.sprite_requirements + if requirement.do_not_randomize and requirement.sprite_id in area_sprite_ids + ) + + +def get_possible_overworld_sprite_groups( + state: EnemyShuffleState, + area: OverworldEnemyArea, +) -> tuple[DungeonSpriteGroup, ...]: + usable_groups = tuple( + group for group in state.sprite_groups.values() + if 0 < group.group_id < 0x40 + and _get_possible_enemy_requirements_for_overworld_group(state, group) + ) + do_not_update = get_overworld_do_not_update_requirements(state, area) + if not do_not_update: + return usable_groups + + do_not_update_matcher = _build_overworld_requirement_group_matcher(do_not_update) + return tuple(group for group in usable_groups if do_not_update_matcher(group)) + + +def _get_possible_enemy_requirements_for_group( + state: EnemyShuffleState, + room: DungeonEnemyRoom, + group: DungeonSpriteGroup, +) -> tuple[EnemySpriteRequirement, ...]: + dungeon_requirements = _get_requirements_for_usable_dungeon_enemies(state) + return tuple( + requirement for requirement in dungeon_requirements + if can_spawn_in_room(requirement, room) + and ( + not requirement.group_ids or group.dungeon_group_id in requirement.group_ids + ) + and (not requirement.subgroup_0 or group.subgroup_0 in requirement.subgroup_0) + and (not requirement.subgroup_1 or group.subgroup_1 in requirement.subgroup_1) + and (not requirement.subgroup_2 or group.subgroup_2 in requirement.subgroup_2) + and (not requirement.subgroup_3 or group.subgroup_3 in requirement.subgroup_3) + ) + + +def _get_randomizable_sprites_in_room( + state: EnemyShuffleState, + room: DungeonEnemyRoom, +) -> tuple[DungeonEnemySprite, ...]: + randomizable_sprite_ids = { + requirement.sprite_id for requirement in state.sprite_requirements + if not requirement.do_not_randomize and room.room_id not in requirement.dont_randomize_rooms + } + return tuple(sprite for sprite in room.sprites if sprite.sprite_id in randomizable_sprite_ids) + + +def _get_possible_enemy_requirements_for_overworld_group( + state: EnemyShuffleState, + group: DungeonSpriteGroup, +) -> tuple[EnemySpriteRequirement, ...]: + overworld_requirements = _get_requirements_for_usable_overworld_enemies(state) + return tuple( + requirement for requirement in overworld_requirements + if ( + not requirement.group_ids or group.group_id in requirement.group_ids + ) + and (not requirement.subgroup_0 or group.subgroup_0 in requirement.subgroup_0) + and (not requirement.subgroup_1 or group.subgroup_1 in requirement.subgroup_1) + and (not requirement.subgroup_2 or group.subgroup_2 in requirement.subgroup_2) + and (not requirement.subgroup_3 or group.subgroup_3 in requirement.subgroup_3) + ) + + +def _get_randomizable_sprites_in_overworld_area( + state: EnemyShuffleState, + area: OverworldEnemyArea, +) -> tuple[OverworldEnemySprite, ...]: + randomizable_sprite_ids = { + requirement.sprite_id for requirement in state.sprite_requirements + if not requirement.do_not_randomize + } + return tuple(sprite for sprite in area.sprites if sprite.sprite_id in randomizable_sprite_ids) + + +def _randomize_dungeon_rooms( + world: "ALTTPWorld", + dungeon_rooms: dict[int, DungeonEnemyRoom], + sprite_groups: dict[int, DungeonSpriteGroup], + sprite_requirements: tuple[EnemySpriteRequirement, ...], +) -> dict[int, RandomizedDungeonEnemyRoom]: + state = EnemyShuffleState( + dungeon_rooms=dungeon_rooms, + overworld_areas={}, + sprite_groups=sprite_groups, + sprite_requirements=sprite_requirements, + room_group_requirements=tuple(), + overworld_group_requirements=tuple(), + shutter_room_ids=frozenset(room.room_id for room in dungeon_rooms.values() if room.is_shutter_room), + water_room_ids=frozenset(room.room_id for room in dungeon_rooms.values() if room.is_water_room), + dont_randomize_room_ids=frozenset(room.room_id for room in dungeon_rooms.values() if room.do_not_randomize), + no_special_enemies_standard_room_ids=frozenset( + room.room_id for room in dungeon_rooms.values() if room.no_special_enemies_standard + ), + boss_room_ids=frozenset(), + dont_randomize_overworld_area_ids=frozenset(), + randomized_dungeon_rooms={}, + randomized_overworld_areas={}, + ) + randomized_rooms: dict[int, RandomizedDungeonEnemyRoom] = {} + + for room_id in sorted(dungeon_rooms): + room = dungeon_rooms[room_id] + skip_randomization = room.do_not_randomize or ( + world.options.mode == "standard" and room.no_special_enemies_standard + ) + + selected_group = sprite_groups.get(room.graphics_block_id + 0x40) + if not skip_randomization: + possible_groups = get_possible_dungeon_sprite_groups(state, room) + if possible_groups: + selected_group = world.random.choice(possible_groups) + + if selected_group is None: + selected_group = sprite_groups[room.graphics_block_id + 0x40] + + randomized_rooms[room_id] = _randomize_room_sprites( + world, + state, + room, + selected_group, + skip_randomization, + ) + + return randomized_rooms + + +def _randomize_overworld_areas( + world: "ALTTPWorld", + overworld_areas: dict[int, OverworldEnemyArea], + sprite_groups: dict[int, DungeonSpriteGroup], + sprite_requirements: tuple[EnemySpriteRequirement, ...], + forced_group_requirements: tuple[OverworldGroupRequirement, ...], +) -> dict[int, RandomizedOverworldEnemyArea]: + state = EnemyShuffleState( + dungeon_rooms={}, + overworld_areas=overworld_areas, + sprite_groups=sprite_groups, + sprite_requirements=sprite_requirements, + room_group_requirements=tuple(), + overworld_group_requirements=forced_group_requirements, + shutter_room_ids=frozenset(), + water_room_ids=frozenset(), + dont_randomize_room_ids=frozenset(), + no_special_enemies_standard_room_ids=frozenset(), + boss_room_ids=frozenset(), + dont_randomize_overworld_area_ids=frozenset(area.area_id for area in overworld_areas.values() if area.do_not_randomize), + randomized_dungeon_rooms={}, + randomized_overworld_areas={}, + ) + randomized_areas: dict[int, RandomizedOverworldEnemyArea] = {} + + for area_id in sorted(overworld_areas): + area = overworld_areas[area_id] + selected_group = sprite_groups.get(area.graphics_block_id) + if not area.do_not_randomize: + possible_groups = get_possible_overworld_sprite_groups(state, area) + if possible_groups: + selected_group = world.random.choice(possible_groups) + + forced_group = _get_forced_overworld_group(area.area_id, forced_group_requirements, sprite_groups) + if forced_group is not None: + selected_group = forced_group + if selected_group is None: + selected_group = sprite_groups[area.graphics_block_id] + + randomized_areas[area_id] = _randomize_overworld_area_sprites( + world, + state, + area, + selected_group, + area.do_not_randomize, + ) + + return randomized_areas + + +def _get_forced_overworld_group( + area_id: int, + forced_group_requirements: tuple[OverworldGroupRequirement, ...], + sprite_groups: dict[int, DungeonSpriteGroup], +) -> Optional[DungeonSpriteGroup]: + for requirement in forced_group_requirements: + if area_id not in requirement.areas or requirement.group_id is None: + continue + return sprite_groups.get(requirement.group_id) + return None + + +def _randomize_room_sprites( + world: "ALTTPWorld", + state: EnemyShuffleState, + room: DungeonEnemyRoom, + selected_group: DungeonSpriteGroup, + skip_randomization: bool, +) -> RandomizedDungeonEnemyRoom: + randomized_sprites = list(_clone_room_sprites(room)) + + if not skip_randomization: + possible_requirements = _get_possible_enemy_requirements_for_group(state, room, selected_group) + sprites_to_update = _get_randomizable_sprites_in_room(state, room) + sprites_to_update_addresses = {sprite.address for sprite in sprites_to_update} + + if possible_requirements: + water_sprite_ids = [ + requirement.sprite_id for requirement in possible_requirements + if requirement.is_water_sprite + ] + + if room.is_water_room: + if water_sprite_ids: + replacement_water_sprite_ids = water_sprite_ids + if room.is_shutter_room: + killable_water_sprite_ids = [ + requirement.sprite_id for requirement in possible_requirements + if requirement.is_water_sprite + and _is_effectively_killable(requirement) + and requirement.sprite_id != STAL_SPRITE_ID + ] + if killable_water_sprite_ids: + replacement_water_sprite_ids = killable_water_sprite_ids + for sprite in randomized_sprites: + if sprite.address in sprites_to_update_addresses: + _set_randomized_sprite_id( + randomized_sprites, + sprite.address, + world.random.choice(replacement_water_sprite_ids), + ) + return _build_randomized_room(room, selected_group, randomized_sprites, False) + + non_water_requirements = _filter_requirements_for_room_water_state(room, possible_requirements) + possible_sprite_ids = [requirement.sprite_id for requirement in non_water_requirements] + if not possible_sprite_ids: + return _build_randomized_room(room, selected_group, randomized_sprites, False) + killable_sprite_ids = [ + requirement.sprite_id for requirement in non_water_requirements + if _is_effectively_killable(requirement) and requirement.sprite_id != STAL_SPRITE_ID + ] + killable_key_sprite_ids = [ + requirement.sprite_id for requirement in non_water_requirements + if _is_effectively_killable(requirement) and not requirement.cannot_have_key and requirement.sprite_id != STAL_SPRITE_ID + ] + stal_count = 0 + + for sprite in sprites_to_update: + replacement_sprite_id: int + if sprite.has_key and killable_key_sprite_ids: + replacement_sprite_id = world.random.choice(killable_key_sprite_ids) + elif room.is_shutter_room and killable_sprite_ids: + replacement_sprite_id = world.random.choice(killable_sprite_ids) + elif not room.is_shutter_room and world.random.randrange(100) < 5: + replacement_sprite_id = STAL_SPRITE_ID + else: + replacement_sprite_id = world.random.choice(possible_sprite_ids) + + _set_randomized_sprite_id(randomized_sprites, sprite.address, replacement_sprite_id) + + if replacement_sprite_id == STAL_SPRITE_ID: + stal_count += 1 + if stal_count > 2: + possible_sprite_ids = [sprite_id for sprite_id in possible_sprite_ids if sprite_id != STAL_SPRITE_ID] + + return _build_randomized_room(room, selected_group, randomized_sprites, skip_randomization) + + +def _randomize_overworld_area_sprites( + world: "ALTTPWorld", + state: EnemyShuffleState, + area: OverworldEnemyArea, + selected_group: DungeonSpriteGroup, + skip_randomization: bool, +) -> RandomizedOverworldEnemyArea: + randomized_sprites = list(_clone_overworld_area_sprites(area)) + bush_sprite_id = area.bush_sprite_id + + if not skip_randomization: + possible_requirements = _get_possible_enemy_requirements_for_overworld_group(state, selected_group) + possible_sprite_ids = [requirement.sprite_id for requirement in possible_requirements] + sprites_to_update = _get_randomizable_sprites_in_overworld_area(state, area) + sprites_to_update_addresses = {sprite.address for sprite in sprites_to_update} + + if possible_sprite_ids: + for sprite in sprites_to_update: + _set_randomized_overworld_sprite_id( + randomized_sprites, + sprite.address, + world.random.choice(possible_sprite_ids), + ) + + flopping_fish_addresses = [ + sprite.address for sprite in randomized_sprites + if sprite.address in sprites_to_update_addresses and sprite.sprite_id == FLOPPING_FISH_SPRITE_ID + ] + if len(flopping_fish_addresses) > 1: + non_fish_sprite_ids = [ + sprite_id for sprite_id in possible_sprite_ids if sprite_id != FLOPPING_FISH_SPRITE_ID + ] + for address in flopping_fish_addresses[1:]: + if non_fish_sprite_ids: + _set_randomized_overworld_sprite_id( + randomized_sprites, + address, + world.random.choice(non_fish_sprite_ids), + ) + + bush_candidates = [ + requirement.sprite_id for requirement in possible_requirements + if not requirement.overlord + ] + if bush_candidates: + bush_sprite_id = world.random.choice(bush_candidates) + + return RandomizedOverworldEnemyArea( + area_id=area.area_id, + sprite_table_address=area.sprite_table_address, + graphics_block_address=area.graphics_block_address, + original_graphics_block_id=area.graphics_block_id, + graphics_block_id=selected_group.group_id, + original_bush_sprite_id=area.bush_sprite_id, + bush_sprite_id=bush_sprite_id, + sprites=tuple(randomized_sprites), + skipped_randomization=skip_randomization, + ) + + +def _clone_room_sprites(room: DungeonEnemyRoom) -> list[RandomizedDungeonEnemySprite]: + return [ + RandomizedDungeonEnemySprite( + address=sprite.address, + byte_0=sprite.byte_0, + byte_1=sprite.byte_1, + original_sprite_id=sprite.sprite_id, + sprite_id=sprite.sprite_id, + is_overlord=sprite.is_overlord, + has_key=sprite.has_key, + ) + for sprite in room.sprites + ] + + +def _clone_overworld_area_sprites(area: OverworldEnemyArea) -> list[RandomizedOverworldEnemySprite]: + return [ + RandomizedOverworldEnemySprite( + address=sprite.address, + y_coord=sprite.y_coord, + x_coord=sprite.x_coord, + original_sprite_id=sprite.sprite_id, + sprite_id=sprite.sprite_id, + ) + for sprite in area.sprites + ] + + +def _set_randomized_sprite_id( + randomized_sprites: list[RandomizedDungeonEnemySprite], + address: int, + sprite_id: int, +) -> None: + for index, sprite in enumerate(randomized_sprites): + if sprite.address != address: + continue + randomized_sprites[index] = RandomizedDungeonEnemySprite( + address=sprite.address, + byte_0=sprite.byte_0, + byte_1=sprite.byte_1, + original_sprite_id=sprite.original_sprite_id, + sprite_id=sprite_id, + is_overlord=sprite.is_overlord, + has_key=sprite.has_key, + ) + return + + +def _set_randomized_overworld_sprite_id( + randomized_sprites: list[RandomizedOverworldEnemySprite], + address: int, + sprite_id: int, +) -> None: + for index, sprite in enumerate(randomized_sprites): + if sprite.address != address: + continue + randomized_sprites[index] = RandomizedOverworldEnemySprite( + address=sprite.address, + y_coord=sprite.y_coord, + x_coord=sprite.x_coord, + original_sprite_id=sprite.original_sprite_id, + sprite_id=sprite_id, + ) + return + + +def _build_randomized_room( + room: DungeonEnemyRoom, + selected_group: DungeonSpriteGroup, + sprites: list[RandomizedDungeonEnemySprite], + skipped_randomization: bool, +) -> RandomizedDungeonEnemyRoom: + return RandomizedDungeonEnemyRoom( + room_id=room.room_id, + room_header_address=room.room_header_address, + sprite_table_address=room.sprite_table_address, + original_graphics_block_id=room.graphics_block_id, + graphics_block_id=selected_group.dungeon_group_id, + tag_1=room.tag_1, + tag_2=room.tag_2, + sort_sprites_value=room.sort_sprites_value, + sprites=tuple(sprites), + skipped_randomization=skipped_randomization, + ) + + +def validate_enemy_shuffle_state(state: EnemyShuffleState, is_standard_mode: bool) -> None: + for room_id, room in state.dungeon_rooms.items(): + randomized_room = state.randomized_dungeon_rooms[room_id] + _validate_dungeon_room(state, room, randomized_room, is_standard_mode) + + for area_id, area in state.overworld_areas.items(): + randomized_area = state.randomized_overworld_areas[area_id] + _validate_overworld_area(state, area, randomized_area) + + +def _validate_dungeon_room( + state: EnemyShuffleState, + room: DungeonEnemyRoom, + randomized_room: RandomizedDungeonEnemyRoom, + is_standard_mode: bool, +) -> None: + selected_group = state.sprite_groups.get(randomized_room.graphics_block_id + 0x40) + if selected_group is None: + raise ValueError(f"Enemy shuffle produced unknown dungeon sprite group {randomized_room.graphics_block_id} for room {room.room_id}") + + skipped = room.do_not_randomize or (is_standard_mode and room.no_special_enemies_standard) + if skipped and randomized_room.graphics_block_id != room.graphics_block_id: + raise ValueError(f"Enemy shuffle changed skipped room {room.room_id} graphics block") + + if not skipped: + possible_groups = get_possible_dungeon_sprite_groups(state, room) + if possible_groups and selected_group not in possible_groups: + raise ValueError(f"Enemy shuffle selected illegal sprite group {selected_group.group_id} for room {room.room_id}") + + possible_requirements = _get_possible_enemy_requirements_for_group(state, room, selected_group) + possible_sprite_ids = {requirement.sprite_id for requirement in possible_requirements} + killable_sprite_ids = _get_effectively_killable_sprite_ids(possible_requirements) + killable_key_sprite_ids = { + requirement.sprite_id for requirement in possible_requirements + if _is_effectively_killable(requirement) and not requirement.cannot_have_key and requirement.sprite_id != STAL_SPRITE_ID + } + water_sprite_ids = { + requirement.sprite_id for requirement in possible_requirements + if requirement.is_water_sprite + } + if not room.is_water_room: + possible_sprite_ids -= water_sprite_ids + killable_sprite_ids -= water_sprite_ids + killable_key_sprite_ids -= water_sprite_ids + do_not_randomize_sprite_ids = { + requirement.sprite_id for requirement in state.sprite_requirements + if requirement.do_not_randomize or room.room_id in requirement.dont_randomize_rooms + } + randomized_by_address = {sprite.address: sprite for sprite in randomized_room.sprites} + + for original_sprite in room.sprites: + randomized_sprite = randomized_by_address[original_sprite.address] + if original_sprite.sprite_id in do_not_randomize_sprite_ids and randomized_sprite.sprite_id != original_sprite.sprite_id: + raise ValueError(f"Enemy shuffle changed do-not-randomize sprite in room {room.room_id} at {hex(original_sprite.address)}") + + if original_sprite.sprite_id in do_not_randomize_sprite_ids or skipped: + continue + + if room.is_water_room: + if randomized_sprite.sprite_id not in water_sprite_ids: + raise ValueError(f"Enemy shuffle placed non-water enemy {hex(randomized_sprite.sprite_id)} in water room {room.room_id}") + continue + if randomized_sprite.sprite_id in water_sprite_ids: + raise ValueError(f"Enemy shuffle placed water enemy {hex(randomized_sprite.sprite_id)} in non-water room {room.room_id}") + + if original_sprite.has_key: + if randomized_sprite.sprite_id not in killable_key_sprite_ids: + raise ValueError(f"Enemy shuffle placed invalid key enemy {hex(randomized_sprite.sprite_id)} in room {room.room_id}") + continue + + if room.is_shutter_room and randomized_sprite.sprite_id not in killable_sprite_ids: + raise ValueError(f"Enemy shuffle placed non-killable shutter enemy {hex(randomized_sprite.sprite_id)} in room {room.room_id}") + + if randomized_sprite.sprite_id != STAL_SPRITE_ID and randomized_sprite.sprite_id not in possible_sprite_ids: + raise ValueError(f"Enemy shuffle placed illegal sprite {hex(randomized_sprite.sprite_id)} in room {room.room_id}") + + if room.is_shutter_room and _get_randomizable_sprites_in_room(state, room): + all_killable_sprite_ids = _get_effectively_killable_sprite_ids( + _filter_requirements_for_room_water_state(room, state.sprite_requirements) + ) + randomized_sprite_ids = {sprite.sprite_id for sprite in randomized_room.sprites} + if not (randomized_sprite_ids & all_killable_sprite_ids): + raise ValueError(f"Enemy shuffle left shutter room {room.room_id} without any killable enemies") + + +def _validate_overworld_area( + state: EnemyShuffleState, + area: OverworldEnemyArea, + randomized_area: RandomizedOverworldEnemyArea, +) -> None: + selected_group = state.sprite_groups.get(randomized_area.graphics_block_id) + if selected_group is None: + raise ValueError(f"Enemy shuffle produced unknown overworld sprite group {randomized_area.graphics_block_id} for area {hex(area.area_id)}") + + if area.do_not_randomize and randomized_area.graphics_block_id != area.graphics_block_id: + raise ValueError(f"Enemy shuffle changed skipped overworld area {hex(area.area_id)} graphics block") + + forced_group = _get_forced_overworld_group(area.area_id, state.overworld_group_requirements, state.sprite_groups) + if forced_group is not None and randomized_area.graphics_block_id != forced_group.group_id: + raise ValueError(f"Enemy shuffle failed forced overworld group for area {hex(area.area_id)}") + + if not area.do_not_randomize and forced_group is None: + possible_groups = get_possible_overworld_sprite_groups(state, area) + if possible_groups and selected_group not in possible_groups: + raise ValueError(f"Enemy shuffle selected illegal overworld group {selected_group.group_id} for area {hex(area.area_id)}") + + possible_requirements = _get_possible_enemy_requirements_for_overworld_group(state, selected_group) + possible_sprite_ids = {requirement.sprite_id for requirement in possible_requirements} + bush_sprite_ids = { + requirement.sprite_id for requirement in possible_requirements + if not requirement.overlord + } + known_sprite_ids = {requirement.sprite_id for requirement in state.sprite_requirements} + do_not_randomize_sprite_ids = { + requirement.sprite_id for requirement in state.sprite_requirements + if requirement.do_not_randomize + } + randomized_by_address = {sprite.address: sprite for sprite in randomized_area.sprites} + + for original_sprite in area.sprites: + randomized_sprite = randomized_by_address[original_sprite.address] + if original_sprite.sprite_id not in known_sprite_ids: + continue + if original_sprite.sprite_id in do_not_randomize_sprite_ids and randomized_sprite.sprite_id != original_sprite.sprite_id: + raise ValueError(f"Enemy shuffle changed do-not-randomize overworld sprite in area {hex(area.area_id)} at {hex(original_sprite.address)}") + if original_sprite.sprite_id in do_not_randomize_sprite_ids or area.do_not_randomize: + continue + if randomized_sprite.sprite_id not in possible_sprite_ids: + raise ValueError(f"Enemy shuffle placed illegal overworld sprite {hex(randomized_sprite.sprite_id)} in area {hex(area.area_id)}") + + randomizable_addresses = {sprite.address for sprite in _get_randomizable_sprites_in_overworld_area(state, area)} + non_fish_sprite_ids = possible_sprite_ids - {FLOPPING_FISH_SPRITE_ID} + if non_fish_sprite_ids and sum( + 1 for sprite in randomized_area.sprites + if sprite.address in randomizable_addresses and sprite.sprite_id == FLOPPING_FISH_SPRITE_ID + ) > 1: + raise ValueError(f"Enemy shuffle placed multiple flopping fish in area {hex(area.area_id)}") + + if area.do_not_randomize and randomized_area.bush_sprite_id != area.bush_sprite_id: + raise ValueError(f"Enemy shuffle changed skipped overworld bush sprite in area {hex(area.area_id)}") + if not area.do_not_randomize and bush_sprite_ids and randomized_area.bush_sprite_id not in bush_sprite_ids: + raise ValueError(f"Enemy shuffle placed illegal bush enemy {hex(randomized_area.bush_sprite_id)} in area {hex(area.area_id)}") + + +def apply_enemy_shuffle(rom: "LocalRom", state: EnemyShuffleState) -> None: + for group in state.sprite_groups.values(): + _write_sprite_group(rom, group) + + for room in state.randomized_dungeon_rooms.values(): + rom.write_byte(room.room_header_address + 3, room.graphics_block_id) + for sprite in room.sprites: + _write_dungeon_sprite(rom, sprite) + + rom.write_byte(0x04CF4F, 0x10) + for area in state.randomized_overworld_areas.values(): + rom.write_byte(area.graphics_block_address, area.graphics_block_id) + for sprite in area.sprites: + _write_overworld_sprite(rom, sprite) + + bush_spawn_table_address = _get_enemizer_symbol("sprite_bush_spawn_table_overworld") + for area in state.randomized_overworld_areas.values(): + rom.write_byte(bush_spawn_table_address + area.area_id, area.bush_sprite_id) + + +def _write_sprite_group(rom: "LocalRom", group: DungeonSpriteGroup) -> None: + address = SPRITE_GROUP_BASE_ADDRESS + (group.group_id * 4) + rom.write_byte(address, group.subgroup_0) + rom.write_byte(address + 1, group.subgroup_1) + rom.write_byte(address + 2, group.subgroup_2) + rom.write_byte(address + 3, group.subgroup_3) + + +def _write_dungeon_sprite(rom: "LocalRom", sprite: RandomizedDungeonEnemySprite) -> None: + sprite_id = sprite.sprite_id + byte_1 = sprite.byte_1 + + if sprite_id == WALLMASTER_SPRITE_ID: + sprite_id = 0x09 + byte_1 |= SPRITE_OVERLORD_MASK + + rom.write_byte(sprite.address, sprite.byte_0) + rom.write_byte(sprite.address + 1, byte_1) + rom.write_byte(sprite.address + 2, sprite_id & 0xFF) + + +def _write_overworld_sprite(rom: "LocalRom", sprite: RandomizedOverworldEnemySprite) -> None: + sprite_id = sprite.sprite_id + if sprite_id == OW_FALLING_ROCKS_SPRITE_ID: + rom.write_byte(sprite.address, 0) + rom.write_byte(sprite.address + 1, 0) + if sprite_id == WALLMASTER_SPRITE_ID: + sprite_id = OW_WALLMASTER_TO_HOULIHAN_SPRITE_ID + rom.write_byte(sprite.address + 2, sprite_id & 0xFF) diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index c7dc7a6948..5b9d443b5d 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -9,6 +9,7 @@ from .SubClasses import ALttPLocation, LTTPRegion, LTTPRegionType from .Shops import TakeAny, total_shop_slots, set_up_shops, shop_table_by_location, ShopType from .Bosses import place_bosses from .Dungeons import get_dungeon_item_pool_player +from .EnemyShuffle import generate_enemy_shuffle_state from .EntranceShuffle import connect_entrance from .Items import item_factory, GetBeemizerItem, trap_replaceable, item_name_groups from .Options import small_key_shuffle, compass_shuffle, big_key_shuffle, map_shuffle, TriforcePiecesMode, LTTPBosses @@ -511,6 +512,8 @@ def generate_itempool(world: "ALTTPWorld"): world.options.turtle_rock_medallion.current_key.title()) place_bosses(world) + if world.options.enemy_shuffle: + world.enemy_shuffle_state = generate_enemy_shuffle_state(world) multiworld.itempool += items diff --git a/worlds/alttp/PotShuffle.py b/worlds/alttp/PotShuffle.py new file mode 100644 index 0000000000..f1f8825983 --- /dev/null +++ b/worlds/alttp/PotShuffle.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from Utils import snes_to_pc +from .enemizer_data.pot_shuffle_data import POT_ROOMS + +if TYPE_CHECKING: + from . import ALTTPWorld + from .Rom import LocalRom + + +POT_ITEM_POINTER_TABLE = 0xDB67 +POT_KEY = 0x08 +POT_ARROW = 0x09 +POT_BLUE_RUPEE = 0x07 +POT_SWITCH = 0x88 +POT_HOLE = 0x80 + + +@dataclass(frozen=True) +class PotData: + x: int + y: int + reserved: int + + +@dataclass(frozen=True) +class PotRoomData: + room_id: int + pots: tuple[PotData, ...] + items: tuple[int, ...] + + +@dataclass(frozen=True) +class FilledPot: + x: int + y: int + item: int + + +def generate_pot_shuffle(world: "ALTTPWorld") -> dict[int, tuple[FilledPot, ...]]: + room_data = _load_pot_room_data() + shuffled_pots: dict[int, tuple[FilledPot, ...]] = {} + + for room in room_data: + room_items = [item for item in room.items if item != POT_HOLE] + if world.options.retro_bow: + room_items = [POT_BLUE_RUPEE if item == POT_ARROW else item for item in room_items] + + empty_pots: list[PotData] = [] + filled_pots: list[FilledPot] = [] + + for pot in room.pots: + if pot.reserved == 3: + filled_pots.append(FilledPot(pot.x, pot.y, POT_HOLE)) + else: + empty_pots.append(pot) + + while POT_KEY in room_items: + candidate_indices = [index for index, pot in enumerate(empty_pots) if pot.reserved == 1] + if not candidate_indices: + break + pot_index = world.random.choice(candidate_indices) + pot = empty_pots.pop(pot_index) + room_items.remove(POT_KEY) + filled_pots.append(FilledPot(pot.x, pot.y, POT_KEY)) + + while POT_SWITCH in room_items: + candidate_indices = [index for index, pot in enumerate(empty_pots) if pot.reserved == 2] + if not candidate_indices: + break + pot_index = world.random.choice(candidate_indices) + pot = empty_pots.pop(pot_index) + room_items.remove(POT_SWITCH) + filled_pots.append(FilledPot(pot.x, pot.y, POT_SWITCH)) + + while room_items and empty_pots: + pot_index = world.random.randrange(len(empty_pots)) + item_index = world.random.randrange(len(room_items)) + pot = empty_pots.pop(pot_index) + item = room_items.pop(item_index) + filled_pots.append(FilledPot(pot.x, pot.y, item)) + + shuffled_pots[room.room_id] = tuple(filled_pots) + + return shuffled_pots + + +def apply_pot_shuffle(rom: "LocalRom", shuffled_pots: dict[int, tuple[FilledPot, ...]]) -> None: + for room_id, pots in shuffled_pots.items(): + pointer_address = POT_ITEM_POINTER_TABLE + (room_id * 2) + snes_address = rom.read_byte(pointer_address) | (rom.read_byte(pointer_address + 1) << 8) | (0x01 << 16) + address = snes_to_pc(snes_address) + for index, pot in enumerate(pots): + rom.write_bytes(address + (index * 3), (pot.x, pot.y, pot.item)) + + +def get_unique_pot_item_position( + shuffled_pots: dict[int, tuple[FilledPot, ...]], + room_id: int, + item: int, +) -> tuple[int, int]: + positions = [ + (pot.x, pot.y) + for pot in shuffled_pots.get(room_id, ()) + if pot.item == item + ] + if len(positions) != 1: + raise ValueError( + f"Expected exactly one pot item {hex(item)} in room {hex(room_id)}, found {len(positions)}" + ) + return positions[0] + + +def _load_pot_room_data() -> tuple[PotRoomData, ...]: + return tuple( + PotRoomData( + room_id=room.room_id, + pots=tuple(PotData(x=pot.x, y=pot.y, reserved=pot.reserved) for pot in room.pots), + items=room.items, + ) + for room in POT_ROOMS + ) diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index c58083b5da..78cb33993d 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -15,7 +15,6 @@ import logging import os import random import struct -import subprocess import threading import concurrent.futures import bsdiff4 @@ -53,9 +52,6 @@ try: except: xxtea = None -enemizer_logger = logging.getLogger("Enemizer") - - class LocalRom: def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None): @@ -179,43 +175,6 @@ class LocalRom: self.write_int32(startaddress + (i * 4), value) -check_lock = threading.Lock() - - -def check_enemizer(enemizercli): - if getattr(check_enemizer, "done", None): - return - if not os.path.exists(enemizercli) and not os.path.exists(enemizercli + ".exe"): - raise Exception(f"Enemizer not found at {enemizercli}, please install it. " - f"Such as https://github.com/Ijwu/Enemizer/releases") - - with check_lock: - # some time may have passed since the lock was acquired, as such a quick re-check doesn't hurt - if getattr(check_enemizer, "done", None): - return - wanted_version = (7, 1, 0) - # version info is saved on the lib, for some reason - library_info = os.path.join(os.path.dirname(enemizercli), "EnemizerCLI.Core.deps.json") - with open(library_info) as f: - info = json.load(f) - - for lib in info["libraries"]: - if lib.startswith("EnemizerLibrary/"): - version = lib.split("/")[-1] - version = tuple(int(element) for element in version.split(".")) - enemizer_logger.debug(f"Found Enemizer version {version}") - if version < wanted_version: - raise Exception( - f"Enemizer found at {enemizercli} is outdated ({version}) < ({wanted_version}), " - f"please update your Enemizer. " - f"Such as from https://github.com/Ijwu/Enemizer/releases") - break - else: - raise Exception(f"Could not find Enemizer library version information in {library_info}") - - check_enemizer.done = True - - def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_random_on_event, sprite_pool): userandomsprites = False if sprite and not isinstance(sprite, Sprite): @@ -282,174 +241,6 @@ def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_rand rom.write_bytes(0x307000 + (i * 0x8000), sprite.palette) rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette) - -def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): - player = world.player - check_enemizer(enemizercli) - randopatch_path = os.path.abspath(os.path.join(output_directory, f'enemizer_randopatch_{player}.sfc')) - options_path = os.path.abspath(os.path.join(output_directory, f'enemizer_options_{player}.json')) - enemizer_output_path = os.path.abspath(os.path.join(output_directory, f'enemizer_output_{player}.sfc')) - - # write options file for enemizer - options = { - 'RandomizeEnemies': world.options.enemy_shuffle.value, - 'RandomizeEnemiesType': 3, - 'RandomizeBushEnemyChance': world.options.bush_shuffle.value, - 'RandomizeEnemyHealthRange': world.options.enemy_health != 'default', - 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[ - world.options.enemy_health.current_key], - 'OHKO': False, - 'RandomizeEnemyDamage': world.options.enemy_damage != 'default', - 'AllowEnemyZeroDamage': True, - 'ShuffleEnemyDamageGroups': world.options.enemy_damage != 'default', - 'EnemyDamageChaosMode': world.options.enemy_damage == 'chaos', - 'EasyModeEscape': world.options.mode == "standard", - 'EnemiesAbsorbable': False, - 'AbsorbableSpawnRate': 10, - 'AbsorbableTypes': { - 'FullMagic': True, 'SmallMagic': True, 'Bomb_1': True, 'BlueRupee': True, 'Heart': True, 'BigKey': True, - 'Key': True, - 'Fairy': True, 'Arrow_10': True, 'Arrow_5': True, 'Bomb_8': True, 'Bomb_4': True, 'GreenRupee': True, - 'RedRupee': True - }, - 'BossMadness': False, - 'RandomizeBosses': True, - 'RandomizeBossesType': 0, - 'RandomizeBossHealth': False, - 'RandomizeBossHealthMinAmount': 0, - 'RandomizeBossHealthMaxAmount': 300, - 'RandomizeBossDamage': False, - 'RandomizeBossDamageMinAmount': 0, - 'RandomizeBossDamageMaxAmount': 200, - 'RandomizeBossBehavior': False, - 'RandomizeDungeonPalettes': False, - 'SetBlackoutMode': False, - 'RandomizeOverworldPalettes': False, - 'RandomizeSpritePalettes': False, - 'SetAdvancedSpritePalettes': False, - 'PukeMode': False, - 'NegativeMode': False, - 'GrayscaleMode': False, - 'GenerateSpoilers': False, - 'RandomizeLinkSpritePalette': False, - 'RandomizePots': world.options.pot_shuffle.value, - 'ShuffleMusic': False, - 'BootlegMagic': True, - 'CustomBosses': False, - 'AndyMode': False, - 'HeartBeepSpeed': 0, - 'AlternateGfx': False, - 'ShieldGraphics': "shield_gfx/normal.gfx", - 'SwordGraphics': "sword_gfx/normal.gfx", - 'BeeMizer': False, - 'BeesLevel': 0, - 'RandomizeTileTrapPattern': False, - 'RandomizeTileTrapFloorTile': False, - 'AllowKillableThief': world.options.killable_thieves.value, - 'RandomizeSpriteOnHit': False, - 'DebugMode': False, - 'DebugForceEnemy': False, - 'DebugForceEnemyId': 0, - 'DebugForceBoss': False, - 'DebugForceBossId': 0, - 'DebugOpenShutterDoors': False, - 'DebugForceEnemyDamageZero': False, - 'DebugShowRoomIdInRupeeCounter': False, - 'UseManualBosses': True, - 'ManualBosses': { - 'EasternPalace': world.dungeons["Eastern Palace"].boss.enemizer_name, - 'DesertPalace': world.dungeons["Desert Palace"].boss.enemizer_name, - 'TowerOfHera': world.dungeons["Tower of Hera"].boss.enemizer_name, - 'AgahnimsTower': 'Agahnim', - 'PalaceOfDarkness': world.dungeons["Palace of Darkness"].boss.enemizer_name, - 'SwampPalace': world.dungeons["Swamp Palace"].boss.enemizer_name, - 'SkullWoods': world.dungeons["Skull Woods"].boss.enemizer_name, - 'ThievesTown': world.dungeons["Thieves Town"].boss.enemizer_name, - 'IcePalace': world.dungeons["Ice Palace"].boss.enemizer_name, - 'MiseryMire': world.dungeons["Misery Mire"].boss.enemizer_name, - 'TurtleRock': world.dungeons["Turtle Rock"].boss.enemizer_name, - 'GanonsTower1': - world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else - "Inverted Ganons Tower"].bosses['bottom'].enemizer_name, - 'GanonsTower2': - world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else - "Inverted Ganons Tower"].bosses['middle'].enemizer_name, - 'GanonsTower3': - world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else - "Inverted Ganons Tower"].bosses['top'].enemizer_name, - 'GanonsTower4': 'Agahnim2', - 'Ganon': 'Ganon', - } - } - - rom.write_to_file(randopatch_path) - - with open(options_path, 'w') as f: - json.dump(options, f) - - max_enemizer_tries = 5 - for i in range(max_enemizer_tries): - enemizer_seed = str(world.random.randint(0, 999999999)) - enemizer_command = [os.path.abspath(enemizercli), - '--rom', randopatch_path, - '--seed', enemizer_seed, - '--binary', - '--enemizer', options_path, - '--output', enemizer_output_path] - - p_open = subprocess.Popen(enemizer_command, - cwd=os.path.dirname(enemizercli), - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True) - - enemizer_logger.debug( - f"Enemizer attempt {i + 1} of {max_enemizer_tries} for player {player} using enemizer seed {enemizer_seed}") - for stdout_line in iter(p_open.stdout.readline, ""): - if i == max_enemizer_tries - 1: - enemizer_logger.warning(stdout_line.rstrip()) - else: - enemizer_logger.debug(stdout_line.rstrip()) - p_open.stdout.close() - - return_code = p_open.wait() - if return_code: - if i == max_enemizer_tries - 1: - raise subprocess.CalledProcessError(return_code, enemizer_command) - continue - - for j in range(i + 1, max_enemizer_tries): - world.random.randint(0, 999999999) - # Sacrifice all remaining random numbers that would have been used for unused enemizer tries. - # This allows for future enemizer bug fixes to NOT affect the rest of the seed's randomness - break - - rom.read_from_file(enemizer_output_path) - os.remove(enemizer_output_path) - - if world.dungeons["Thieves Town"].boss.enemizer_name == "Blind": - rom.write_byte(0x04DE81, 6) - rom.write_byte(0x1B0101, 0) # Do not close boss room door on entry. - - # Moblins attached to "key drop" locations crash the game when dropping their item when Key Drop Shuffle is on. - # Replace them with a Slime enemy if they are placed. - if world.options.key_drop_shuffle: - key_drop_enemies = { - 0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201, - 0x4E20A, 0x4E326, 0x4E4F7, 0x4E687, 0x4E70C, 0x4E7C8, 0x4E7FA - } - for enemy in key_drop_enemies: - if rom.read_byte(enemy) == 0x12: - logging.debug(f"Moblin found and replaced at {enemy} in world {player}") - rom.write_byte(enemy, 0x8F) - - for used in (randopatch_path, options_path): - try: - os.remove(used) - except OSError: - pass - - tile_list_lock = threading.Lock() _tile_collection_table = [] @@ -795,9 +586,13 @@ def get_nonnative_item_sprite(code: int) -> int: # https://discord.com/channels/731205301247803413/827141303330406408/852102450822905886 -def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int, enemized: bool): +def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int): local_random = multiworld.worlds[player].random local_world = multiworld.worlds[player] + enemized = bool(local_world.options.boss_shuffle or local_world.options.enemy_shuffle + or local_world.options.enemy_health != 'default' or local_world.options.enemy_damage != 'default' + or local_world.options.pot_shuffle or local_world.options.bush_shuffle + or local_world.options.killable_thieves) # patch items @@ -1757,6 +1552,71 @@ def patch_rom(multiworld: MultiWorld, rom: LocalRom, player: int, enemized: bool if encoded_players > ROM_PLAYER_LIMIT: rom.write_bytes(0x195FFC + ((ROM_PLAYER_LIMIT - 1) * 32), hud_format_text("Archipelago")) + if enemized: + from . import EnemizerPatches as enemizer_patches + from .EnemyShuffle import apply_enemy_shuffle + from .PotShuffle import apply_pot_shuffle + + enemizer_patches.apply_enemizer_base_patch(rom) + + enemy_shuffle_enabled = bool(local_world.options.enemy_shuffle) + bush_shuffle_enabled = bool(local_world.options.bush_shuffle) + enemy_health_key = enemizer_patches._option_key(local_world.options.enemy_health) + enemy_damage_key = enemizer_patches._option_key(local_world.options.enemy_damage) + + if enemy_shuffle_enabled or bush_shuffle_enabled: + enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_randomize_bushes", True) + hidden_enemy_chance_pool = ( + enemizer_patches.RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL + if bush_shuffle_enabled + else enemizer_patches.VANILLA_HIDDEN_ENEMY_CHANCE_POOL + ) + rom.write_bytes(enemizer_patches.HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, hidden_enemy_chance_pool) + enemizer_patches._update_hidden_enemy_item_table_for_retro_mode(rom) + + if enemy_shuffle_enabled: + enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_randomize_sprites", True) + enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_enable_mimic_override", True) + enemizer_patches._set_enemizer_flag(rom, "EnemizerFlags_enable_terrorpin_ai_fix", True) + rom.write_bytes(0x1F2D5, (0x54, 0x9C)) + rom.write_byte(0x1F2E5, 0xB0) + rom.write_byte(0x1F2EB, 0xD0) + + if local_world.options.killable_thieves: + enemizer_patches._apply_killable_thief(rom) + + if enemy_health_key != "default" or enemy_damage_key != "default": + rng = enemizer_patches._make_native_enemizer_rng(local_world) + else: + rng = None + + if enemy_health_key != "default": + assert rng is not None + enemizer_patches._randomize_enemy_health(rom, rng, enemy_health_key) + + if enemy_damage_key != "default": + assert rng is not None + enemizer_patches._randomize_enemy_damage(rom, rng, allow_zero_damage=True) + enemizer_patches._shuffle_damage_groups( + rom, + rng, + chaos_mode=enemy_damage_key == "chaos", + allow_zero_damage=True, + ) + + enemy_shuffle_state = getattr(local_world, "enemy_shuffle_state", None) + if local_world.options.enemy_shuffle and enemy_shuffle_state is not None: + apply_enemy_shuffle(rom, enemy_shuffle_state) + + if local_world.options.boss_shuffle: + # Boss shuffle must run after enemy shuffle so boss room sprite pointers + # and graphics block IDs are not restored to the enemy-shuffled room values. + enemizer_patches.patch_bosses(local_world, rom) + + pot_shuffle_state = getattr(local_world, "pot_shuffle_state", None) + if local_world.options.pot_shuffle and pot_shuffle_state is not None: + apply_pot_shuffle(rom, pot_shuffle_state) + # Write title screen Code hashint = int(rom.get_hash(), 16) code = [ @@ -1907,7 +1767,7 @@ def apply_oof_sfx(rom: LocalRom, oof: str): rom.write_bytes(0x12803A, oof_bytes) rom.write_bytes(0x12803A + len(oof_bytes), [0xEB, 0xEB]) - # Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT") + # Preserve SPC $3188 instead of writing the unused "WHAT" sound effect there. rom.write_bytes(0x13000D, [0x00, 0x00, 0x00, 0x08]) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 2b99162837..187ce67918 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -14,9 +14,10 @@ from .InvertedRegions import create_inverted_regions, mark_dark_world_regions from .ItemPool import generate_itempool, difficulties from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem from .Options import ALTTPOptions, small_key_shuffle +from .PotShuffle import generate_pot_shuffle from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance, \ is_main_entrance, key_drop_data -from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \ +from .Rom import LocalRom, patch_rom, patch_race_rom, apply_rom_settings, \ get_hash_string, get_base_rom_path, LttPDeltaPatch from .Rules import set_rules from .Shops import create_shops, Shop, push_shop_inventories, ShopType, price_rate_display, price_type_display_name @@ -253,17 +254,6 @@ class ALTTPWorld(World): create_items = generate_itempool - _enemizer_path: typing.ClassVar[typing.Optional[str]] = None - - @property - def enemizer_path(self) -> str: - # TODO: directly use settings - cls = self.__class__ - if cls._enemizer_path is None: - cls._enemizer_path = settings.get_settings().generator.enemizer_path - assert isinstance(cls._enemizer_path, str) - return cls._enemizer_path - # custom instance vars dungeon_local_item_names: typing.Set[str] dungeon_specific_item_names: typing.Set[str] @@ -305,6 +295,8 @@ class ALTTPWorld(World): self.required_medallions = ["Ether", "Quake"] self.escape_assist = [] self.shops = [] + self.enemy_shuffle_state = None + self.pot_shuffle_state = None self.logical_heart_containers = 10 self.logical_heart_pieces = 24 super(ALTTPWorld, self).__init__(*args, **kwargs) @@ -316,10 +308,6 @@ class ALTTPWorld(World): raise FileNotFoundError(rom_file) if multiworld.is_race: import xxtea # noqa - for player in multiworld.get_game_players(cls.game): - if multiworld.worlds[player].use_enemizer: - check_enemizer(multiworld.worlds[player].enemizer_path) - break def generate_early(self): multiworld = self.multiworld @@ -339,6 +327,9 @@ class ALTTPWorld(World): self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options) self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options) + if self.options.pot_shuffle: + self.pot_shuffle_state = generate_pot_shuffle(self) + if self.options.mode == 'standard': if self.options.small_key_shuffle: if (self.options.small_key_shuffle not in @@ -564,13 +555,6 @@ class ALTTPWorld(World): def stage_generate_output(cls, multiworld, output_directory): push_shop_inventories(multiworld) - @property - def use_enemizer(self) -> bool: - return bool(self.options.boss_shuffle or self.options.enemy_shuffle - or self.options.enemy_health != 'default' or self.options.enemy_damage != 'default' - or self.options.pot_shuffle or self.options.bush_shuffle - or self.options.killable_thieves) - def generate_output(self, output_directory: str): multiworld = self.multiworld player = self.player @@ -578,14 +562,9 @@ class ALTTPWorld(World): self.pushed_shop_inventories.wait() try: - use_enemizer = self.use_enemizer - rom = LocalRom(get_base_rom_path()) - patch_rom(multiworld, rom, player, use_enemizer) - - if use_enemizer: - patch_enemizer(self, rom, self.enemizer_path, output_directory) + patch_rom(multiworld, rom, player) if multiworld.is_race: patch_race_rom(rom, multiworld, player) diff --git a/worlds/alttp/enemizer_data/README.md b/worlds/alttp/enemizer_data/README.md new file mode 100644 index 0000000000..f2e9371ead --- /dev/null +++ b/worlds/alttp/enemizer_data/README.md @@ -0,0 +1,27 @@ +These modules are vendored/generated from the upstream Enemizer compiled release and source that were already present +locally in `/home/alchav/PycharmProjects/Archipelago/EnemizerCLI` and `/home/alchav/PycharmProjects/Archipelago/Enemizer`. + +Source details: + +- Upstream project: `Ijwu/Enemizer` +- Release family: `7.1` +- Library version from `EnemizerCLI/EnemizerCLI.Core.deps.json`: `EnemizerLibrary/7.1.0` + +Vendored data modules: + +- `base_patch_data.py` +- `symbols.py` +- `enemy_room_metadata.py` +- `enemy_sprite_requirements.py` +- `overworld_enemy_metadata.py` +- `dungeon_sprite_addresses.py` +- `pot_shuffle_data.py` + +Purpose: + +- `base_patch_data.py` contains the generated base patch Enemizer applies before feature-specific randomization. +- `symbols.py` contains the assembled symbol map consumed by Enemizer's runtime code for ROM addresses. +- `enemy_room_metadata.py` and `overworld_enemy_metadata.py` contain room and area grouping/randomization constraints. +- `enemy_sprite_requirements.py` contains the sprite metadata used by the native enemy shuffle implementation. +- `dungeon_sprite_addresses.py` contains dungeon sprite slot metadata derived from Enemizer's source tables and keyed-enemy address list. +- `pot_shuffle_data.py` contains the native pot shuffle room/item source data. diff --git a/worlds/alttp/enemizer_data/__init__.py b/worlds/alttp/enemizer_data/__init__.py new file mode 100644 index 0000000000..7a18008b01 --- /dev/null +++ b/worlds/alttp/enemizer_data/__init__.py @@ -0,0 +1 @@ +"""Native ALTTP Enemizer data modules.""" diff --git a/worlds/alttp/enemizer_data/base_patch_data.py b/worlds/alttp/enemizer_data/base_patch_data.py new file mode 100644 index 0000000000..5fd7845393 --- /dev/null +++ b/worlds/alttp/enemizer_data/base_patch_data.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from typing import NamedTuple + + +class BasePatchData(NamedTuple): + address: int + patch_data: bytes + +ENEMIZER_BASE_PATCHES = ( + BasePatchData(address=208, patch_data=b'\\l\x956'), + BasePatchData(address=2304, patch_data=b'k'), + BasePatchData(address=2317, patch_data=b'"\x92\x9a6'), + BasePatchData(address=2335, patch_data=b'"\x98\x9a6'), + BasePatchData(address=65816, patch_data=b'"\x87\x9a6\xea\xea'), + BasePatchData(address=66134, patch_data=b'"C\x976'), + BasePatchData(address=67657, patch_data=b'"1\x976'), + BasePatchData(address=67961, patch_data=b'"C\x976'), + BasePatchData(address=68630, patch_data=b'"C\x976'), + BasePatchData(address=70456, patch_data=b'"C\x976'), + BasePatchData(address=192354, patch_data=b'"\xbe\x996'), + BasePatchData(address=197241, patch_data=b'\xea\xea\xea\xea\xea\xea\xea\xea\xea\xea"\xf5\x946\xea'), + BasePatchData(address=198603, patch_data=b'\xb6\x91'), + BasePatchData(address=201142, patch_data=b'"\x1a\x9a6'), + BasePatchData(address=224264, patch_data=b'"N\x9a6\xea'), + BasePatchData(address=224678, patch_data=b'"f\x9a6'), + BasePatchData(address=229578, patch_data=b'"\xb2\x996\xea\xea\xea\xd0$\xea\xea'), + BasePatchData(address=312587, patch_data=b'"S\xb76\xea\xb7\x00'), + BasePatchData(address=312600, patch_data=b'\xb7\x00'), + BasePatchData(address=312616, patch_data=b'\xb7\x00'), + BasePatchData(address=312625, patch_data=b'\x85\n'), + BasePatchData(address=312628, patch_data=b'\xb7\x00'), + BasePatchData(address=312635, patch_data=b'e\n'), + BasePatchData(address=312646, patch_data=b'\xb7\x00'), + BasePatchData(address=312655, patch_data=b'\xb7\x00'), + BasePatchData(address=317847, patch_data=b'\x00\x07\x07\x8c\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\x07\x07\x8d\xff\x00\t\t\t\xff'), + BasePatchData(address=318243, patch_data=b'\x00\x05\x04S\x05\x07S\x05\nS\x08\nS\x08\x07S\x08\x04S\x08\xe7\x19\x07\x07\xe3\x07\x08\xe3\x08\x07\xe3\x08\x08\xe3\xff'), + BasePatchData(address=318513, patch_data=b'\x00\x06\x08\x88\xff'), + BasePatchData(address=318667, patch_data=b'\x00\x07\x06T\x07\tT\t\x07T\xff'), + BasePatchData(address=319262, patch_data=b'\x00\t\t\t\xff'), + BasePatchData(address=319561, patch_data=b'\x00\x06\x07\x92\xff'), + BasePatchData(address=319934, patch_data=b'\x00\x07\x06T\x07\tT\t\x07T\x18\x17\xd1\x1c\x03\xc5\xff'), + BasePatchData(address=320599, patch_data=b'\x00\x05\x07\xbd\xff'), + BasePatchData(address=320954, patch_data=b'\x00\x05\x07\xcb\x05\x07\xcc\x05\x07\xcd\xff'), + BasePatchData(address=321108, patch_data=b'\x00\x05\t\xce\xff'), + BasePatchData(address=321671, patch_data=b'\x00\x05\x04S\x05\x07S\x05\nS\x08\nS\x08\x07S\x08\x04S\x08\xe7\x19\xff'), + BasePatchData(address=322049, patch_data=b'\x00\x05\x07\xa3\x05\x07\xa4\x05\x07\xa2\xff'), + BasePatchData(address=439998, patch_data=b'\x00'), + BasePatchData(address=440745, patch_data=b'\x00'), + BasePatchData(address=819304, patch_data=b'\x881\xfc8'), + BasePatchData(address=958593, patch_data=b'\\\x82\xb76'), + BasePatchData(address=961895, patch_data=b'"\x9c\x996'), + BasePatchData(address=972942, patch_data=b'"\xe7\x996\xea\xea\xea\xea\xea\xea\xea\xea'), + BasePatchData(address=973746, patch_data=b'\xa2\x07'), + BasePatchData(address=974570, patch_data=b'"z\xb76'), + BasePatchData(address=986033, patch_data=b'\x95\xc7'), + BasePatchData(address=988440, patch_data=b'"\x87\x996'), + BasePatchData(address=996017, patch_data=b'"\x1a\x976\xea'), + BasePatchData(address=999751, patch_data=b'"\x02\x9a6\xea\xea\xea\xea\xea'), + BasePatchData(address=1024348, patch_data=b'\xff\xff\xff\xff\xf0\xffa\x18\xff\xff'), + BasePatchData(address=1245184, patch_data=b'\t\x00\xe1>\x7f\x7f\x00\x00\x19\xff\xf0p\x04t\x07\x881\x00\x00\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\n#]r\xc6\xa4\xe5\xc3\xa0D\xa0\x0cO \xa4\xee^\x1cM\x11\xf2\xd3J\xa0\xb4B\x02=!\x00 \xe0\xb0\xf2\x1d\xce\xef\x15~\xcd-\xcc}\xb1xj2\xd2\x00\x0e\xb0\x9f\xee\xee\xbd\xd7RF$\xb4\x1d\x0f\x1f\x0b]\x1e.\x04\xb0\xf4\xe5/\x0e\xd8\xe9\xad\xee\xa4\x01-\xf5?G\xd3O\xd5\xb0O\xfe\n\xda\xdf\x01\xc2\x10\xa8l\x02\xf2\x96\xfe\x87\xd5\x10\xa49C\x17\x10\x15\xd1&\xee\xac\xf1\xc3\xe4\x92D\xef!h\xa4\x10\xa2\xcf)\x10\xec\x11\xf0\xac\xd1\xf7\x05\x8cr\xb3\xbeA\xa4\xe8\xd4\x00\x91!c\x0fs\xa4\x04\xcb-\xf9\xa1;|A\xb4/\xd5\x10\x0f\xf2P\xed\r\xb40\xbd-\x13\x022\xfe%\xb8\xb0\xed}\x1f\xe3\xf4\xc0O\xb4\x10\x00\x11\x10\xb2\x0e\xef\xa3\xa4a\xdcMg:t\xe6\xe3\xb8\xdf\xb4A\xc4\xecq\r\xe3\xbc\xd3\x0b \xd0\xf2 -\xf6\xbc\xf0\xef\xf3\x01\xfa?<=\xb4\x10\xf1\x06\x01\x0cU\x12\r\xc0-,\xbd\xbb\xdd\xf02A\xcc{\x1f\xff>\xf2\xe1"\xe0\xb42\xb6\xeeL+\xb6\xde\xfc\xbcP1\x91<5\x94\xfdN\xb4\xd2\xbd\xceB\x13\xef`#\xb4\xf2\xd1\xd9<\xa3\xf0qO\xb4\x07\xde<\xd4\xcd\xfc,C\xb0\xe6b%\x04!\xedR\xe1\xb4\xdb\x14\x02\xf5\xb0L#\xe1\xb4?\xee\xec\xa6\x1dq\xe2^\xb4\xf1K\xf4\x9d*\xff42\xb4=`\xa2\x0c\x12\xf9\xf3\xbe\xb0\xef\x17R5@\x01\xd1^\xb0\xcf\xba\xbe\xefS\xe5A4\xb4\xe01\x8d\x1a\x15\xd23\xf2\xbc+\x15\x90@\x84\x1c%\x1c\xbc3\xa2I\x04\xfe\x12\xbem\xbc\xd6K?\xc1@\xa2=\x15\xb0;\xed\xab\xde\xf3>\x05"\xb4.\x03)\xdf\xc2?\x13/\xa4\x16\x00<\xe1\xf9\xc9\x93g\xa8\xa5\xdcR\x80[O\x187\xb0\x89\xd0&P63/\xd4\xb4\xfb\xe2\xa3/%\x0b#\xd3\xb4<\xd6\xfc\xfe\xa1_\x03\x1c\xb4D\xc2]\xc4\xfb\xf1\xaeP\xb4\x06\x1b\'\xc0\\\xc6\x08\x0c\xb0\x88\xfe\xc7m\x05`q\xc3\xb0n\xbe\x98\x01\xc2N\xf3\xf0\xcc.\xd5\x0b1\xa2{\x03\xfb\xb4$\xd1Q\xc4\xd9 \xbel\xb45\xfd3\xe2\xe0\x04\xf9\x0f\xb4\xd0}\xf7\xfb2\xd4+\xe5\xb0=\xff\x99/\xd4=\x03\xee\xb0b\x17N\xe0\x98/\xc4\x1b\xb4S\xc2L\x07\xeb\xef\xceq\xb4\xc5\nS\xe2-\xe7\t\xd0\xb0\x882\xd3,%\x03O\xf5\xb4,\xa0\xdfU\x0c\x0f\xe5=\xb8>\x15)\xb2MW\x8c=\xbc\xf7\x1b\x00\xf5J\xeb\x13\x15\xb8\t\x12\xc3[\xd3\xf3L\xad\xb0\x9a\x93_\xf1\xc1R\xf0\xe0\xb0w\xeb\xc9\xb7P\x0e\xb2R\xbc\xf3\xe5X\xa1@&\xbb\xf4\xbc\xf3+\xd3!M\xabE\x11\xa4P\x8bBA\xac6s\x1c\xb8\x95a\xe1\xedO\xe1\x0e3\xb4B\x1e\x9a\x14\x03/\x01\xfe\xb0\xec\xc1Fu\xa9\xdd\xf4\x0e\xb0!\x11\xfc\xe1Gq\x9a\xed\xb05\xdd #\x0b\xf2\x15c\xac\x93w\xe8B\xb6\xea1\xf3\xbc-1\xbc\xe54\xd1\t\x14\xb4\xf0\xd4A\x0cP\xb8A\x16\xbc\xcf\xde0!\xd5\xff\xff\x86\xb4\x117\xef\xfc\xf0\x12\xe5\x12\xb0a\x99\xc0$\x111\x0e\xdb\xb0\xd23R\xec\xab$!.\xa4?\xcasD\x0f\x1e\x88\xd7\xacy\x8e\x13J\x04\x04(\xd7\xbc\xe2\xccc<\r\xd0P\x02\xbc\x0e\x1f\x0f\xa3B/\xcf\xb4\xb4\x1111\x00\r\x82\x07\x12\xb4\n\xef\x01\x12"\x10\x0b\x93\xb8\xf2\xb7\x8d3\xd3:\x17\x90\xb0"\x0e\xa9\x172\x0f\xcd\xf1\xb0#B\x10\xf2C\xc8\xd4S\xa8\xf0\x04^\xee1\r\x1c\xa0\xbcd\x0b\x1e\xd1O\x13\xef\xf0\xb0>\x8a\xb3U=\xba\xde\x13\xbc\xf0\xf0\x1a\x06"\xa4\xd9S\xbc\xe2L\xf0*#\xd2\xf4\x00\xa0[\xca\xa0De\x0e\xd0v\xb0\xfa\xb2c\xec\xd11\xf0\x12\xb0\x11#N\xa8\xc5T\xfe\xde\xb0\xff\xf143/\x99\xb2T\xbc\xfe\x02\x11/\xff\x00\x195\xb8\x01\xb5\x9do@\xff\x0e\x10\xbc\x1b\xf6z\xc2\r\x13\xf2,\xc4\xff\x1f!\xdf\xb7?\xcf0\xb0\xe0\xf7D\xdf\xf53\x08\x90\xbc\t\xc7:\r}0\xc5\xc3\xb0+\x8a\xf72\x1f\xcd\xc03\xb0!\x13\x18\x9e5\x02\x0e\xdc\xb8!9\x10\x02\x19EX\xb2\xb4\x1c\xe4\xc4\x1d\xcdC!\xba\xb4G-\xb0P\xf33,!\xb4\xd1\x0e\x8a\x06\x0b\xd3@\xce\xbc1\x02\xff\x02\xeb\xf6n\xa0\xb4\xe0\xdf\xf33/\xf2\r\x92\xb4"\xce\xfa\xee\'\x1d\x02\xf0\xbc\x1d\x07;\xc1l\xb3a\xdc\xbc\xd2=>\xc1b\xc9F\x18\xc4\xc1P\x0e\xf3\x10\x1e\xefb\xb4\xb8E\r\xd4A\xc1Q\xfc\xb8\x0bf\x1a\xc4\x1e\x1dS\xbf\xbc\x00/\xfb6K\xa5\x00\xc2\xbc\x17\x8e@\xdc\x7f\xe3\xf4\xdd\xbc\xb3_\xd39@\x02\xa6\x86\xb4d\x0e\xa11\xb4\x7f\xa1@\xb0<\xb0\xb8\xc0\xda\xb1\x1f\xfe\xb4M\xb3/9\xffw\xfcU\xb4\t\xc0O\x10\r\xb0\nV\xb4+\xb4-\x0f%\xe5,\x0e\xb0\x0e\xcdb\x00\xe0\xf3Fw\xb4\xff\xeb\xe4\xec\xb7M\xd1\x11\xb0\x9c\xfd~2\xeet_v\xb0Oe\xfc\x9d\xfd\xf1.\xd5\xb0J\xce\xdd\xcd<\xb8\x02\xf2\xb0AMB\xe13CUQ\xb010\xde\xdf\xee\xc9\x80>\xb4\x12\x1b\x95/\x11#\x04\x10\xb4\xf8T\x91^\xdc\xd4\xe2\xe2\xb4m\xd3\xdb\x1e^\xe6\x1d>\xbcN\xd3.\x1f\x0f\xf6\xdd}\xb0SF=\x1d\x9b\x9b\xfb\xf1\xb4\xde\x13\xdaf\x903\xff\x05\xb8\x96\xdeL\xf2\xf3\xa6L\xc5\xb42\xda/\x911\xd0o\xb4\xb41.\x10\xf4\xed\x1d1\xd3\xb4N\x0e\x1d\xf5\xea0\xee?\xb4\x0fj\xd2\xf1\xfdQ\xe2r\xb4\x0fm\x91>\xdd\x0f\xf3\x0e\xa03\x13/\xac,\x9b\xae\xc8\xbc2\xe0\x00\xe1Z\xe5\x00\x01\xa4\xe2\x14\x8c\x1e\xfe\x90/\xf1\xa0\x8c\xd7\t\xae\xbe\xdc\xbfe\xb4\x04\x01\xfe\xe1\x00\xdf\xf0@\xa4\xd3_\xff\xab\x05\xaaO\xf1\xbc\x1e_\xfd\x12\xf1\xfbA\x1f\xa4O\x0fx\xd4<\xed\xcfp\xa4\xacA=\xbc?\x11\xf2W\xa4 t\xed\xfc\xd2\x08\xa5\x01\xb0(\xf1D\x13\r\xfe\x0c\x1b\xb8D\xd2<\xb5\x04\xa4\xc0"\xa4\x13\xe5\x0c\x19a\x8c\xf0\xe3\xb0+\xbd\x0f\xff\xbb\xcd\xde\xdf\xac\x1ek[\xa3\xf4\xd6\x8bs\xa4\x03=?-\x02\xa0\xee\xe1\xa4\x1d\xd3VN\xddb\x1f\xd0\xa4\x14\x1f2\xbe\x0f?\xcb\xb4\x945\x88\xe7M\xbf\xa0^7\xa4$\xf4cK\xbe\x11\xfd\xae\xac\x13\xf0\x01\x0cMM\xe1\xb5\xa8\x00\xee&\xfd\xfd]n\xb3\xa4\xf2! \x1e\xbe!\xfe\x8d\x94\xf7#\x8e1\xfe\xf4\x90\xd1\xac0\xe2\xe5\x0f\xdd@\xf2\xb0\x94\xcd \xf0\x1f\xfbd\x1b\xb8\xa4\xf2\x0e\xfe4O\xff\x10P\xa4"\xf2\x01@\x1d\xc0\x1e\xf9\x94\x8f\xf5\xed\xe0\x1e\xd2N\x0e\x98\'\xc0\xe3\x17\x80\x85]=\xa4\xcf\xef\x01\x02\xdf\xe2A\xfd\x9c\xa7=.\xf5\xe4\xc2\x10\xf5\x98\xd2X>\x15\xd2\xbeA\xd0\x9c\x93P>\r-=P\xde\x98\xf2|\x1e\xf3m\n\xf4\x01\x94\x1c\x1d\xae\xef<\xeb\x170\xa4\xfc\xe0\xe0\x01\xee\xb3\x7f|\xb4\xf1#\xf3.\x1a\x7f\r-\xa4\xcc\xe2\xd0\x0f\xf0\x93\x05\x02\xa4\xd1\xfds\xf3-Q \x00\x94\xa1\x14\xed\xa2\xce\x1d\x0f[\xac \xb4\x00\x02\xc3\xe2\x0e>\xac\x00_\xf2\xef\x01\x11\xe0\xfe\xa8\xe2\x13\xc1\x1e<\\\xf1.\x94\xf7\xd0\xd6\x113cB\xe0\x9c\xd7\xee\xf1\xe0\x01\r-B\x98+\xde\x10"\xe0,\xf1/\x943#W5\xf2#?\xfe\x88\xa3\xb22\x0f\x13\xbe\x0e%\x8cJ\xff\xc6\x11\xe3\x021\xee\x8c\x1f\x1fm\xc3\x90\x02\xe1\x04\x8c\x1f\xfa\xc2\xd3?/\r\xf4|$%\'^\x83\x05o\xa8x\x0e\xa1\x10A\x01/\x8d\x03\x88B\xdb\xf0\xe1O\xf4\xf0\x1e|\\Z@=\xb0\x8f/\xf5\x84C\x1e\xb8\x9c\xbf\x02\xfe\xfe|5"Ec\xfd\xd6$\x0ex\x8e\x0b\xe2O!\x00\xee\xc3y\x15\x0e\xac\x0f\xe21\x13\xf0\x00\x00\x00\x08'), + BasePatchData(address=1343488, patch_data=b'kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk'), + BasePatchData(address=1769728, patch_data=b'\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'), + BasePatchData(address=1770352, patch_data=b'\x00\xd8\xe3\xd86'), + BasePatchData(address=1774837, patch_data=b'\x84\r\xaf\x00\x816\xd0\x12\xc0\x04\xd0\n"q\xba\r)\x03\x18i\x13\xa8\xb9\xf3\x81k\xdaZ\x8bK\xab\xc0\x04\xd0#"q\xba\r)\x03\xa8\xb9p\x83\x82C\x00\x00\xd9>y\xd9\xdc\xd8\xda\xe4\xe1\xdc\xd8\xdf\xe0\x0bB\xd3A\xd4\xd9\xe3\xd8\xc0\x0f\xf0\r\xc0\x11\xf0\t\xc0\x10\xf0\x05\xb9%\x95\x80\x1c\xaf\n\x04~\xa8\xaf\xc5\xf3~\xc9\x03\x90\x0c\xaf\n\x04~\xc9@\xb0\x04\x18i\x90\xa8\xb9 \x81\xabz\xfakH\x08\xe2 \xaf\xb0P\x7f\xf0$)\x01\xd0\n\xaf\xb0P\x7f)\x02\xd0\x0e\x80\x16"\xa5\x956\xa9\x00\x8f\xb0P\x7f\x80\n"\x17\x966\xa9\x00\x8f\xb0P\x7f(h\x8b\xa9\x00\x00[\\\xd5\x80\x00H\xad\x00CH\xad\x01CH\xad\x02CH\xad\x03CH\xad\x04CH\xad\x05CH\xad\x06CH\xa9\x80\x8d\x15!\xa9\x00\x8d\x16!\xa94\x8d\x17!\xa9\x01\x8d\x00C\xa9\x18\x8d\x01C\xa9\x9a\x8d\x02C\xa9\xc7\x8d\x03C\xa96\x8d\x04C\xa9\x00\x8d\x05C\xa9\x10\x8d\x06C\xa9\x01\x8d\x0bBh\x8d\x06Ch\x8d\x05Ch\x8d\x04Ch\x8d\x03Ch\x8d\x02Ch\x8d\x01Ch\x8d\x00ChkH\xad\x00CH\xad\x01CH\xad\x02CH\xad\x03CH\xad\x04CH\xad\x05CH\xad\x06CH\xa9\x80\x8d\x15!\xa9\x00\x8d\x16!\xa94\x8d\x17!\xa9\x01\x8d\x00C\xa9\x18\x8d\x01C\xa9\x9a\x8d\x02C\xa9\xd7\x8d\x03C\xa96\x8d\x04C\xa9\x00\x8d\x05C\xa9\x08\x8d\x06C\xa9\x01\x8d\x0bBh\x8d\x06Ch\x8d\x05Ch\x8d\x04Ch\x8d\x03Ch\x8d\x02Ch\x8d\x01Ch\x8d\x00ChH\xad\x00CH\xad\x01CH\xad\x02CH\xad\x03CH\xad\x04CH\xad\x05CH\xad\x06CH\xa9\x80\x8d\x15!\xa9\xa0\x8d\x16!\xa9:\x8d\x17!\xa9\x01\x8d\x00C\xa9\x18\x8d\x01C\xa9\x9a\x8d\x02C\xa9\xdf\x8d\x03C\xa96\x8d\x04C\xa9\xc0\x8d\x05C\xa9\x00\x8d\x06C\xa9\x01\x8d\x0bBh\x8d\x06Ch\x8d\x05Ch\x8d\x04Ch\x8d\x03Ch\x8d\x02Ch\x8d\x01Ch\x8d\x00Chk\xa9\x10\x85\xbc\xaf\x03\x816\xf0\x0b"q\xba\r)\x1f\x18i`\x85\xbc\xa9\x00\x8f\xb1P\x7f"=\x89\x00kH\xaf\x06\x816\xd0\x07h)\x03\x9d\xe0\rkhH)\x03\x9d\xe0\rhk\xaf\xf3\x0c~\xf0\x07\xa9\x05\x85\x11\x9c\xf3\x0c"\x00\x80\x07k"\x14\xc1\t\xa5\xa0\xa6\xa1\xc9\x07\xd0\x0f\xe0\x00\xd0\x0b"N\xc4\t"\x14\xc1\t\x82\xdc\x00\xc9\xc8\xd0\x0b"N\xc4\t"\x14\xc1\t\x82s\x01\xc9)\xd0\x16"N\xc4\t"\x14\xc1\t\xa9\x07\x8d\x00\x0b\x9c(\x0b\xee\x08\x0b\x82Y\x01\xc93\xd0\x0b"N\xc4\t"\x14\xc1\t\x82\x9d\x01\xc9Z\xd0\x0b"N\xc4\t"\x14\xc1\t\x82;\x01\xc9\x90\xd0\x0b"N\xc4\t"\x14\xc1\t\x82\x7f\x01\xc9\xac\xd0\x1d"N\xc4\t"\x14\xc1\t\xaf\x01\x816\xf0\x0c\xeeh\x04\x9c\x8e\x06\x9c\x90\x06\xee\xf3\x0c\x82\x0b\x01\xc9\x06\xd0\x0f\xe0\x00\xd0\x0b"N\xc4\t"\x14\xc1\t\x82K\x01\xc9\xde\xd0\x0b"N\xc4\t"\x14\xc1\t\x82\x96\x00\xc9\xa4\xd0\x0b"N\xc4\t"\x14\xc1\t\x82-\x01\xc9\x1c\xd0\x0f\xe0\x00\xd0\x0b"N\xc4\t"\x14\xc1\t\x82\xc7\x00\xc9l\xd0\x0b"N\xc4\t"\x14\xc1\t\x82\x0b\x01\xc9M\xd0\x0b"N\xc4\t"\x14\xc1\t\x82\x03\x00\x82L\x01\xa2\x00\xbd \x0e\xc9\xe3\xd0\x02\x80\x1e\xc9\xd1\xd0\x02\x80\x18\xc9\xc5\xd0\x02\x80\x12\xbd\x10\r\x18ih\x9d\x10\r\xbd\x00\r\x18ih\x9d\x00\r\xe8\xe0\x10\xd0\xd4\xa2\x00\xbd\x00\x0b\xc9\xe3\xd0\x02\x80\x12\xbd\x08\x0b\x18ih\x9d\x08\x0b\xbd\x18\x0b\x18ih\x9d\x18\x0b\xe8\xe0\x08\xd0\xe0\x82\xf9\x00\xa2\x00\xbd \x0e\xc9\xe3\xd0\x02\x80\x1e\xc9\xd1\xd0\x02\x80\x18\xc9\xc5\xd0\x02\x80\x12\xbd \r\x18i\x00\x9d \r\xbd0\r\x18i\x01\x9d0\r\xe8\xe0\x10\xd0\xd4\xa2\x00\xbd\x00\x0b\xc9\xe3\xd0\x02\x80\x12\xbd\x10\x0b\x18i\x01\x9d\x10\x0b\xbd \x0b\x18i\x00\x9d \x0b\xe8\xe0\x08\xd0\xe0\x82\xa6\x00\xa2\x00\xbd \x0e\xc9\xe3\xd0\x02\x80\x1e\xc9\xd1\xd0\x02\x80\x18\xc9\xc5\xd0\x02\x80\x12\xbd \r\x18i\x01\x9d \r\xbd0\r\x18i\x01\x9d0\r\xe8\xe0\x10\xd0\xd4\xa2\x00\xbd\x00\x0b\xc9\xe3\xd0\x02\x80\x12\xbd\x10\x0b\x18i\x01\x9d\x10\x0b\xbd \x0b\x18i\x01\x9d \x0b\xe8\xe0\x08\xd0\xe0\x82S\x00\xa2\x00\xbd \x0e\xc9\xe3\xd0\x02\x80\x1e\xc9\xd1\xd0\x02\x80\x18\xc9\xc5\xd0\x02\x80\x12\xbd \r\x18i\x01\x9d \r\xbd0\r\x18i\x00\x9d0\r\xe8\xe0\x10\xd0\xd4\xa2\x00\xbd\x00\x0b\xc9\xe3\xd0\x02\x80\x12\xbd\x10\x0b\x18i\x00\x9d\x10\x0b\xbd \x0b\x18i\x01\x9d \x0b\xe8\xe0\x08\xd0\xe0\x82\x00\x00k\xad\xba\x0c\xd0\x0b\xa9\x01\x8d\xba\x0c\xa9\x01\x8f\xb0P\x7f"\x7f\xd9\rk\xad\xba\x0c\xd0\x0b\xa9\x01\x8d\xba\x0c\xa9\x02\x8f\xb0P\x7f\xa9\x03\x9d\xc0\rk\xc5\x00\x90\x058\xe5\x00\x80\x02\xa9\x00kH\xa9x\x9d\x10\r\x9d\x00\r\xa5#\x9d0\r\xa5!\x9d \r\xa5\xa0\xc9\x07\xd0\n\xa5"\x9d\x10\r\xa5 \x9d\x00\rh"\xbd\x84\x06k\xda\xaf\x02\x816\xaa\xa5\x90\x18i\x04\x00\x85\x90\xa5\x92\x18i\x01\x00\x85\x92\xca\x10\xed\xfak\xbd\xe0\r\x1a)\x03\x9d\xe0\r\xbd\xa0\r\x1a\x9d\xa0\r\xc9 \x90\x03\x9e\xd0\rk\xaf\x05\x816\xd0\x05"\xfa\xc6\x1ek\xbd \x0e\xc9\xb8\xf0\x05"\x1a\xc7\x1ek\xa9\x83\x9d \x0e"\x18\xb8\r\xa9\xb8\x9d \x0e\xbd\xaa\x0c)\xfb\t\x80\x9d\xaa\x0c"\r\xc7\x1ek\xaf\x05\x816\xf0\x0c\xbd \x0e\xc9\xb8\xd0\x05\xa9\x83\x9d \x0e\xbd \x0e\xc9zk\xbd \x0e\xc9\xb8\xf0\r\xaf\x05\x816\xf0\x0c\xbd \x0e\xc9\xb8\xd0\x08\xa9\x83L\x82\x9a\xbd \x0e\xc2 \n\nk"\x9e\x9a6\xa9\x00\x8f\x11\xc0~kx"\x88\x88\x00kx"\x88\x88\x00k\xaf\xb1P\x7f\xf0\x01k\xa9\x01\x8f\xb1P\x7fx\x9c\x00B\x9c\x0cB\x9c6\x01\xa9\xff\x8d@!\xa9\x00\x85\x00\xa9\x80\x85\x01\xa9&\x85\x02"\x88\x88\x00\xa9\x81\x8d\x00BXk'), + BasePatchData(address=1783505, patch_data=b'\x8bK\xab\x08\xc20\xa0\x00\x00\xafY\xf3~)\xff\x00\n\xaa\xbd\t\xb7\xaa\xbf\xd1\x9a6\xda\xbb\x9f\x00\x90~\xfa\xbf\xd1\x9c6\xda\xbb\x9f\x80\x91~\xfa\xe8\xe8\xc8\xc8\xc0\x80\x01\x90\xe1(\xabk\x00\x00\x00\x00\x00\x04\x00\x08\x00\x0c\x8bK\xab\x08\xc20\xa0\x00\x03\xafZ\xf3~)\xff\x00\n\xaa\xbdK\xb7\xaa\xbf\xd1\xaa6\xda\xbb\x9f\x00\x90~\xfa\xbf\xd1\xac6\xda\xbb\x9f\xc0\x90~\xfa\xe8\xe8\xc8\xc8\xc0\xc0\x03\x90\xe1(\xabk\x00\x00\x00\x00\x00\x04\x00\x08\x85\x01\xa0\x00\x00\xa9\t\x85\x02k\xa9y"]\xf6\x1d0\x14\xa5"\x99\x10\r\xa5#\x990\r\xa5 \x99\x00\r\xa5!\x99 \rk\x9e\xba\x0c"\x18\xb8\rk\xa5\xa0\xc9\xac\xd0\x0e\xaf\x81\xde\t\xf0\x08\xaf\xcc\xf3~\\\x85\xa0\x1d\\\x90\xa0\x1d'), + BasePatchData(address=1787802, patch_data=b'\xcf=\xef^\xb7o\x9fp\x88x\xc7?g\x9fp\xaf\x00\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\xfc\x03\xc7xc\xbd1\xde\xd8\xef\xf6\xe7\xf6g\xf6\xa7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\x00\x08\x00\x08\x00\xc0\x00\xe0\x00\x7f\x00\xbf\x80\xb8\x00\xb7\x07\xa8\x0f\x9f\x10\xff\x00\xff\x00\xff\x00\x7f\x00\x7f\x00x\x00p\x00`\x00\x00\x00\x00\x00\x1c\x00\xbe\x00\xe3\x00]\x1c\xa2\xbe\xff@\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xe3\x00A\x00\x00\x00\xdf\x10\xe0\x00~\x00=\x01;\x025\x065\x065\x06\xe0\x00\xff\x00\xff\x00\xfe\x00\xfc\x00\xf8\x00\xf8\x00\xf8\x00\xfc\x03\xce\xf1\xd7\xf8\xeb\x0c\xfc\xcf\x1e\xe7\xff\xfb?\xc0\x00\x00\x00\x00\x00\x00\x10\x00\xc0\x00\x00\x00\xf8\x00\x00\x00\xf7\x07\xef\x0f\xee\x0el\x0c\xb7\x87\xbb\x83\xdf\xc0\xbf\xe0\xf8\x00\xf0\x00\xf1\x00\xf3\x00x\x00|\x00?\x00\x1f\x00\x0f\x00\x0f\x00g`d`\t\x01\x9b\x83\xf5\x07\xe9\x0f\xff\x00\xff\x00\x9f\x00\x9f\x00\xfe\x00|\x00\xf8\x00\xf0\x00\xef\x0f\xf2\x03\xfd\x01\x8f\x00wp\xfb\xf8\xef\xec\xcf\xcc\xf0\x00\xfc\x00\xfe\x00\xff\x00\x8f\x00\x07\x00\x13\x003\x00\x07\x043030\x06\x00\xcd\x01\xf2\x03\xe4\x07\xef\x0f\xfb\x00\xcf\x00\xcf\x00\xff\x00\xfe\x00\xfc\x00\xf8\x00\xf0\x00\xff\x00\xff\x7f\xffo\xefV\xfeY\xefM\xe5G\xf7W\x00\x00\x7f\x00o\x00F\x00@\x00P\x00X\x00H\x00\xff\x00\xe0\xdf\xdc\xc1[\xdc\xa5\xee\x02\x07Ss\x8b\xfb\x00\x00\x00\x00\x00\x00 \x00\x10\x00\xf8\x00\x8c\x00\x04\x00\xd4\xf7\xea\xef\x15\x1e;\xfc\xf6\xf9|\x83\xc6;\x86\xfb\x08\x00\x10\x00\xe0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe7W\xe5W\xed_\xed_\xfdO\xf5O\xf8G\xe4[H\x00H\x00@\x00@\x00`\x00`\x00`\x00@\x00\xd2\xd6\xf3\xf7\xe5\xf7\xea\xfb\x80\xfcE\xc7\xba\x83\xed\xa9)\x00\x08\x00\x08\x00\x04\x00\x03\x008\x00D\x00\x12\x00\xabn\xaam\xabm\xabm\xabm\xbbm\x9bU\xabm\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00(\x00\x10\x002\xeel\xdc\xda\xfb\x90\xb7\x117\x146\x132)9\x01\x00\x03\x00\x04\x00H\x00\xc8\x00\xc9\x00\xcd\x00\xc6\x00\x00\x0f\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x0c\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00Of\xbf\xe6\x19\xcag\xf4S4\xed*\xd7Z\xab\xb6\x98\x00\x18\x004\x00\x08\x00\xc8\x00\xd0\x00\xa0\x00@\x00C\xc0\x89H\x9c\\\x9eR\x8eR\x9cJ\xbcj\xae\\?\x007\x00#\x00!\x00!\x00!\x00\x01\x00\x01\x00\x7f\x80\x80\xff\x03\x0269\x7fg?!\x99\x1e\xc0\x00\x00\x00\x00\x00\xfc\x00\xc0\x00\x80\x00\xc0\x00\xe0\x00\xff\x00\xf0o\xfao\xfao\xf0n\x90o\xb0/\xf8o\xf8o\x00\x00\n\x00\n\x00\x01\x00\r\x00B\x00\x0b\x00\t\x008\xc7\x00\x7f\x03\xff\x84\xfc\x06\xfe!\xff\xa0\xffF{\x00\x00\x83\x00\x07\x00\xcf\x00\x8f\x00G\x00\x83\x00\xe6\x00\xff\x00\xf3\xfb\xf7\xff\xff\x00c\xff\x00\xff`\xff\x00\xef\x00\x00\x04\x00\x00\x00\x00\x00k\x00\x08\x00f\x00\x1b\x00\x12\xbe\x01\xff\x86\x7f\x80\x7f\x80\x7f\x19\xfe)\xef$\xe7[\x00\x15\x00\x01\x00\x00\x00\x18\x00=\x00\x7f\x00~\x00\xc8\xe0[\xf3\xd4w\xe4w\x94W\xa8kr\xb3\xde8\x1f\x00\x0c\x00\x08\x00\x08\x00(\x00\x14\x00\x0c\x00\x07\x00\x03\x0f\xe2\xefg\xeeg\xee\xed\xeeM\xce\x96\x9d+<\xf0\x00\x10\x00\x10\x00\x10\x00\x10\x000\x00`\x00\xc0\x00\xbd\xc2\xf3\xfc\x16\xf7k{\xc5\x0598C~@~\x00\x00\x00\x00\x08\x00\x84\x00\xfa\x00\xc7\x00\x81\x00\x81\x00J~||yy\x02\x03\r\xff>\xff\xf3\xfc\xbd\xc2\x81\x00\x83\x00\x86\x00\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\xefX\xecZ\xecZ\xeb]\xef]\xf7N\xfbW\xfdZ@\x00A\x00A\x00@\x00@\x00`\x00@\x00@\x00\xfe<_=\xbe\x82\xcfLP\xb0\xbf\xc0\xfbgY\xb7\x01\x00\x80\x00A\x000\x00\x0f\x00\x00\x00\x00\x00\x00\x00.\xd9:\xd5\xb0\xcf\xe7\x1fL\xbcZ\xb8d\xb0\xf19\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x07\x00\x0f\x00\x06\x00\xa7\xe5\xbd\xfa[~c>\xb5\x86KL\xfe\x81\x8ds\x18\x00\x00\x00\x80\x00\xc0\x00x\x00\xb0\x00\x00\x00\x00\x00(%QD\xa0\x85H\r\x90\x1d`}\x80\xfd\x00\xfd\x1b\x003\x00b\x00\xc2\x00\x82\x00\x02\x00\x06\x00\x06\x007\xce\xab\xda\xfb\x9a\xd76\xabnS\xde\xa7\xbe\x076\x00\x00\x04\x00\x04\x00\x08\x00\x10\x00 \x00@\x00\xc8\x00\xdf0\xc88\xe7_\xf0o\xffp\xff\x7f\xbf?\xff\x00\x00\x00\x07\x00\x00\x00 \x000\x00?\x00@\x00\x00\x00\xff\x00\xfd\xc3Z~\x81\x00\x00~\x00~\x00~\x00~\x00~\x00~\x00\x00\x00\x10\x908\xa8\x1c\x94\x0e\xca\x07\xe5\x03\xf2\x01\xff\x00\x00\x00\x00\x10\x00\x08\x00\x04\x00\x02\x00\x81\x00\xc0\x00\xff\x00\\D.#\x17\x11\x0b\x08\x05\x06\x82\x8f\xc0\x7f\x00\x00\x18\x00\x0c\x00\x06\x00\x03\x00\x01\x00\x01\x00\x83\x00\xff\x00\x00\xff\xc0\x00\x17\xf9\x0b\x08\x05\x04\x02\x02\x01\x01\x00\x00\xf0\x00\xff\x00\x06\x00\x03\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\xff\x03\x00\x00\xff\x80\x80\xc0@\xe0 p\x10\xb8\x88\x0f\x00\xff\x00\x00\x00\x00\x00\x80\x00\xc0\x00`\x000\x00\x00\x00\x0e\xee\x03s\x00\xbc\x80\xdf@\xef\xa0\xf7\xd0\xfb\xfe\x001\x00\x1c\x00\x0f\x00\x07\x00\x03\x00\x01\x00\x00\x00\xe8\xfd\xf4\xfe\xfa\xff\xfd\x7f~??\x1f\x1f\x0f\x0f\x07\x00\x00\x00\x00\x00\x00\x80\x00@\x00 \x00\x10\x00\x08\x00\xef]\xf7M\xfae\xffp\xff|\xff\x7f\x80\x7f\xff\x00@\x00`\x00p\x00x\x00\x7f\x00\x7f\x00\x7f\x00\x00\x00\xea\xd7;\'\xd9\xd7z\xf7\xfb\x07\xf8\xf6\x04\xfb\xff\x00\x00\x00\xc0\x00 \x00\x00\x00\x00\x00\xf1\x00\xf8\x00\x00\x00\xff\x00\x90p\xafo\xdb\\\xb4;\xa97\xf3\x0f\xe7\x1c\x00\x00\x0f\x00\x1f\x008\x00p\x00`\x00\x00\x00\x00\x00\xff\x00\x00\x00\xff\xff\xff\x00\x18\xe7/\xdf\xd8\xb8\xf32\x00\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00\x07\x00\x0c\x00\x00\xfd\x00\xfd\x00\xfd\x00}\x00\x1d\x00\r\x00\x01\x00\x00\x06\x00\x06\x00\x8f\x00\xff\x00\x7f\x00\x1f\x00\x0f\x00\x07\x00\xff\x00\x05\x06\xfb\xf8\xbfx]\xb2\xab\xd6[f\xf7\x0e\x00\x00\xf8\x00\xfc\x008\x00\x10\x00\x00\x00\x80\x00\x00\x00P\xdf\x04\xdf\xaf\xff\xc0\xff\x85\xe5\x00\xc0\x02\x82\x00\x00 \x00 \x00\x00\x00\x00\x00\x02\x00\x02\x00\x00\x00\x00\x00(\xef\x12\xff\x93\xff\x07\xffS\xdf!\'\x00\x03\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\xf8g\xf0o\xf3h\xf3d\x93d\xb3$\xf3d\xf9n\x00\x00\x00\x00\x04\x00\x08\x00\x08\x00H\x00\x08\x00\x00\x00x\x87\x87\xffL\xff\xbb|\x87H\x97X\xdf\x10\xe78\x00\x00\x00\x00\x00\x00\x00\x000\x00 \x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x03\x00\x07\x00\x00\x00\x00\x00\x1f\x1fbcD\xc68\xfd\x00\xf1\x00\x8d\x00\x00\x1f\x00`\x00\x80\x00\x01\x00\x83\x00\xcf\x00\xf3\x00\x00\x00{;\xfd\xdd\xfe.\xdf\xf7\xef\xfbw};>\x7f\x00\xc4\x00"\x00\xd1\x00\x08\x00\x04\x00\x02\x00\x01\x00\x00\x00\xff\xfd\x01\x00\x00\x00\x00\x00\x80\x80\xc0\xc0\xe0\xe0\xff\x00\x02\x00\x01\x00\x00\x00\x80\x00@\x00 \x00\x10\x00\x00\x00\xf4\xfe\xfa\xff\xfd\x7f~??\x1f\x1f\x0f\x0f\x07\xff\x00\x00\x00\x00\x00\x80\x00@\x00 \x00\x10\x00\x08\x00\x00\x03\x00\x03\x00\x03\x00\x03\x00\x06\x01\x01\x06\x06\x08\x08\x06\x00\x06\x00\x07\x00\x07\x00\x0f\x00\x0e\x00\x08\x00\x10\x00\x06\xf6\x08\xe8\x10\x90`a\x80\x82\x00\x05\x00\n\x00\x14\x08\x00p\x00\xe8\x00\x88\x00\x05\x00\x02\x00\x04\x00\x08\x00\x00-\x00X\x01\xb9\x00`\x00\xd0\x00\xa0\x00@\x00\xc0\x16\x00/\x00\\\x00\xac\x00H\x00\x98\x00(\x000\x00 \xe0\x10\xf0\x08\xf8\x04|\x0f\x1f\x00`\x00o`h\x00\x00\x00\x00\x80\x00\x80\x00`\x00\x1f\x00\x10\x00\x10\x00\x1d\x1a\x1a\x1d\x1d\x1e8?\xe0\xff\x00\x00\x00\xff\xb8\x88\x05\x00\x02\x00\x01\x00\x00\x00\x00\x00\xff\x00\x00\x000\x00\xf0p\xf88|\x9c\xbeN_\xa7\x00\x00\x00\xff\x00\x00\x88\x00\xc4\x00b\x00\xb1\x00X\x00\xff\x00\x00\x00\x00\x00\x00\x80\x00\x80\x00\x80\xc0@\xc0\xe0`r0\xb7\x17\xd7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00@\x00 \x00\x01\x01\x00\x01\x00\x01\x03\x02\x03\x07\x06N\x0c\xed\xe8\xeb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x04\x00\xf0i\xf3o\x99g\xacc\xdf8\xee\x1dw\x8e3\xce\x06\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x18\xef\x18\xff\x80\xff\xc0\xff\x00p\xef0\xef\x0f\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x02\x00\x02\x00\x03\x00\x00\x06\x01\x01\x0e\x0e\x100 \xe0F\xc7x\xfe!\xf9\x0f\x00\x1e\x000\x00\xc0\x00\x00\x00\x00\x00\x01\x00\x06\x00p}\x90\x99\x04\x15\x0cm\x1c\x9dhm\x80\x89\x00\x15\x82\x00\x06\x00\n\x00\x12\x00b\x00\x82\x00\x06\x00\n\x00\x1d\x1f\x0e\x0f\x07\x07\x03\x03\x01\x01\x00\x00\x80\x80@\xc0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0p\xf88|\x9c\xbe\xce\xdf\xe7\xef\xf3wy;<\x88\x00\xc4\x00b\x001\x00\x18\x00\x0c\x00\x06\x00\x03\x00\x07\x03\x03\x01\x01\x00\x00\x00\x00\x00\x80\x80\xc0\xc0\xe0\xe0\x04\x00\x02\x00\x01\x00\x00\x00\x80\x00@\x00 \x00\x10\x00\x10\x10 ` `\x13s\x0c\x7f\x00~\x00}\x00z`\x00\x80\x00\x80\x00\x80\x00\x80\x00\x81\x00\xc2\x00\xcc\x00\x00)\x00S\x80\xa3\x04\xa69}\x00\xf9\x01\xc9\x11\x11\x10\x00 \x00@\x00A\x00\x82\x00\x06\x00\x04\x00\x08\x00\x00\x80\x00\x00\x00\x00\x01\x01\x81\x81\x02\x03<\xbf\x00\x7fP\x00\x90\x00\xa0\x00\xa0\x00@\x00@\x00@\x00\x80\x00\x80\xe8\x80\xe8\x84\xec\x02\xee\x02\xee\x02\xee\x01\xef\x01\xef\x10\x00\x10\x00\x10\x00\x10\x00\x10\x00\x10\x000\x00p\x00\xdc\xc4\xae\xa2\x17\x11\x0b\x08\x05\x042\x129)\x1c\x14\x18\x00\x0c\x00\x06\x00\x03\x00\x01\x00 \x00\x10\x00\x08\x00\x00\x00\x00\x00\x00\x00\x80\x80\xc0@\xe0 p\x10\xb8\x88\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\xc0\x00`\x000\x00\x01\x01\x00\x00\x00\x00\x00\x00\x01\x01\x06\x06\x18\x18\x08\x08\x02\x00\x04\x00\x04\x00\t\x00\x0e\x00\x18\x00 \x00\x10\x00\x98\x98\x00\x00\x18\x18xx\xb0\xb000ppdl\x06\x00\x1c\x00d\x00\x84\x00\x08\x00\x08\x00\x08\x00\x10\x00\x00\x13\x00\x12\x00\x18\x01\x19\x03\x1a\x03\x19&2Dt\x0f\x00\x0f\x00\x07\x00\x06\x00\x05\x00\x06\x00\x0c\x00\x08\x00XI\xb0\x91\xe0\xa1\xc0A\x80\x83\x04\x04\n\x08\x14\x12\x90\x00 \x00B\x00\x84\x00\x08\x00\x13\x00\x07\x00\r\x00\x00\xc0\x00\xb0\x04\xa0\x08@ ` \xe0\x18\xf8\x07\xff?\x00@\x00D\x00\x88\x00\x80\x00\x00\x00\x80\x00\xc0\x00\x00\x03\x00\r \x05\x10\x02\x04\x06\x04\x07\x18\x1f\xe0\xff\xfc\x00\x02\x00"\x00\x11\x00\x01\x00\x00\x00\x01\x00\x03\x00\x00\xff@\xc0\x1f\x9f?\xbf?\xbf?\xbf?\xbf?\xbf\x00\x00?\x00`\x00@\x00@\x00@\x00@\x00@\x00\x0c\x0c\x07\x07\x07\x07\x03\x03\x01\x01\x01\x01\x00\x00\x00\x00\x10\x00\x08\x00\x08\x00\x04\x00\x02\x00\x02\x00\x01\x00\x01\x00co\xe0\xef\xc0\xdf\xc0\xdf\xc0\xdf\xc0\xdf\x80\xbe\x80\xbe\x10\x00\x10\x00 \x00 \x00 \x00!\x00C\x00O\x00\x000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xde\x00\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t\x00h\x03S\x0c\x0f\x00\x0f\x00\x03\x00\x00\x00\x00\xf8\x00\xf0\x00\xe0\x00`\x00\x10\x00\x0f\x00\x03\x00\x03\x00!!\xc3\xe3\x02\xc2\x06\x86\x18\x98dd\x9c\x9cll\x10\x00\x10\x00!\x00A\x00\xc7\x00\x9a\x00b\x00\x82\x00\x00\x03\x1c<\x03#\x00\x80\x00\x91\x00O\x00\'\x00\'\xfc\x00\xc3\x00\xc0\x00`\x00`\x000\x00\x1f\x00\x1f\x00\x00\xef\x00\x0f\x80\xef\x10\xd3\x10\xd1"\xa0&\xa3LE\xf8\x00\xf8\x00<\x00n\x00c\x00\xc3\x00\xc4\x00\x88\x00\x8e\x8a\x87\x85C\xc2A\xc10\xf0\x0c\xfc\x03\x7f\x00\x9f\x04\x00\x02\x00\x01\x00\x00\x00\x00\x00\x80\x00\xe0\x00x\x00]E)!\x92\x93\xceO\xec\xafp_\xc0\xfe\x00\xf9\x18\x00\x08\x00\x00\x00\x80\x00@\x00!\x00\x07\x00\x1e\x00\x03\xff\x1c\xff7\xf8N\xf1\x9c\xe38\xc7`\x9f\x18\xff\x03\x00\x1f\x00?\x00\x7f\x00\xff\x00\xff\x00\xff\x00\xff\x00\xb8\xff\x1c\xff\x02\xff\x17\xef\x10\xefx\x87\xf0\x0f\xe0\x1f\xf8\x00\xfc\x00\xfe\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x00\xff\x00\xff\xaa\xff\xff\xff\xff\xff\x1f\xff?\xff\x1f\xff\x00\x00\x00\x00\x00\x00\xf0\x00\xfc\x00\xfe\x00\xff\x00\xff\x00\x00\xff\x00\xff\xaf\xff\xff\xff\xf0\xff\xec\xf3\x9d\xe3\x1a\xe7\x00\x00\x00\x00\x07\x00\x0f\x00\x1f\x00\x7f\x00\xff\x00\xff\x00\x00\xff\x00\xff\xaa\xff\xff\xff\xff\xff\x1f\xff\xff\xff?\xff\x00\x00\x00\x00\x80\x00\xf0\x00\xf8\x00\xfc\x00\xfe\x00\xff\x00\r\xfe\x01\xfe\x83|\xc78\xc78\xcf0\xde!\xdf \xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xc1?\x8cs\x8cs\x98g8\xc7q\x8e\xf3\x0c\xf7\x08\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xe7\xff\x0c\xff\x10\xff\x06\xf9\x9f`\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x04\xffr\xfd\xfe\xf9\x1f\xfc#\xde\xa0_\xf2\r\xf7\x08\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x1d\xffx\xff\xde9\xef\x19\xff\x08\xf7\x08w\x88w\x88\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\xff\xff\xff\xfe\xff\xbf\xff\x0e\xff\x8d~\xcb<\xf7\x18\xe0\x00\xf0\x00\xf8\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\xff\xfb\xff\xe9\xff\x98\xff(\xf7\x99g=\xc3~\x81>\x00\x7f\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\xff\xff\xff\xf5\xf5p\xf0\xf8\xf8\x9c\xfc\xd2.\xfe\x01\x00\x00\x80\x00\xea\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\xff\xaa\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xffUU\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x00\x00\x00\x00\x03\x00\x06\x02\x0c\x04\x19\t3\x12&\x04\x00\x00\x00\x00\x00\x00\x01\x00\x03\x00\x06\x00\x0c\x00\x18\x00\x00\x00\x00\x00\xc0\x00`@0 \x98\x90\xccHd \x00\x00\x00\x00\x00\x00\x80\x00\xc0\x00`\x000\x00\x18\x00<\x00$\x00$\x00$\x00$\x00$\x00$\x00<\x00\x00\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x18\x00\x00\x00\xe7$\xc3B\x81\x81\x00\x00\x00\x00\x81\x81\xc3B\xe7$\x18\x00<\x00~\x00\xff\x00\xff\x00~\x00<\x00\x18\x00\x00\x00\x01\x00\x06\x00\t\x01\x0f\x07\x0f\x07\x1f\x0f\x1e\x0f\x00\x00\x01\x00\x07\x00\x0e\x00\x08\x00\x08\x00\x10\x00\x10\x00\xd8\x1f\x0c\x0f\xea\xef\xf9\xff\xfb\xff\xfe\xff\xfd\xff\xfe\xff\xf8\x00\xfc\x00\x1a\x00\t\x00\x0b\x00\x0e\x00\r\x00\x0e\x007\xce\x16\xefP\xef]\xe2\xdf\xe0\xcb\xf6\x8d\xf2\x87\xf8?\x00?\x00\x7f\x00\x7f\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x07\xf3\x0f\xf0\x0f\xfe\x01\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\x9a~\x95|\xaax\xcb9\xd5?\xed\x1f\xb5O\x15\xef\xf9\x00\xf2\x00\xe5\x00\xe4\x00\xf0\x00\xfc\x00\xfc\x00\xfc\x00\n\x07\n\x07\x05\x03\x04\x03\x04\x03\x02\x01\x01\x00\x00\x00\x0f\x00\x0f\x00\x07\x00\x07\x00\x07\x00\x03\x00\x01\x00\x00\x00\xff\x00\xff\x7f\xffo\xefV\xfeY\xefM\xe5G\xf7W\x00\x00\x7f\x00o\x00F\x00@\x00P\x00X\x00H\x00\xff\x00\xe0\xdf\xdc\x1f<\x1f \x1f\x10\x00\x10\x00\x12\x002\x006\x00=\x003\x00?\x00?\xff?\xff\xc4\xff\x86\xff\x03\xff\x03\xfe\x07\xf8\x0f\xf1\x17\x00\x1b\x00=\x00~\x00\xff\x00\xff\x00\xff\x00\xff\x00\x87\xf8\x8f\xf0?\xcc\x7f\xbe\xcf\x7f\xc7\xff\xe0\xff\xf0\xff\xff\x00\xff\x00\xff\x00\xdf\x00\x8f\x00\x87\x00\xc3\x00\xe7\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xfc\x83p\x8f8\xdf\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xfc\x00\xf0\x00\xf8\x00U\xefU\xefU\xef\x95o\x15\xef\x0c\xf7\x04\xfb\x04\xfb\xfc\x00\xfc\x00\xfc\x00\xfc\x00\x1c\x00\x0c\x00\x04\x00\x0c\x00\x80\x7f\xb2\x7f\xa1~\x83|\x86y\x8cs\x8cs\x98g\xf0\x00\xfa\x00\xfd\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xe7W\xe5W\xed_\xed_\xfdO\xf5O\xf8G\xe4[H\x00H\x00@\x00@\x00`\x00`\x00`\x00@\x00\xd2\xd6\xf3\xf7\xe5\xf7\xea\xfb\x80\xfcE\xc7\xba\x83\xed\xa9)\x00\x08\x00\x08\x00\x04\x00\x03\x008\x00D\x00\x12\x00\xae\xe4\xe3j\xe3i\xd1D\xa0\x02\xdf\x00\xaf\x1f\xa3c\x13\x00\x15\x00\x16\x00;\x00}\x00`\x00@\x00\x1c\x00\xba\xc6E|\xbb9\xd6\x92\xedD\xffDUEEE\x01\x00\x83\x00\xc6\x00m\x00\xbb\x00\xba\x00\xba\x00\xba\x00\xd3\xc3\xd9\xc1\xb5\x81c\x00\xc3\x00\x83\x01\x87\x03\xff\x04<\x00>\x00~\x00\xfe\x00\xfe\x00\xfe\x00\xfc\x00\xf8\x00\xc4f\xa2\xb3p9\xa8-PE\xec%\xf4\xd5\xb8\xad\x99\x00L\x00\xc6\x00\xd2\x00\xba\x00\x1a\x00\n\x00B\x00\xbc\xfd|}xy\xf8y`a;\x01\xbf\xe1\x8e\xe2\x02\x00\x82\x00\x86\x00\x86\x00\x9e\x00\xfe\x00\x1e\x00\x1d\x00\x00\x00\x00\x00\x00\x00\x01\x00\x03\x01\x06\x03\x0c\x07\x1e\x0f\x00\x00\x00\x00\x00\x00\x01\x00\x02\x00\x04\x00\x0c\x00\x1e\x00\x00\x00\x07\x00y\x00\xf2r\xaf\xef\x17\xff$\xff\x01\xff\x00\x00\x07\x00~\x00\x8d\x00\x10\x00\x00\x00\x00\x00\x00\x00\x01\x00\xc7\x01\xfb\x07u\x0f\xa9\x97\xc9\xd7j\xf6\x8a\xf6\x01\x00\xc6\x00<\x00\x9c\x00\\\x00<\x00\x1d\x00}\x00 \x1f \x1f1\x0e\x12\r\x13\r\x17\x0b\x17\x0b\x15\x0b?\x00?\x00?\x00\x1e\x00\x1f\x00\x1f\x00\x1f\x00\x1d\x00?\xc1w\x9b\xf7{\xcb\xf7\x0b\xf7\x8d\xf3\x86\xf9\x87\xf8\xff\x00\xe7\x00\x87\x00\x0f\x00\x0f\x00\xbf\x00\xff\x00\xff\x00\xe0\xff\xe0\xff\xc0\xff\xc9\xfe\x82\xfd\x84\xfb\x08\xf7\xd1/\xff\x00\xff\x00\xff\x00\xff\x00\xf7\x00\xfd\x00\xfd\x00\xfd\x00<\xdf~\xbf|\xbf\xf8?x\xbf9\xdf\xd0\xef\xe8\xf7\xfc\x00\xfe\x00\xfd\x00\xfb\x00\xfb\x00\xff\x00\xff\x00\xff\x00\x06\xfb\x06\xfb\x07\xfb\x8b\xf7\x8b\xf7\x13\xef\x11\xef!\xdf\x1e\x00\xfe\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00p\x8f\xe8\x17\x84{b\xfdy\xfe=\xfe\x1c\xff\x10\xff\x7f\x00\xff\x00\xbf\x00\x7f\x00\x7f\x00?\x00_\x00\xff\x00\xefX\xecZ\xecZ\xeb]\xef]\xf7N\xfbW\xfdZ@\x00A\x00A\x00@\x00@\x00`\x00@\x00@\x00\xfe<_=\xbe\x82\xcfLP\xb0\xbf\xc0\xfbgY\xb7\x01\x00\x80\x00A\x000\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x0c%)#+&\x86\x0c\xe6\x0c6\x0c.4^t\xd2\x00\xd4\x00\xd1\x00\xf3\x00\xf3\x00\xf3\x00\xc3\x00\x83\x00\xc6\x83\xbb\x01E8\xba|\xfeD\xfeD\xfeD}\xa3|\x00\xc6\x00\x83\x00\x01\x00\x01\x00\x01\x00\x19\x00\x18\x00n\xfagjwvwrwr;9\x1d\x1c\x0e\x0e\x05\x00\x95\x00\x89\x00\x8d\x00\x8d\x00\xc6\x00\xe3\x00\xf1\x00E|T\xfc\x8a\xee\xc5g\xc2s\xc1i\xc8l\xc8n\x82\x00\x03\x00\x11\x00\x98\x00\x8c\x00\x96\x00\x93\x00\x91\x00\x00\x00\x03\x00\x07\x03\x0c\x07\x0c\x07\x1c\x0f\x1f\x0c<\x03\x00\x00\x03\x00\x04\x00\x08\x00\x08\x00\x18\x00\x1f\x00<\x00~\x1f\xff?\xdf?o\x9fo\x9f3\xcf\x9cc_\xa4~\x00\xff\x00\xff\x00\x7f\x00\x7f\x00\x7f\x00\xff\x00\x7f\x00\x00\xff\x80\xff\xc0\xff\xd4\xff\xc8\xff\x80\xff\x81\xfe\x0f\xf1\x01\x00\x87\x00\xdf\x00\xff\x00\xfb\x00\xfb\x00\xff\x00\xff\x00~\x81\xe7~\x91~\xb1~\x85~\x93~\xc3~~\x81\x00\x00~\x00~\x00~\x00~\x00~\x00~\x00\x00\x00\x0f\x07\x0f\x07\x1e\x0f\x1e\x07<\x03?\x00\x15\x0e\x12\x0f\x0f\x00\x0f\x00\x1f\x00\x1f\x00?\x00?\x00\x17\x00\x1b\x00\x07\xf8\x0f\xf0\x1f\xe08\xc7\xf3\x1f\xf9?\x9a\x7f\x88\x7f\xff\x00\xff\x00\xff\x00\xff\x00\xf3\x00\xf9\x00\xd8\x00\xe8\x00\xf3\x0f\xf7\x0f\xa5_-\xdf.\xdf\xee\xdf\xec\xdfh\x9f\xfd\x00\xfd\x00\xfd\x00\xfd\x00\xfe\x00\xfe\x00\xfd\x00{\x00\xf7\xf8\xf8\xff\xff\xff\xde\xff\xb9\xff\x02\xfb\x0c\xff2\xff\xff\x00\xff\x00\xff\x00\xde\x00\x99\x00\x07\x00\xfd\x00\xff\x00\xe1\x1f`\x9f"\xdf#\xdf\x13\xef\x11\xef\x10\xef\x18\xe7\xff\x00\xff\x00\x7f\x00\xff\x00\xff\x00\xff\x00\xff\x00\xff\x00\xa1\xff\xc1\xff\x10\xff3\xfc\xff\x01\xfa\x1d\xed>H\xbf\xbf\x00\xff\x00\xff\x00\xff\x00\xff\x00\xef\x00\xcf\x00\xcf\x00\xef]\xf7M\xfae\xffp\xff|\xff\x7f\x80\x7f\xff\x00@\x00`\x00p\x00x\x00\x7f\x00\x7f\x00\x7f\x00\x00\x00\xea\xd7;\'\xd9\xd7z\xf7\xfb\x07\xf8\xf6\x04\xfb\xff\x00\x00\x00\xc0\x00 \x00\x00\x00\x00\x00\xf1\x00\xf8\x00\x00\x00\xff\x007\xedK\xd9\x91\xb3 d@I\x01\x13\x02&\x00\x00\x00\x00$\x00L\x00\x9b\x00\xb6\x00\xec\x00\xd9\x00\xff\x00\xfeW\xfeWV\xff\x02\xab\x80\xa9)m|\xc6\x00\x00\x00\x00\x00\x00\x00\x00T\x00V\x00\x92\x009\x00\n\xf6\x08\xf4\x14\xec*\xdaN\xbe\xc2>\xd3?\xca>\xfd\x00\xff\x00\xfb\x00\xf5\x00\xe1\x00\xe1\x00\xf0\x00\xf9\x00\xfc\x03\xaea\xafdA\xca\x89\x9a\x91\xb2!\xe6\xa1\xee\x00\x00\x10\x00\x10\x004\x00d\x00L\x00\x18\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xbcCt\x0b:\x05\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x7f\x00?\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x18\xff\t\xfe\x02\xfd\xe7\x19\x1e\x01\x01\x00\x00\x00\xff\x00\xff\x00\xff\x00\xfe\x00\xff\x00\x1f\x00\x01\x00\x00\x00P\xbf\xe0_\xf1\xee~\xf0\xc2\xfc\x02\xfc\xfc\x00\x00\x00\xff\x00\xbf\x00\x7f\x00~\x00\xfe\x00\xfe\x00\xfc\x00\x00\x00@\x80\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x009\xc6\x7f\x81\xf9\x079\xf7\xe4\xfb$\xfb\x06\xf9\xf9\x00\xff\x00\xff\x00\xff\x00?\x00\xff\x00\xff\x00\xff\x00\xf9\x00'), + BasePatchData(address=46567, patch_data=b'6'), + BasePatchData(address=160226, patch_data=b"u\x83\x83\x83\x91\x83\x9f\x83\xad\x83\xbb\x83\xc9\x83\xd7\x83\xe5\x83\xf3\x83\x01\x84\x0f\x84\x1d\x84+\x849\x84G\x84U\x84c\x84q\x84\x7f\x84\x8d\x84\x9b\x84\xa9\x84\xb7\x84\xc5\x84\xd3\x84\xe1\x84\xef\x84\xfd\x84\x0b\x85\x19\x85'\x855\x85C\x85Q\x85_\x85m\x85{\x85\x89\x85\x97\x85\xa5\x85\xb3\x85\xc1\x85\xcf\x85\xdd\x85\xeb\x85\xf9\x85\x07\x86\x15\x86#\x861\x86?\x86M\x86[\x86i\x86w\x86\x85\x86\x93\x86\xa1\x86\xaf\x86\xbd\x86\xcb\x86\xd9\x86\xe7\x86\xf5\x86\x03\x87\x11\x87\x1f\x87-\x87;\x87I\x87W\x87e\x87s\x87\x81\x87\x8f\x87\x9d\x87\xab\x87\xb9\x87\xc7\x87\xd5\x87\xe3\x87\xf1\x87\xff\x87\r\x88\x1b\x88)\x887\x88E\x88S\x88a\x88o\x88}\x88\x8b\x88\x99\x88\xa7\x88\xb5\x88\xc3\x88\xd1\x88\xdf\x88\xed\x88\xfb\x88\t\x89\x17\x89%\x893\x89A\x89O\x89]\x89k\x89y\x89\x87\x89\x95\x89\xa3\x89\xb1\x89\xbf\x89\xcd\x89\xdb\x89\xe9\x89\xf7\x89\x05\x8a\x13\x8a!\x8a/\x8a=\x8aK\x8aY\x8ag\x8au\x8a\x83\x8a\x91\x8a\x9f\x8a\xad\x8a\xbb\x8a\xc9\x8a\xd7\x8a\xe5\x8a\xf3\x8a\x01\x8b\x0f\x8b\x1d\x8b+\x8b9\x8bG\x8bU\x8bc\x8bq\x8b\x7f\x8b\x8d\x8b\x9b\x8b\xa9\x8b\xb7\x8b\xc5\x8b\xd3\x8b\xe1\x8b\xef\x8b\xfd\x8b\x0b\x8c\x19\x8c'\x8c5\x8cC\x8cQ\x8c_\x8cm\x8c{\x8c\x89\x8c\x97\x8c\xa5\x8c\xb3\x8c\xc1\x8c\xcf\x8c\xdd\x8c\xeb\x8c\xf9\x8c\x07\x8d\x15\x8d#\x8d1\x8d?\x8dM\x8d[\x8di\x8dw\x8d\x85\x8d\x93\x8d\xa1\x8d\xaf\x8d\xbd\x8d\xcb\x8d\xd9\x8d\xe7\x8d\xf5\x8d\x03\x8e\x11\x8e\x1f\x8e-\x8e;\x8eI\x8eW\x8ee\x8es\x8e\x81\x8e\x8f\x8e\x9d\x8e\xab\x8e\xb9\x8e\xc7\x8e\xd5\x8e\xe3\x8e\xf1\x8e\xff\x8e\r\x8f\x1b\x8f)\x8f7\x8fE\x8fS\x8fa\x8fo\x8f}\x8f\x8b\x8f\x99\x8f\xa7\x8f\xb5\x8f\xc3\x8f\xd1\x8f\xdf\x8f\xed\x8f\xfb\x8f\t\x90\x17\x90%\x903\x90A\x90O\x90]\x90k\x90y\x90\x87\x90\x95\x90\xa3\x90\xb1\x90\xbf\x90\xcd\x90\xdb\x90\xe9\x90\xf7\x90\x05\x91\x13\x91!\x91/\x91=\x91K\x91Y\x91g\x91u\x91\x83\x91\x91\x91\x9f\x91\xad\x91\xbb\x91\xc9\x91\xd7\x91\xe5\x91\xf3\x91\x01\x92\x0f\x92\x1d\x92+\x929\x92G\x92U\x92c\x92q\x92\x7f\x92\x8d\x92\x9b\x92\xa9\x92\xb7\x92\xc5\x92\xd3\x92\xe1\x92\xef\x92\xfd\x92\x0b\x93\x19\x93'\x935\x93C\x93Q\x93_\x93m\x93{\x93\x89\x93\x97\x93\xa5\x93\xb3\x93\xc1\x93\xcf\x93\xdd\x93\xeb\x93\xf9\x93\x07\x94\x15\x94#\x941\x94?\x94M\x94[\x94i\x94w\x94\x85\x94\x93\x94\xa1\x94\xaf\x94\xbd\x94\xcb\x94\xd9\x94\xe7\x94"), + BasePatchData(address=1770357, patch_data=b'A!\x13"\x07=\x00\x00\x00\x10\xc0\x00\x00\x04\xc0\x00\x00\x04\x00\x00\x00\x00\x00\x00r\x00PR\xc0\x1d\x04\x06\x00\x14\x00\x00\x00\x00\x11\x00\x18\r\xc0\x07\x06\x19\x00\x00\x00\x00\x0c\x02\x12\x00\x00\x00\x00\x18\r&\x00&\x14\x00\x00\x00\xb5\x00\x08\x08\x00\x08\x08\x14\x00%\x00 \x06\x05\x0c\x00%\x00\x00\x08\x08\x14\x00%\x00 \x06\x05\x0c\x00%\x00 \x06\x05\x0c\x00%\x00\x00\x00\x17\x17\xc0\x07\x06\xc0\x07\x06\x07\x00\x00\x00\x00\x0f\x07\x19\x00\'\x00\x00\x0f\x07\x19\x00\'\x00\x00\x00KJJ\x00\x0f\x00\x0f\x07\x19\x00\'\x00\x00\x00\t:\x01\x0f\x07\x01\x0f\x07\x19\x00\x03\x00\x00\x00j\x1b\xc0(\x0e\xc0(\x0e\x13\x00\x00\x00\x00\x00\x00k\x8c\x8c@@\x1b\x0e\x18\x058\x00\x00\x13\x0b\x1c\x00\x08\x00\x00\x13\x0b\x1c\x00\x08\x00\x00\x00\x00\x1e\x00!\x13\x00!\x13"\x00\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00!\x13"\x00\x00\x00\x00\x01\x01\x01\x00\x00\x00\x00\x01\x01\x01\x00\x00\x00\x08\x00\x00\x02\xc0\x1d\x04\xc0\x1d\x04\x06\x00\x00\x00\x00\x18\r&\x00\x00\x00\x00\x18\r&\x00\x00\x00\x00\x18\r\x1e\x00\x00\x00 \x18\r&\x00\x00\x00\xc0\x18\r&\x00\x00\x00\xc0\x18\r&\x00\x00\x00\x00\x00\x00\xb6\x90\x08\x08\x90\x08\x08\x11\x03\x00\x00\x00\x00\x00f \x06\x05 \x06\x05\x19\x005\x00\x00\x00\'\x07\'\x01\x0f\x00\x07\x06\x07\x00\x00\x00\x00"\x12\x07\x00\x00\x00\x01\x0f\x07\x19\x00\x00\x00\x00\x0f\x07\x19\x00\x16\x00\x00\x0f\x07\x19\x00\x16\x00\x00\x00\x00jjh\x0fh\x0f\x07\x08\x00\x03\x1c\x00\x00\x00\x0b\x00\x1a\x0e\x00\x1a\x0e\t\x00\x04?\x00\x00\x00\x8c\x00\x1b\x0e\x00\x1b\x0e\x18\x00\x00\x00\x00\x00\x00L \x13\x0b \x13\x0b\x1c\x00\x17\x00\x00\x00>\x0e\x00\x13\x0b\x00\x13\x0b)\x00\x17\x00\x00\x00\x00? \x0c\x02 \x0c\x02\x12\x00\x15%\x01\x01\x01\x01\x00\x00\x00\x01\x01\x01\x01\x00\x00\x00\x00\x18\r&\x00\x01\x00\x01\x01\x01\x01\x00\x00\x00\x00\x18\r&\x00\x01\x00\x00\x18\r&\x00\x00\x00\x00\x18\r\x1e\x00\x00\x00\x00\x18\r&\x00\x01\x00\x00\n\x08\x11\x00\x16\x00\x00\n\x08\x11\x00\x16\x00\x00\x00\x00vvv \x00\n\x08\x11\x00\x16\x00\x00\x00\x00vvv \x06\x05\x19\x006\x00\x00\x001\x171\x80\n\x80\n\x08\x11\x002\x1b\x00\x00\x008\xcc\x0e\t\xcc\x0e\t\x1a\x02%\x00\x00\x0f\x07\x19\x00\x00\x00\x00\x0f\x07\x19\x00\x00\x00\xc0\x0f\x07+\x00\x16\x00\xc0\x0f\x07+\x00\x16\x00\x00\x00\x00;\x00\x13\x0b\x00\x07\x06\x07\x00\x00\x00\x00"\x12\x07\x00\x00\x00\x00\x13\x0b\x1c\x00*\x00\xc0\x07\x06\x19\x00\x00\x00\x00\x13\x0b\x1c\x00*\x00\xc0\x07\x06\x19\x00\x00\x00\xc0\x07\x06\x19\x00\x00\x00\x00\x0c\x02\x12\x00\x00\x00\x00\x0c\x02\x12\x00\x00\x00\x00\x00\x00@ \x06\x05 \x06\x05\x19\x007\x04"\x00w\'w\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00B\x00\x04\x05\x00\x04\x05\x0b\x00\x15%\x80\n\x08\x11\x00\x00\x00\x80\n\x08\x11\x00\x00\x00\x00\x00\x00T\x80\n\x08\x80\n\x08\x11\x00\x00\x19\x80\n\x08\x11\x00\x00\x00\x80\n\x08\x11\x00\x00\x00\x80\n\x08\x11\x00\x00\x00\x80\n\x08\x11\x00\x00\x19\x80\n\x08\x11\x00\x00\x00\x80\n\x08\x11\x00\x00\x00\x00\x00\x00( \r\t \r\t\x13\x00\x00\x00\x00\x00) \x0f\x07\x19 \x0f\x07\x19\x00\x00\x00\x00\x00\n\n\x00\x0f\x07\x00\x0f\x07\x08\x00\x00\x00\x00\x00\x00+\x00\x07\x06\x00\x07\x06\x13\x00\x00\x00 \x1a\x0e\x0c\x003\x00 \x1a\x0e\x0c\x003\x00\x00\x00\x96\x96\xcc\x13\x0b\xcc\x13\x0b)\x02\x02\x00\x00\x00\x00\x1e\x00\x13\x0b\x00\x13\x0b)\x00\'\x14\x00\x00\x00\x1f_\xc0\x00\xc0\x00\x02\'\x00\x00\x00\x00\x00\x000\xb0\x01\x00\x01\x00\x00\x02\x00\x13\x00\x00\x00\x00B\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00A2h\x04h\x04\x05\n\x00\x00\x1d\x00\x17\n\x1b\x00\x01\x00\x00\x17\n\x1b\x00\x01\x00`\x17\n\x1b\x00\x01\x00`\x17\n\x1b\x00\x01\x00\x00\x00\x00\xbc\x00\n\x08\x00\n\x08\x11\x00<\x00\x00\r\t\x13\x0034\x00\r\t\x13\x0034\x00\x0f\x07\x19\x00\x17\x00\x00\r\t\x13\x0034\x00\x0f\x07\x19\x00\x17\x00\x00\r\t\x13\x0034\x00\x0f\x07\x19\x00\x17\x00\x00\x0f\x07\x19\x00\x17\x00\x00\x00\x00\t\t\x00\x0f\x00\x0f\x07\x08\x00\x01\x00\x00\x00\t\x00\x1a\x0e\x0c\x00\x1a\x0e\x0c\x00\x00\x00\x00\x00\x00\x1d \x1a\x0e \x1a\x0e\x0c\x002?\x00\x00\xa6\xa6\x00\x13\x0b\x00\x13\x0b)\x00\x17\x00\x00\x00\x00n\x00\x13\x0b\x00\x13\x0b\x1c\x00\x00\x00\x00\x00\xbe\xc0\x00\x00\x04\xc0\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\xc0\x00\x00\x03\x00\x00\x00\x00\x00\x00a\xc0\x00\x00\xc0\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\xc0\x04\x05\n\x00\x03\x00\x00\x00\x00c \n\x08 \n\x08\x11\x00\x00\x00\x00\x0044\x01\x01\x10\x01\x01\x10\r\x00\x00\x00\x00\r\t\x13\x00#\x00\x00\r\t\x13\x00#\x00\x00\r\t\x13\x00\x16\x00\x00\r\t\x13\x00\x16\x00\x00\r\t\x13\x00!(\x00\r\t\x13\x00!(\xc0\r\t\x13\x00\x00\x00\xc0\r\t\x13\x00\x00\x00\x00\x10\x07\x15\x00%\x00\x00\x10\x07\x15\x00%\x00\xc0\x1b\x0e\n\x00\x17\x00\xc0\x1b\x0e\n\x00\x17\x00\x00\x1b\x0e\n\x00\x00\x00\x00\x1b\x0e\n\x00\x00\x00\x00\x00\x00]\x00$\x0e\x00$\x0e#\x00\t\x00\x00\x00\x00\\ \x13\x0b \x13\x0b\x1c\x00\x00\x00\x00\x00~~\x00\x13\x0b\x00\x13\x0b\x1c\x00\'\x00\x00\x00\x00?\x7f\xc0\x00\xc0\x00\x00\x04\x00\x00\x00\xc0\x00\x00\x04\x00\x00\x00\xc0\x00\x00\x04\x00\x00\x00\x08\x00\x00Q\x00\t\x05\xc0\x00\x00\x04\x00\x00\x00\xc0\x00\x00\x04\x00\x00\x00\x00\t\x05\n\x00\r\x00\x00\x00\x00S\xe0#\n\xe0#\n!\x00\x17\x00\x00\x00\x00\xab\xe0#\n\xe0#\n!\x00\x00\x00\x00\x00\xac\xc0\n\x08\x11\xc0\n\x08\x11\x00<\x00\x00\x00\x00\x16\x00\r\t\x00\r\t\x13\x00"\x00\x00\r\t\x13\x00\x00\x00\x00\r\t\x13\x00\x00\x00\x01\x0f\x07\x19\x00\x00\x00\x01\x0f\x07\x19\x00\x00\x00\x00\x00\x00\x1a\x1a\x00\x1b\x01\x0f\x07\x19\x00\x00\x00\x00\x00\x00\x1a\x1a\x00\x1b\x00\x1b\x0e\n\x00\x08\x0b\x00\x00\x00\x0c\x00$\x0e\x00$\x0e#\x00\x03?\x00\x00\x00\xa5\x00$\x0e\x00$\x0e#\x00\x05\x00\x00\x13\x0b\x1c\x00\x02\x00\x00\x13\x0b\x1c\x00\x02\x00\x00\x00\x00N\x00\x01\x01\x00\x01\x01\x04\x00\x00\x00\x08\x00\x00q\x80\xc0\x01\x00\x01\x01\x04\x00\x00\x00\x08\x00\x00q\x80\xc0\x01\xc0\x01\x01\x04\x00\x08\x00\x00\x00\x00p\xc0\x01\x01\xc0\x01\x01\x04\x00\x00\x00\x08\x00\x00\x01\x00\t\x05\x00\t\x05\n\x00\x17\x00\x00\t\x05\n\x00\'\x00\x00\t\x05\n\x00\'\x00\x00\t\x05\n\x00\x01\x00\x00\t\x05\n\x00\x01\x00\x80\n\x08\x11\x00\x00\x18\x80\n\x08\x11\x00\x00\x18\x00\x00\x00&&&\xc0\xc0\x06\x05\x19\x00\x00\x00\x00\x00\xa71\x87\x87\x00\x00(\x0e\x13\x00\x039\x00\x00\x9d\x00(\x0e\x13\x00(\x0e\x13\x00\x039\x00\x00\x9d\x00(\x0e\x13\x00(\x0e\x13\x00\x039\x00\x00\x9d\x00(\x0e\x13\x00(\x0e\x13\x00\x039\x00\x00\x9d\x00(\x0e\x13\x00(\x0e\x13\x00 \x00\x00(\x0e\x13\x00\x04<\x00(\x0e\x13\x00\x04<\x00\x00\x9b \x13\x0b\x1c \x13\x0b\x1c\x00+\x17\x00\x00\x9e^\x00\x13\x0b\x00\x13\x0b\x1c\x00\x00\x00\x00\x00\x00_`\x01\x01`\x01\x01\x04\x00\x00\x00\x00\x00\x00p\xc0\x01\x01\xc0\x01\x01\x04\x00\x00\x00\x00\t\x05\n\x00\r\x00\xc0\x01\x01\x04\x00\x00\x00\x00\t\x05\n\x00\r\x00\x00\t\x05\n\x00\r\x00\x00\t\x05\n\x00\x00\x00\x00\t\x05\n\x00\x00\x00\x00\t\x05\n\x00\x02\x00\x00\t\x05\n\x00\x02\x00\x00\x06\x05\x19\x00>\x01\x00\x06\x05\x19\x00>\x01(\x00\x00ww\x00\x0b\x00\x06\x05\x19\x00>\x01(\x00\x00ww\x00\x0b\x00\x0b\x05\x08\x00\x00\x00\x02\x00\xa9\x00(\x0e\x13\x00\x0b\x05\x08\x00\x00\x00\x02\x00\xa9\x00(\x0e\x13\x00(\x0e\x13\x00:\x0c (\x0e\x13\x00\x16\x00\x00(\x0e\x13\x00:\x0c (\x0e\x13\x00\x16\x00 (\x0e\x13\x00\x16\x00(\x00\x1c\x0c\x0c\x1c\x00\x00(\x0e\x13\x003)\x00\x13\x0b\x1c\x00\x00\x00\x00\x13\x0b\x1c\x00\x00\x00\x00\x00\x00\xae\x80\x12\x0c\x80\x12\x0c\x16\x00%\x00\x00\x11\x0c\x1c\x00\x00\x00\x80\x12\x0c\x16\x00%\x00\x00\x11\x0c\x1c\x00\x00\x00\x00\x11\x0c\x1c\x00\x00\x00\x00\x00\x00\xa0\x01\x11\x0c\x01\x11\x0c\x1c\x00\x00\x00\x01\x11\x0c\x1c\x00\x16\x00\x01\x11\x0c\x1c\x00\x16\x00\x08\x00\x00\xa2\x00%\x0e\x00%\x0e$\x00\x00\x00\x00%\x0e$\x003\x00\x00%\x0e$\x00\x00\x00\x00%\x0e$\x003\x00\x00%\x0e$\x003\x00\x00\x00\x00=h\x11\x0ch\x11\x0c\x1d\x00\x1c\x00\x00\x00\xd1\xd1\x00\x11\x0c\x00\x11\x0c\x1c\x00\x00\x00\x00\x00\x00\xd2\x01\x0b\x05\x01\x0b\x05\x08\x00\x00\x00\x00\x00\x00\xda\x00(\x0e\x00(\x0e\x13\x00\x00\x00\x00\x00}\x00(\x0e\x13\x00(\x0e\x13\x00\x00\x00\x00\x00}\x00(\x0e\x13\x00(\x0e\x13\x06\x00\x00\x00(\x0e\x13\x06\x00;\x00(\x0e\x13\x06\x00;\x00\x00{ \x13\x0b\x1c \x13\x0b\x1c\x00\x00\x00\x00\x00\xbe\xbe\x00\x13\x0b\x00\x13\x0b\x1c\x00\x17\x00\x00\x12\x0c\x1d\x00\x00\x00\x00\x12\x0c\x1d\x00\x00\x00\x00\x00\x00\x91\x00\x11\x0c\x00\x11\x0c\x1d\x00\x00\x00\xc0\x11\x0c\x1d\x00\x00\x00\xc0\x11\x0c\x1d\x00\x00\x00\x00\x00\x00\x93`\x19\r\x00\x11\x0c\x1d\x00\x00\x00\xc0\x11\x0c\x1d\x00\x00\x00`\x19\r\x17\x04%\x00\x00%\x0e$\x00\x07\x00\x00%\x0e$\x00\x07\x00\x00\x00\x00l\x00%\x0e\x00%\x0e$\x00\x00\x00\x00\x00\x00M\x00\x06\x05\x00\x06\x05\x19\x00\x00\x00\x00\x00\x17\xc0\x0b\x05\x08\xc0\x0b\x05\x08\x00\x03\x00\xc0\x0b\x05\x08\x00\x17\x00\xc0\x0b\x05\x08\x00\x17\x00\x00\x00\x89\xc0\x0b\x05\x08\xc0\x0b\x05\x08\x00\x17\x00\x00\x17\n\x1b\x00\x00\x00\x00\x17\n\x1b\x00\x00\x00\x00\x00\x00d\xe0\x17\n\xe0\x17\n \x00%\x00\x00\x13\x0b\x1c\x00\'\x00\x00\x13\x0b\x1c\x00\'\x00\x00\x00\x00\x8e\x00\x13\x0b\x00\x13\x0b\x1c\x00\'\x00\x00\x00\x00\x8e\x00\x13\x0b\x00\x13\x0b\x1c\x00\x00\x00\x00&\x02!\x00\x05\x02\x00&\x02!\x00\x05\x02\x08\x00\x00@\xc0\x00\x11\x00\x11\x0c\x1d\x00\x00\x00\x02\x00\xb2\xc0\x11\x0c\x1d\xc0\x11\x0c\x1d\x00\x03\x0e\xc0\x11\x0c\x1d\x00\'\x00\xc0\x11\x0c\x1d\x00\'\x00\x00\x19\r\x17\x00\x00\x00\x00\x19\r\x17\x00\x00\x00\x00\x00\x00\xc4\x01\x18\r\x01\x18\r%\x00\x17\x00\x00\x00\x00\x04\x00\x18\r\x00\x18\r\x1e\x00\x04<\x00\x00\x00\x15\x00\x0b\x05\x00\x18\r\x1e\x00\x00\x00 \x18\r&\x00\x00\x00\x00\x0b\x05\x08\x00\'\x00\xc0\x0b\x05\x08\x00\x00\x00\xc0\x0b\x05\x08\x00\x00\x00\x01\x0b\x05\x08\x00\x17\x00\x01\x0b\x05\x08\x00\x17\x00@\x17\n\x1b\x00\x00\x00@\x17\n\x1b\x00\x00\x00\x00\x17\n\x1b\x00\x17\x00\x00\x17\n\x1b\x00\x17\x00\x00\x00\x00E\x00\x13\x0b\x00\x13\x0b)\x00\x16\x00\x00\x00O\x9e\x00\x13\x0b\x00\x13\x0b)\x00\x16\x00\x00\x00O\x9e\x00\x13\x0b\x00\x13\x0b)\x00\x00\x00\x01\x00\x02\'\x00\x02\x0f\x01\x00\x02\'\x00\x02\x0f\x00\x00\x00\xb0\xd0\x00\x11\x00\x11\x0c\x1d\x003\x00\xc0\x11\x0c\x1d\x00\'\x00\xc0\x11\x0c\x1d\x00\'\x00\xc0\x11\x0c\x1d\x00\x00\x00\xc0\x11\x0c\x1d\x00\x00\x00\x00\x18\r%\x00\x00\x00\x00\x18\r%\x00\x00\x00\x00\x00\x00\xb4\x00\x18\r\x00\x18\r%\x00\x00\x00\x00\x18\r\x1e\x003\x00\x00\x18\r\x1e\x00\x00\x00 \x18\r&\x00\x00\x00\x00\x18\r\x1e\x003\x00\x00\x0b\x05\t\x00\x15%\x00\x0b\x05\t\x00\x15%\x00\x0b\x05\x08\x00\x17\x00\x00\x0b\x05\x08\x00\x17\x00\xc0\x17\n\x1b\x00\x00\x00\xc0\x17\n\x1b\x00\x00\x00 \x13\x0b)\x00\x14\x00\xc0\x17\n\x1b\x00\x00\x00 \x13\x0b)\x00\x14\x00\xc0\x17\n\x1b\x00\x00\x00 \x13\x0b)\x00\x14\x00 \x13\x0b)\x00\x14\x00\x00\x00\xde\x01\x00\x02! \x13\x0b)\x00\x14\x00\x00\x00\xde\x01\x00\x02!\x01\x00\x02!\x00\x0f\x00\x00\x00\x00\xc0\xe0\x00\x11\x01\x00\x02!\x00\x0f\x00\x00\x00\x00\xc0\xe0\x00\x11\x00\x11\x0c\x1d\x00\x00\x00\x00\x00\xb1\x97\x00\x11\x0c\x00\x11\x0c\x1d\x00\n\x00\x00\x00\x00\x98\x00\x0b\x05\x00\x0b\x05\x08\x00\x06\x00\x00\x0b\x05\x08\x00\x17\x00\x00\x0b\x05\x08\x00\x06\x00\x00\x0b\x05\x08\x00\x17\x00\x00\x18\r%\x00\x00\x00\x00\x18\r\x1e\x003\x00\x00\x18\r\x1e\x00\x00\x00 \x18\r&\x00\x00\x00\x00\x0b\x05\x08\x00\x06\x00\x00\x0b\x05\x08\x00\x17\x00\x00\x0b\x05\x08\x00\x06\x00\x00\x0b\x05\x08\x00\x17\x00\x00\x0b\x05\x08\x00\x17\x00\x00\x0b\x05\x08\x00\x17\x00\x00\x0b\x05\x08\x00\x17\x00\x00\x00\x00\x99\xe0\x14\x0b\xc0\x17\n\x1b\x00\x00\x00 \x13\x0b)\x00\x14\x00\xc0\x17\n\x1b\x00\x00\x00 \x13\x0b)\x00\x14\x00\xe0\x14\x0b\x16\x00%\x00\xc0 \x06\x13\x00\x00\x00\xe0\x14\x0b\x16\x00%\x00\xc0 \x06\x13\x00\x00\x00\xc0 \x06\x13\x00\x00\x00\x00\x00\x00\xef\x00&\x02\x00&\x02!\x00\x01*\x00\x00\x00\xd0\xc0\x07\x06\xc0\x07\x06(\x00\x00\x00\x00 \x06\x13\x00\x00\x00\x00 \x06\x13\x00\x00\x00\xc0 \x06\t\x00\x00\x00\xc0 \x06\t\x00\x00\x00\x01\x07\x14\x01\x00\x00\x00\x01\x07\x14\x01\x00\x00\x00\x01\x07\x06\x01\x00\x00\x00\x01\x07\x14\x01\x00\x00\x00\x01\x07\x06\x01\x00\x00\x00\x01\x07\x06\x01\x00\x00\x00 \x07\x06\x13\x00\x00\x00\x01\x07\x06\x01\x00\x00\x00 \x07\x06\x13\x00\x00\x00 \x07\x06\x13\x00\x00\x00\x00\x00\xf8\xf8\xf8\xf8\xf8 \x06\x13\x00\x00\x00\x00\x00\xfa\xfa \x07\x06 \x06\x13\x00\x00\x00\x00\x00\xfa\xfa \x07\x06 \x07\x06\x19\x00\x00\x00\x00\x00\xfb\xfb \x06 \x06\x13\x00\x00\x00\x00\x00\xfd\xfd\xfd \x06\x13\x00\x00\x00\x00\x00\xfd\xfd\xfd \x06\x13\x00\x00\x00\x00\x00\xfe \x06\x13 \x06\x13\x00\x02\x00\x08\x00\xff\xdf\xff\x00\x02\x01\x07\x06\x01\x00\x00\x00 \x07\x06\x13\x00\x00\x00\x01\x07\x06\x01\x00\x00\x00 \x07\x06\x13\x00\x00\x00\x00\x02\x03\x05\x00\x00\x02\x03\x0f\x00\x00\x00\x00\x07\x00\x02\x03\x05\x00\x00\x02\x03\x0f\x00\x00\x00\x00\x07\x00\x02\x03\x0f\x00\x00\x00\x00\x07\x06\x13\x00\x00\x00\x00\x02\x03\x0f\x00\x00\x00\x00\x07\x06\x13\x00\x00\x00\x00\x07\x06\x13\x00\x00\x00\x00\x00\x00\xe8\xe8\xe8\xe8\x00\x07\x06\x13\x00\x00\x00\x00\x00\x00\xe8\xe8\xe8\xe8\x00\x07\x06\x13\x00\x00\x00\x00\x00\x00\xe8\xe8\xe8\xe8\x00 \x06\x13\x00\x00\x00\xc0 \x06\x13\x00\x00\x00\xc0 \x06\x13\x00\x00\x00\x00\x00\x00\xea\x00\x07\x06\x00\x07\x06\x19\x00\x00\x00\x00\x00\x00\xeb\x00 \x06\x00 \x06\x13\x00\x00\x00\x00\x00\x00\xed\xed\x00\x07\x00 \x06\x13\x00\x00\x00\x00\x00\x00\xed\xed\x00\x07\x00 \x06\x13\x00\x00\x00\xc0 \x06\x13\x00\x00\x00\x00\x07\x06\x05\x00\x00\x00\x00\x00\x00\xef\x00\x05\x03\x00\x05\x03(\x00\x00\x00\x00\x1f\x03\x05\x00\x00\x00\x00\x02\x03\x0f\x00\x00\x00\x00\x15\x03\r\x00\x00\x00\x00\x15\x03\r\x00\x00\x00\x00\x05\x03\x0f\x00\x00\x00\x00\x05\x03\x0f\x00\x00\x00\x01\x15\x03\r\x00\x00\x00\x01\x15\x03\r\x00\x00\x00\x00\x1c\x0f\x10\x00\x00\x00\x00\x1c\x0f\x10\x00\x00\x00\x00\x1f\x03\x0f\x00\x00\x00\x00\x1f\x03\x0f\x00\x00\x00\x00\x02\x03\x01\x00\x00\x00\x00\x02\x03\x01\x00\x00\x00\x00\x02\x03\x0e\x00\x00\x00\x00\x02\x03\x0e\x00\x00\x00\x01\x05\x03\x05\x00\x00\x00\x01\x05\x03\x05\x00\x00\x00\x01\x07\x06\x10\x00\x00\x00\x01\x07\x06\x10\x00\x00\x00\x80\n\x08\x08\x00\x00\x1a\x80\n\x08\x08\x00\x00\x1a\x00\'\x06\x08\x00\x03\x00\x00\'\x06\x08\x00\x03\x00\x00\n\x08\x11\x00\x00\x00\x00\n\x08\x11\x00\x00\x00\x00\x07\x14\x05\x00\x00\x00\x00\x07\x14\x05\x00\x00\x00\x00\x1e\x11\x05\x00\x00\x00\x00\x1f\x03\x05\x00\x00\x00\x00\x02\x03\x0f\x00\x00\x00\x00\x1f\x03\x05\x00\x00\x00\x00\x02\x03\x0f\x00\x00\x00\x00\x1e\x11\x05\x00\x00\x00\x00\x07\x14\x05\x00\x00\x00\x00\x07\x14\x05\x00\x00\x00\x00\x03\x10\x08\x00\x00\x00\x00\x03\x10\x08\x00\x00\x00\x00\x07\x06\x07\x00\x00\x00\x00\x07\x06\x07\x00\x00\x00\x00"\x12\x07\x00\x00\x00\x00\x07\x06\x07\x00\x00\x00\x00"\x12\x07\x00\x00\x00\x00"\x12\x07\x00\x00\x00\x00 \x14\x05\x00\x00\x00\x00 \x14\x05\x00\x00\x00\xe0#\n\x0f\x00\x00\x00\x00\x05\x03\x0f\x00\x00\x00\x01\x15\x03\r\x00\x00\x00\xe0#\n\x0f\x00\x00\x00\x00\x00\x00\x1d\x00\x1c\x0f\x00\x1c\x0f\x05\x00\x00\x00\xc0\x07\x06\x08\x00\x00\x00\xc0\x07\x06\x08\x00\x00\x00\x00#\n\x0f\x00\x00\x00\x00\x1f\x03\x05\x00\x00\x00\x00\x02\x03\x0f\x00\x00\x00\x00#\n\x0f\x00\x00\x00\x00\x00\x00\x19\x00 \x06\x00 \x06*\x00\x00\x00\x00\x05\x03\x05\x00\x00\x00\x00\x05\x03\x05\x00\x00\x00\x00\x13\x06\x13\x00\x00\x00\x00\x13\x06\x13\x00\x00\x00\x00\x07\x06(\x00\x03\x00\x00\x1e\x11\x05\x00\x00\x00\x00\x07\x14\x05\x00\x00\x00\x00\x1e\x11\x05\x00\x00\x00\x00\x07\x14\x05\x00\x00\x00\x00\x07\x06(\x00\x03\x00\x00\x07\x06(\x00\x00\x00\x00\x07\x06(\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x07\x06(\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00\x07\x06(\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\x00 \x06*\x00\x00\x00\x00\x05\x03\x05\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'), +) diff --git a/worlds/alttp/enemizer_data/dungeon_sprite_addresses.py b/worlds/alttp/enemizer_data/dungeon_sprite_addresses.py new file mode 100644 index 0000000000..af739b103a --- /dev/null +++ b/worlds/alttp/enemizer_data/dungeon_sprite_addresses.py @@ -0,0 +1,202 @@ +from __future__ import annotations + +from typing import NamedTuple + + +class DungeonSpriteAddressData(NamedTuple): + room_id: int + sprite_id_addresses: tuple[int, ...] + +DUNGEON_SPRITE_ADDRESSES = ( + DungeonSpriteAddressData(room_id=2, sprite_id_addresses=(317750, 317753, 317756, 317759, 317762, 317792, 317795)), + DungeonSpriteAddressData(room_id=4, sprite_id_addresses=(317803, 317806, 317809, 317812, 317827, 317839, 317842, 317845)), + DungeonSpriteAddressData(room_id=9, sprite_id_addresses=(317904, 317907, 317910)), + DungeonSpriteAddressData(room_id=10, sprite_id_addresses=(317915, 317918, 317921, 317924, 317930, 317933)), + DungeonSpriteAddressData(room_id=11, sprite_id_addresses=(317941, 317944, 317947, 317950, 317953, 317956, 317959, 317962, 317965)), + DungeonSpriteAddressData(room_id=14, sprite_id_addresses=(317978, 317981, 317984)), + DungeonSpriteAddressData(room_id=17, sprite_id_addresses=(317992, 317995, 317998, 318001, 318004, 318007, 318010, 318013)), + DungeonSpriteAddressData(room_id=19, sprite_id_addresses=(318029, 318032, 318035, 318038, 318044, 318056, 318053, 318041)), + DungeonSpriteAddressData(room_id=21, sprite_id_addresses=(318105, 318108, 318111, 318114, 318117, 318120)), + DungeonSpriteAddressData(room_id=22, sprite_id_addresses=(318125, 318128, 318131, 318134, 318137, 318140, 318143)), + DungeonSpriteAddressData(room_id=23, sprite_id_addresses=(318157, 318160, 318163, 318166, 318169, 318172)), + DungeonSpriteAddressData(room_id=25, sprite_id_addresses=(318177, 318180, 318183, 318186)), + DungeonSpriteAddressData(room_id=26, sprite_id_addresses=(318191, 318194, 318197, 318200, 318203, 318206, 318209, 318212, 318218)), + DungeonSpriteAddressData(room_id=27, sprite_id_addresses=(318232, 318235, 318238, 318241)), + DungeonSpriteAddressData(room_id=30, sprite_id_addresses=(318284, 318287, 318290, 318293, 318296, 318299)), + DungeonSpriteAddressData(room_id=31, sprite_id_addresses=(318304, 318307, 318310, 318313, 318316, 318319, 318322, 318325)), + DungeonSpriteAddressData(room_id=33, sprite_id_addresses=(318335, 318341, 318344, 318347, 318350, 318353, 318356, 318359, 318362, 318365, 318368)), + DungeonSpriteAddressData(room_id=34, sprite_id_addresses=(318373, 318376, 318379, 318382, 318385, 318388, 318391)), + DungeonSpriteAddressData(room_id=36, sprite_id_addresses=(318413, 318416, 318419, 318422, 318425, 318428, 318431)), + DungeonSpriteAddressData(room_id=38, sprite_id_addresses=(318471, 318438, 318441, 318444, 318447, 318450, 318453, 318459, 318462, 318465, 318468)), + DungeonSpriteAddressData(room_id=39, sprite_id_addresses=(318476, 318479, 318482, 318485, 318488, 318491, 318494)), + DungeonSpriteAddressData(room_id=40, sprite_id_addresses=(318511,)), + DungeonSpriteAddressData(room_id=42, sprite_id_addresses=(318530, 318533, 318536, 318539, 318542, 318545)), + DungeonSpriteAddressData(room_id=43, sprite_id_addresses=(318556, 318559, 318562, 318565, 318568, 318571)), + DungeonSpriteAddressData(room_id=46, sprite_id_addresses=(318590, 318593, 318596, 318599, 318602, 318605)), + DungeonSpriteAddressData(room_id=49, sprite_id_addresses=(318621, 318624, 318627, 318630, 318633, 318636, 318639, 318642, 318645, 318648)), + DungeonSpriteAddressData(room_id=50, sprite_id_addresses=(318653, 318656, 318659, 318662, 318665)), + DungeonSpriteAddressData(room_id=52, sprite_id_addresses=(318681, 318684, 318687, 318693, 318696, 318699, 318690)), + DungeonSpriteAddressData(room_id=53, sprite_id_addresses=(318710, 318713, 318716, 318719, 318722, 318728, 318731, 318734, 318725)), + DungeonSpriteAddressData(room_id=54, sprite_id_addresses=(318742, 318745, 318754, 318760, 318763)), + DungeonSpriteAddressData(room_id=55, sprite_id_addresses=(318777, 318780, 318783, 318786, 318792, 318795, 318798, 318801, 318789)), + DungeonSpriteAddressData(room_id=56, sprite_id_addresses=(318806, 318809, 318812, 318815, 318818, 318821, 318824)), + DungeonSpriteAddressData(room_id=57, sprite_id_addresses=(318829, 318835, 318841, 318844, 318847, 318850)), + DungeonSpriteAddressData(room_id=58, sprite_id_addresses=(318855, 318858, 318861, 318864, 318867, 318870)), + DungeonSpriteAddressData(room_id=59, sprite_id_addresses=(318875, 318878, 318881, 318884, 318887, 318890, 318893)), + DungeonSpriteAddressData(room_id=60, sprite_id_addresses=(318898, 318901, 318904)), + DungeonSpriteAddressData(room_id=61, sprite_id_addresses=(318915, 318921, 318924, 318927, 318930, 318933, 318939, 318942, 318945, 318948, 318951)), + DungeonSpriteAddressData(room_id=62, sprite_id_addresses=(318959, 318962, 318980, 318983, 318989, 318992)), + DungeonSpriteAddressData(room_id=63, sprite_id_addresses=(319000, 319006, 319009)), + DungeonSpriteAddressData(room_id=64, sprite_id_addresses=(319014, 319017, 319023, 319026, 319029)), + DungeonSpriteAddressData(room_id=65, sprite_id_addresses=(319036, 319039, 319042, 319045)), + DungeonSpriteAddressData(room_id=66, sprite_id_addresses=(319050, 319053, 319056, 319059, 319062, 319065)), + DungeonSpriteAddressData(room_id=67, sprite_id_addresses=(319070, 319073)), + DungeonSpriteAddressData(room_id=68, sprite_id_addresses=(319084, 319087, 319090, 319093, 319096, 319102)), + DungeonSpriteAddressData(room_id=69, sprite_id_addresses=(319110, 319116, 319119, 319131, 319134, 319137, 319113, 319122, 319125, 319128)), + DungeonSpriteAddressData(room_id=70, sprite_id_addresses=(319142, 319148, 319154)), + DungeonSpriteAddressData(room_id=73, sprite_id_addresses=(319161, 319164, 319167, 319170, 319173, 319176, 319182, 319185, 319188, 319191, 319194, 319197)), + DungeonSpriteAddressData(room_id=74, sprite_id_addresses=(319205, 319208)), + DungeonSpriteAddressData(room_id=75, sprite_id_addresses=(319213, 319216, 319219, 319222, 319225, 319228, 319231, 319234)), + DungeonSpriteAddressData(room_id=76, sprite_id_addresses=(319245, 319248, 319251, 319254, 319257, 319260)), + DungeonSpriteAddressData(room_id=78, sprite_id_addresses=(319270, 319273, 319276, 319279)), + DungeonSpriteAddressData(room_id=80, sprite_id_addresses=(319295, 319298, 319301)), + DungeonSpriteAddressData(room_id=81, sprite_id_addresses=(319309, 319312)), + DungeonSpriteAddressData(room_id=82, sprite_id_addresses=(319317, 319320, 319323)), + DungeonSpriteAddressData(room_id=83, sprite_id_addresses=(319328, 319331, 319334, 319337, 319340, 319343, 319346, 319349, 319352, 319355, 319358, 319361, 319364)), + DungeonSpriteAddressData(room_id=84, sprite_id_addresses=(319369, 319372, 319375, 319378, 319381, 319384, 319387, 319390)), + DungeonSpriteAddressData(room_id=85, sprite_id_addresses=(319398, 319401)), + DungeonSpriteAddressData(room_id=86, sprite_id_addresses=(319415, 319418, 319421, 319424, 319430, 319433, 319436, 319442, 319439)), + DungeonSpriteAddressData(room_id=87, sprite_id_addresses=(319447, 319450, 319453, 319456, 319459, 319462, 319468, 319471, 319474, 319477, 319480, 319483, 319486, 319489)), + DungeonSpriteAddressData(room_id=88, sprite_id_addresses=(319497, 319500, 319506, 319509, 319515, 319518, 319521)), + DungeonSpriteAddressData(room_id=89, sprite_id_addresses=(319526, 319529, 319538, 319544, 319547, 319550, 319553, 319556, 319559, 319541)), + DungeonSpriteAddressData(room_id=91, sprite_id_addresses=(319575, 319578, 319581, 319584)), + DungeonSpriteAddressData(room_id=93, sprite_id_addresses=(319615, 319618, 319621, 319624, 319627, 319633, 319636, 319639, 319651, 319642, 319645, 319648, 319630)), + DungeonSpriteAddressData(room_id=94, sprite_id_addresses=(319659, 319662, 319665, 319668)), + DungeonSpriteAddressData(room_id=95, sprite_id_addresses=(319673, 319676, 319679)), + DungeonSpriteAddressData(room_id=96, sprite_id_addresses=(319684,)), + DungeonSpriteAddressData(room_id=97, sprite_id_addresses=(319689, 319692, 319695)), + DungeonSpriteAddressData(room_id=98, sprite_id_addresses=(319700, 319703, 319706)), + DungeonSpriteAddressData(room_id=99, sprite_id_addresses=(319714, 319711)), + DungeonSpriteAddressData(room_id=100, sprite_id_addresses=(319719, 319725, 319728, 319731, 319734, 319737)), + DungeonSpriteAddressData(room_id=101, sprite_id_addresses=(319760, 319763, 319766, 319769, 319772)), + DungeonSpriteAddressData(room_id=102, sprite_id_addresses=(319777, 319783, 319786, 319795, 319798, 319801, 319804, 319810)), + DungeonSpriteAddressData(room_id=103, sprite_id_addresses=(319818, 319821, 319824, 319827, 319830, 319833, 319836, 319839, 319842)), + DungeonSpriteAddressData(room_id=104, sprite_id_addresses=(319859, 319865, 319868)), + DungeonSpriteAddressData(room_id=106, sprite_id_addresses=(319873, 319876, 319879, 319882, 319885, 319888)), + DungeonSpriteAddressData(room_id=107, sprite_id_addresses=(319899, 319902, 319905, 319911, 319914, 319917, 319920, 319923, 319926, 319929, 319932)), + DungeonSpriteAddressData(room_id=109, sprite_id_addresses=(319954, 319957, 319960, 319963, 319966, 319969, 319972, 319975, 319978)), + DungeonSpriteAddressData(room_id=110, sprite_id_addresses=(319983, 319986, 319989, 319992, 319995)), + DungeonSpriteAddressData(room_id=113, sprite_id_addresses=(320000, 320003)), + DungeonSpriteAddressData(room_id=114, sprite_id_addresses=(320011, 320017)), + DungeonSpriteAddressData(room_id=115, sprite_id_addresses=(320022, 320025, 320028, 320031, 320034, 320037)), + DungeonSpriteAddressData(room_id=116, sprite_id_addresses=(320045, 320048, 320051, 320054, 320057, 320060, 320063, 320066)), + DungeonSpriteAddressData(room_id=117, sprite_id_addresses=(320071, 320074, 320077, 320080, 320083, 320086, 320095, 320098)), + DungeonSpriteAddressData(room_id=118, sprite_id_addresses=(320106, 320109, 320112, 320115, 320121)), + DungeonSpriteAddressData(room_id=119, sprite_id_addresses=(320126, 320138, 320141)), + DungeonSpriteAddressData(room_id=123, sprite_id_addresses=(320146, 320149, 320152, 320155, 320158, 320161, 320167, 320170, 320173, 320176)), + DungeonSpriteAddressData(room_id=124, sprite_id_addresses=(320181, 320184, 320187, 320190, 320193, 320196)), + DungeonSpriteAddressData(room_id=125, sprite_id_addresses=(320216, 320219, 320225, 320228, 320234, 320222, 320231, 320204, 320207, 320210, 320213, 320222, 320231)), + DungeonSpriteAddressData(room_id=126, sprite_id_addresses=(320242, 320245, 320254, 320257)), + DungeonSpriteAddressData(room_id=128, sprite_id_addresses=(320291, 320294)), + DungeonSpriteAddressData(room_id=129, sprite_id_addresses=(320302, 320305)), + DungeonSpriteAddressData(room_id=130, sprite_id_addresses=(320310, 320313, 320316)), + DungeonSpriteAddressData(room_id=131, sprite_id_addresses=(320321, 320324, 320327, 320330, 320333, 320336, 320339, 320342, 320345, 320348)), + DungeonSpriteAddressData(room_id=132, sprite_id_addresses=(320353, 320356, 320359, 320362, 320365, 320368, 320371)), + DungeonSpriteAddressData(room_id=133, sprite_id_addresses=(320376, 320379, 320382, 320385, 320388, 320391, 320394, 320397, 320400, 320403)), + DungeonSpriteAddressData(room_id=135, sprite_id_addresses=(320410, 320413, 320416, 320419, 320434, 320437, 320440, 320446, 320422)), + DungeonSpriteAddressData(room_id=139, sprite_id_addresses=(320468, 320471, 320474, 320477, 320480)), + DungeonSpriteAddressData(room_id=140, sprite_id_addresses=(320503, 320506, 320509, 320512, 320518, 320521, 320527, 320524, 320515)), + DungeonSpriteAddressData(room_id=141, sprite_id_addresses=(320538, 320541, 320544, 320547, 320550, 320556, 320559, 320562, 320565, 320568, 320571, 320535)), + DungeonSpriteAddressData(room_id=142, sprite_id_addresses=(320579, 320582, 320585, 320588, 320591, 320594, 320597)), + DungeonSpriteAddressData(room_id=145, sprite_id_addresses=(320610, 320616, 320619, 320622, 320625, 320613)), + DungeonSpriteAddressData(room_id=146, sprite_id_addresses=(320636, 320639, 320642, 320645, 320648, 320654, 320657, 320660, 320663)), + DungeonSpriteAddressData(room_id=147, sprite_id_addresses=(320668, 320671, 320674, 320677, 320680, 320683, 320686, 320689)), + DungeonSpriteAddressData(room_id=149, sprite_id_addresses=(320694, 320697, 320700, 320703)), + DungeonSpriteAddressData(room_id=151, sprite_id_addresses=(320728,)), + DungeonSpriteAddressData(room_id=152, sprite_id_addresses=(320733, 320736, 320739, 320742, 320745)), + DungeonSpriteAddressData(room_id=153, sprite_id_addresses=(320750, 320753, 320756, 320759, 320765, 320768, 320771, 320774, 320777, 320780)), + DungeonSpriteAddressData(room_id=155, sprite_id_addresses=(320794, 320797, 320800, 320803, 320806, 320809, 320812, 320815, 320818, 320821)), + DungeonSpriteAddressData(room_id=156, sprite_id_addresses=(320826, 320829, 320832, 320835, 320838, 320841)), + DungeonSpriteAddressData(room_id=157, sprite_id_addresses=(320852, 320855, 320858, 320861, 320864, 320867, 320870, 320873)), + DungeonSpriteAddressData(room_id=158, sprite_id_addresses=(320878, 320881, 320884, 320887)), + DungeonSpriteAddressData(room_id=159, sprite_id_addresses=(320907, 320910)), + DungeonSpriteAddressData(room_id=160, sprite_id_addresses=(320915, 320918, 320921)), + DungeonSpriteAddressData(room_id=161, sprite_id_addresses=(320929, 320932, 320935, 320938, 320941, 320944, 320947, 320950)), + DungeonSpriteAddressData(room_id=165, sprite_id_addresses=(320968, 320971, 320974, 320977, 320980, 320983, 320986, 320989, 320998, 321001)), + DungeonSpriteAddressData(room_id=167, sprite_id_addresses=(321014, 321017)), + DungeonSpriteAddressData(room_id=168, sprite_id_addresses=(321022, 321025, 321028, 321031, 321034)), + DungeonSpriteAddressData(room_id=169, sprite_id_addresses=(321039, 321042, 321057, 321060, 321045, 321048, 321051, 321054)), + DungeonSpriteAddressData(room_id=170, sprite_id_addresses=(321065, 321068, 321071, 321074, 321077, 321080)), + DungeonSpriteAddressData(room_id=171, sprite_id_addresses=(321088, 321091, 321094, 321097, 321100, 321103, 321106)), + DungeonSpriteAddressData(room_id=174, sprite_id_addresses=(321116, 321119)), + DungeonSpriteAddressData(room_id=176, sprite_id_addresses=(321129, 321132, 321135, 321138, 321141, 321144, 321147, 321150, 321153, 321156, 321159, 321165, 321168)), + DungeonSpriteAddressData(room_id=177, sprite_id_addresses=(321173, 321176, 321179, 321182, 321185, 321188, 321191, 321194, 321197, 321200)), + DungeonSpriteAddressData(room_id=178, sprite_id_addresses=(321205, 321208, 321211, 321214, 321217, 321220, 321223, 321226, 321229, 321232, 321235, 321238, 321241, 321244)), + DungeonSpriteAddressData(room_id=179, sprite_id_addresses=(321249, 321252, 321255, 321258, 321261)), + DungeonSpriteAddressData(room_id=182, sprite_id_addresses=(321277, 321280, 321289, 321292, 321301, 321304)), + DungeonSpriteAddressData(room_id=183, sprite_id_addresses=(321309, 321312)), + DungeonSpriteAddressData(room_id=184, sprite_id_addresses=(321317, 321320, 321323, 321326, 321329, 321332)), + DungeonSpriteAddressData(room_id=186, sprite_id_addresses=(321342, 321345, 321348, 321351, 321354, 321357, 321360)), + DungeonSpriteAddressData(room_id=187, sprite_id_addresses=(321365, 321368, 321371, 321374, 321377, 321380, 321386, 321389, 321392, 321395, 321383)), + DungeonSpriteAddressData(room_id=188, sprite_id_addresses=(321403, 321406, 321409, 321412, 321418, 321421, 321424, 321433, 321400, 321415, 321427, 321430)), + DungeonSpriteAddressData(room_id=190, sprite_id_addresses=(321440, 321446, 321449, 321452, 321455, 321458)), + DungeonSpriteAddressData(room_id=192, sprite_id_addresses=(321471, 321474, 321477, 321480, 321486, 321489, 321492, 321495)), + DungeonSpriteAddressData(room_id=193, sprite_id_addresses=(321503, 321506, 321509, 321512, 321518, 321524, 321527, 321530, 321521, 321515, 321536)), + DungeonSpriteAddressData(room_id=194, sprite_id_addresses=(321547, 321550, 321553, 321556, 321562, 321559, 321544, 321541)), + DungeonSpriteAddressData(room_id=195, sprite_id_addresses=(321567, 321585, 321588)), + DungeonSpriteAddressData(room_id=196, sprite_id_addresses=(321605, 321608, 321611, 321614, 321617, 321620)), + DungeonSpriteAddressData(room_id=201, sprite_id_addresses=(321697, 321700, 321703)), + DungeonSpriteAddressData(room_id=203, sprite_id_addresses=(321708, 321717, 321720, 321723, 321726, 321729, 321732, 321735, 321738, 321741, 321714, 321711)), + DungeonSpriteAddressData(room_id=204, sprite_id_addresses=(321746, 321749, 321755, 321758, 321761, 321770, 321773, 321776, 321779, 321782, 321785, 321752, 321764, 321767)), + DungeonSpriteAddressData(room_id=206, sprite_id_addresses=(321790, 321793, 321799, 321802, 321805, 321808, 321811)), + DungeonSpriteAddressData(room_id=208, sprite_id_addresses=(321816, 321819, 321822, 321825, 321828, 321831, 321834, 321837, 321840, 321843, 321846)), + DungeonSpriteAddressData(room_id=209, sprite_id_addresses=(321851, 321854, 321857, 321860, 321863, 321866, 321869, 321872)), + DungeonSpriteAddressData(room_id=210, sprite_id_addresses=(321877, 321880, 321883, 321886, 321889, 321892, 321895, 321898, 321901, 321904)), + DungeonSpriteAddressData(room_id=216, sprite_id_addresses=(321937, 321940, 321943, 321946, 321949, 321952, 321955, 321958, 321961, 321964, 321967)), + DungeonSpriteAddressData(room_id=217, sprite_id_addresses=(321975, 321978, 321981, 321972)), + DungeonSpriteAddressData(room_id=218, sprite_id_addresses=(321986, 321989)), + DungeonSpriteAddressData(room_id=219, sprite_id_addresses=(321994, 321997, 322000, 322006, 322003, 322012, 322009)), + DungeonSpriteAddressData(room_id=220, sprite_id_addresses=(322020, 322023, 322026, 322029, 322032, 322035, 322047, 322017, 322038, 322041, 322044)), + DungeonSpriteAddressData(room_id=223, sprite_id_addresses=(322063, 322066)), + DungeonSpriteAddressData(room_id=224, sprite_id_addresses=(322071, 322074, 322077, 322080)), + DungeonSpriteAddressData(room_id=232, sprite_id_addresses=(322189, 322192, 322195, 322198)), + DungeonSpriteAddressData(room_id=238, sprite_id_addresses=(322213, 322216, 322219, 322222, 322225)), + DungeonSpriteAddressData(room_id=239, sprite_id_addresses=(322230, 322233, 322236)), + DungeonSpriteAddressData(room_id=249, sprite_id_addresses=(322323, 322326, 322329, 322332)), + DungeonSpriteAddressData(room_id=254, sprite_id_addresses=(322378, 322381, 322384, 322387, 322390)), + DungeonSpriteAddressData(room_id=263, sprite_id_addresses=(322444, 322447)), + DungeonSpriteAddressData(room_id=264, sprite_id_addresses=(322452, 322455, 322458, 322461)), + DungeonSpriteAddressData(room_id=267, sprite_id_addresses=(322494,)), + DungeonSpriteAddressData(room_id=269, sprite_id_addresses=(322525, 322528)), + DungeonSpriteAddressData(room_id=291, sprite_id_addresses=(322671, 322674, 322677, 322680)), +) + +KEYED_SPRITE_ID_ADDRESSES = frozenset((317984, + 318044, + 318335, + 318835, + 318915, + 318983, + 320003, + 320011, + 320294, + 320759, + 321292, + 321480, + 321530, + 320000, + 321159, + 321937, + 321940, + 321943, + 321946, + 321949, + 321952, + 321955, + 321958, + 321961, + 321964, + 321967, + 321424, + 321421, + 321418)) diff --git a/worlds/alttp/enemizer_data/enemy_room_metadata.py b/worlds/alttp/enemizer_data/enemy_room_metadata.py new file mode 100644 index 0000000000..f8d29c1538 --- /dev/null +++ b/worlds/alttp/enemizer_data/enemy_room_metadata.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +from typing import NamedTuple, Optional + + +class RoomGroupRequirementData(NamedTuple): + group_id: Optional[int] + subgroup_0: Optional[int] + subgroup_1: Optional[int] + subgroup_2: Optional[int] + subgroup_3: Optional[int] + rooms: tuple[int, ...] + +SHUTTER_ROOM_IDS = frozenset((184, + 11, + 27, + 75, + 4, + 36, + 182, + 40, + 14, + 46, + 62, + 110, + 49, + 135, + 68, + 69, + 83, + 117, + 133, + 61, + 93, + 107, + 109, + 123, + 125, + 141, + 150, + 165, + 113, + 168, + 216, + 176, + 192, + 224, + 178, + 210, + 239, + 268, + 291)) +WATER_ROOM_IDS = frozenset((22, 40, 52, 54, 56, 70, 102)) +DONT_RANDOMIZE_ROOM_IDS = frozenset((0, 1, 3, 13, 20, 32, 48, 127)) +NO_SPECIAL_ENEMIES_STANDARD_ROOM_IDS = frozenset((1, 2, 17, 33, 34, 50, 65, 66, 80, 81, 82, 85, 96, 97, 98, 112, 113, 114, 128, 129, 130)) +BOSS_ROOM_IDS = frozenset((200, 51, 108, 7, 77, 90, 6, 41, 172, 222, 144, 164, 32, 13, 0)) + +ROOM_GROUP_REQUIREMENTS = ( + RoomGroupRequirementData(group_id=1, subgroup_0=70, subgroup_1=73, subgroup_2=28, subgroup_3=82, rooms=(228, 240)), + RoomGroupRequirementData(group_id=5, subgroup_0=75, subgroup_1=77, subgroup_2=74, subgroup_3=90, rooms=(243, 265, 270, 271, 272, 273, 282, 284, 290)), + RoomGroupRequirementData(group_id=None, subgroup_0=75, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(255, 274, 287)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=77, subgroup_2=None, subgroup_3=21, rooms=(289,)), + RoomGroupRequirementData(group_id=7, subgroup_0=75, subgroup_1=77, subgroup_2=57, subgroup_3=54, rooms=(8, 44, 276, 277)), + RoomGroupRequirementData(group_id=13, subgroup_0=81, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(85, 258, 260)), + RoomGroupRequirementData(group_id=14, subgroup_0=71, subgroup_1=73, subgroup_2=76, subgroup_3=80, rooms=(18, 261, 266)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=80, rooms=(264,)), + RoomGroupRequirementData(group_id=15, subgroup_0=79, subgroup_1=77, subgroup_2=74, subgroup_3=80, rooms=(244, 245, 257, 259, 262, 280, 281)), + RoomGroupRequirementData(group_id=18, subgroup_0=85, subgroup_1=61, subgroup_2=66, subgroup_3=67, rooms=(32, 48)), + RoomGroupRequirementData(group_id=24, subgroup_0=85, subgroup_1=26, subgroup_2=66, subgroup_3=67, rooms=(13,)), + RoomGroupRequirementData(group_id=34, subgroup_0=33, subgroup_1=65, subgroup_2=69, subgroup_3=51, rooms=(0,)), + RoomGroupRequirementData(group_id=40, subgroup_0=14, subgroup_1=None, subgroup_2=74, subgroup_3=80, rooms=(225, 256, 293, 292, 294)), + RoomGroupRequirementData(group_id=None, subgroup_0=14, subgroup_1=30, subgroup_2=None, subgroup_3=None, rooms=(291,)), + RoomGroupRequirementData(group_id=23, subgroup_0=64, subgroup_1=None, subgroup_2=None, subgroup_3=63, rooms=()), + RoomGroupRequirementData(group_id=9, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=29, rooms=(227,)), + RoomGroupRequirementData(group_id=11, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=61, rooms=()), + RoomGroupRequirementData(group_id=22, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=49, rooms=()), + RoomGroupRequirementData(group_id=22, subgroup_0=None, subgroup_1=None, subgroup_2=60, subgroup_3=None, rooms=()), + RoomGroupRequirementData(group_id=21, subgroup_0=None, subgroup_1=None, subgroup_2=58, subgroup_3=62, rooms=()), + RoomGroupRequirementData(group_id=28, subgroup_0=None, subgroup_1=None, subgroup_2=38, subgroup_3=82, rooms=(14, 126, 142, 158, 190)), + RoomGroupRequirementData(group_id=12, subgroup_0=None, subgroup_1=None, subgroup_2=48, subgroup_3=None, rooms=()), + RoomGroupRequirementData(group_id=26, subgroup_0=None, subgroup_1=None, subgroup_2=56, subgroup_3=None, rooms=()), + RoomGroupRequirementData(group_id=20, subgroup_0=None, subgroup_1=None, subgroup_2=57, subgroup_3=None, rooms=()), + RoomGroupRequirementData(group_id=32, subgroup_0=None, subgroup_1=44, subgroup_2=59, subgroup_3=None, rooms=()), + RoomGroupRequirementData(group_id=3, subgroup_0=93, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(81,)), + RoomGroupRequirementData(group_id=42, subgroup_0=21, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(286,)), + RoomGroupRequirementData(group_id=10, subgroup_0=47, subgroup_1=None, subgroup_2=46, subgroup_3=None, rooms=(92, 117, 185, 217)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=34, subgroup_3=None, rooms=(54, 70, 102, 118)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=32, subgroup_2=None, subgroup_3=None, rooms=(62, 159)), + RoomGroupRequirementData(group_id=None, subgroup_0=31, subgroup_1=None, subgroup_2=None, subgroup_3=None, rooms=(127,)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=35, subgroup_3=None, rooms=(57, 73, 86, 87, 104, 141)), + RoomGroupRequirementData(group_id=37, subgroup_0=31, subgroup_1=None, subgroup_2=39, subgroup_3=82, rooms=(36, 180, 181, 198, 199, 214)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(23, 42, 68, 76, 86, 88, 89, 103, 104, 126, 139, 235, 251)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(23, 42, 76, 89, 103, 104, 126, 139, 235, 251)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(11, 19, 27, 30, 42, 43, 49, 61, 62, 91, 107, 119, 135, 139, 145, 146, 155, 157, 161, 171, 182, 191, 193, 196, 239)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(11, 19, 27, 30, 42, 43, 49, 53, 62, 91, 107, 119, 135, 139, 145, 146, 155, 157, 161, 171, 182, 191, 193, 196, 239)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(19, 35, 150, 165, 195, 197, 213)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(19, 35, 150, 165, 197, 213)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(26, 38, 43, 64, 74, 87, 107, 123)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(38, 43, 64, 74, 87, 107, 123, 206)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(2, 88, 100, 140, 267)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=82, rooms=(26, 61, 68, 86, 94, 124, 149, 195)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(4, 63, 206)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=83, rooms=(53, 55, 118)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=34, subgroup_3=None, rooms=(40,)), + RoomGroupRequirementData(group_id=None, subgroup_0=None, subgroup_1=None, subgroup_2=37, subgroup_3=None, rooms=(151,)), +) diff --git a/worlds/alttp/enemizer_data/enemy_sprite_requirements.py b/worlds/alttp/enemizer_data/enemy_sprite_requirements.py new file mode 100644 index 0000000000..472cc9d7b1 --- /dev/null +++ b/worlds/alttp/enemizer_data/enemy_sprite_requirements.py @@ -0,0 +1,295 @@ +from __future__ import annotations + +from typing import NamedTuple, Optional + + +class EnemySpriteRequirementData(NamedTuple): + sprite_name: str + sprite_id: int + boss: bool + overlord: bool + do_not_randomize: bool + killable: bool + npc: bool + never_use_dungeon: bool + never_use_overworld: bool + cannot_have_key: bool + is_object: bool + absorbable: bool + is_water_sprite: bool + is_enemy_sprite: bool + group_ids: tuple[int, ...] + subgroup_0: tuple[int, ...] + subgroup_1: tuple[int, ...] + subgroup_2: tuple[int, ...] + subgroup_3: tuple[int, ...] + parameters: Optional[int] + special_glitched: bool + excluded_rooms: tuple[int, ...] + dont_randomize_rooms: tuple[int, ...] + spawnable_rooms: tuple[int, ...] + +ENEMY_SPRITE_REQUIREMENTS = ( + EnemySpriteRequirementData(sprite_name='RavenSprite', sprite_id=0, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17, 25), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='VultureSprite', sprite_id=1, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='EmptySprite', sprite_id=3, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='PullSwitch_GoodSprite', sprite_id=4, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='PullSwitch_TrapSprite', sprite_id=6, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Octorok_OneWaySprite', sprite_id=8, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12, 24), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MoldormSprite', sprite_id=9, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(48,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Octorok_FourWaySprite', sprite_id=10, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ChickenSprite', sprite_id=11, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(21, 80), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BuzzblobSprite', sprite_id=13, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SnapdragonSprite', sprite_id=14, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(22,), subgroup_1=(), subgroup_2=(23,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OctoballoonSprite', sprite_id=15, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OctoballoonHatchlingsSprite', sprite_id=16, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='HinoxSprite', sprite_id=17, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(22,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MoblinSprite', sprite_id=18, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(23,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MiniHelmasaurSprite', sprite_id=19, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(30,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GargoylesDomainGateSprite', sprite_id=20, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AntifairySprite', sprite_id=21, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(64, 210, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SahasrahlaAginahSprite', sprite_id=22, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(76,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BushHoarderSprite', sprite_id=23, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MiniMoldormSprite', sprite_id=24, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(30,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='PoeSprite', sprite_id=25, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(14, 21), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='DwarvesSprite', sprite_id=26, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(77,), subgroup_2=(), subgroup_3=(21,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ArrowInWall_MaybeSprite', sprite_id=27, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='StatueSprite', sprite_id=28, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268, 63), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='WeathervaneSprite', sprite_id=29, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CrystalSwitchSprite', sprite_id=30, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BugCatchingKidSprite', sprite_id=31, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(81,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SluggulaSprite', sprite_id=32, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(37,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='PushSwitchSprite', sprite_id=33, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(83,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RopaSprite', sprite_id=34, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(22,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RedBariSprite', sprite_id=35, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(127,), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BlueBariSprite', sprite_id=36, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(127,), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='TalkingTreeSprite', sprite_id=37, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(21,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='HardhatBeetleSprite', sprite_id=38, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(30,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='DeadrockSprite', sprite_id=39, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(127, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='StorytellersSprite', sprite_id=40, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BlindHideoutAttendantSprite', sprite_id=41, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14, 79), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SweepingLadySprite', sprite_id=42, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MultipurposeSpriteSprite', sprite_id=43, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='LumberjacksSprite', sprite_id=44, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(74,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='TelepathicStones_NoIdeaWhatThisActuallyIsLikelyUnusedSprite', sprite_id=45, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FluteBoysNotesSprite', sprite_id=46, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RaceHPNPCsSprite', sprite_id=47, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Person_MaybeSprite', sprite_id=48, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FortuneTellerSprite', sprite_id=49, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AngryBrothersSprite', sprite_id=50, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(79,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='PullForRupeesSpriteSprite', sprite_id=51, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ScaredGirl2Sprite', sprite_id=52, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='InnkeeperSprite', sprite_id=53, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='WitchSprite', sprite_id=54, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(76,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='WaterfallSprite', sprite_id=55, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ArrowTargetSprite', sprite_id=56, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AverageMiddleAgedManSprite', sprite_id=57, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='HalfMagicBatSprite', sprite_id=58, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(29,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='DashItemSprite', sprite_id=59, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='VillageKidSprite', sprite_id=60, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Signs_ChickenLadyAlsoShowedUp_ScaredLadiesOutsideHousesSprite', sprite_id=61, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RockHoarderSprite', sprite_id=62, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='TutorialSoldierSprite', sprite_id=63, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='LightningLockSprite', sprite_id=64, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BlueSwordSoldier_DetectPlayerSprite', sprite_id=65, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(13, 73), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GreenSwordSoldierSprite', sprite_id=66, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RedSpearSoldierSprite', sprite_id=67, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(13, 73), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AssaultSwordSoldierSprite', sprite_id=68, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GreenSpearSoldierSprite', sprite_id=69, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(13, 73), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BlueArcherSprite', sprite_id=70, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(72,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GreenArcherSprite', sprite_id=71, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(72,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RedJavelinSoldierSprite', sprite_id=72, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RedJavelinSoldier2Sprite', sprite_id=73, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RedBombSoldiersSprite', sprite_id=74, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GreenSoldierRecruits_HMKnightSprite', sprite_id=75, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(73,), subgroup_2=(19,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GeldmanSprite', sprite_id=76, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RabbitSprite', sprite_id=77, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='PopoSprite', sprite_id=78, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Popo2Sprite', sprite_id=79, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CannonBallsSprite', sprite_id=80, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ArmosSprite', sprite_id=81, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GiantZoraSprite', sprite_id=82, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(68,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ArmosKnightsSprite', sprite_id=83, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(29,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='LanmolasSprite', sprite_id=84, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(49,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FireballZoraSprite', sprite_id=85, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12, 24), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='WalkingZoraSprite', sprite_id=86, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(68,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='DesertPalaceBarriersSprite', sprite_id=87, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CrabSprite', sprite_id=88, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(12,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BirdSprite', sprite_id=89, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(55,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SquirrelSprite', sprite_id=90, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(55,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Spark_LeftToRightSprite', sprite_id=91, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Spark_RightToLeftSprite', sprite_id=92, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Roller_VerticalMovingSprite', sprite_id=93, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Roller_VerticalMoving2Sprite', sprite_id=94, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RollerSprite', sprite_id=95, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Roller_HorizontalMovingSprite', sprite_id=96, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BeamosSprite', sprite_id=97, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MasterSwordSprite', sprite_id=98, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(55,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Devalant_NonShooterSprite', sprite_id=99, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Devalant_ShooterSprite', sprite_id=100, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ShootingGalleryProprietorSprite', sprite_id=101, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_RightSprite', sprite_id=102, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_LeftSprite', sprite_id=103, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_DownSprite', sprite_id=104, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MovingCannonBallShooters_UpSprite', sprite_id=105, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BallNChainTrooperSprite', sprite_id=106, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CannonSoldierSprite', sprite_id=107, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MirrorPortalSprite', sprite_id=108, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RatSprite', sprite_id=109, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RopeSprite', sprite_id=110, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='KeeseSprite', sprite_id=111, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='LeeverSprite', sprite_id=113, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(47,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ActivatoForThePonds_WhereYouThrowInItemsSprite', sprite_id=114, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='UnclePriestSprite', sprite_id=115, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(71, 81), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RunningManSprite', sprite_id=116, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BottleSalesmanSprite', sprite_id=117, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(6,), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='PrincessZeldaSprite', sprite_id=118, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='VillageElderSprite', sprite_id=120, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AgahnimSprite', sprite_id=122, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(85,), subgroup_1=(26, 61), subgroup_2=(66,), subgroup_3=(67,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AgahnimEnergyBallSprite', sprite_id=123, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FloatingStalfosHeadSprite', sprite_id=124, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BigSpikeTrapSprite', sprite_id=125, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GuruguruBar_ClockwiseSprite', sprite_id=126, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(181, 150), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GuruguruBar_CounterClockwiseSprite', sprite_id=127, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(181, 150), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='WinderSprite', sprite_id=128, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='WaterTektiteSprite', sprite_id=129, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(40,), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AntifairyCircleSprite', sprite_id=130, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GreenEyegoreSprite', sprite_id=131, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RedEyegoreSprite', sprite_id=132, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='KodongosSprite', sprite_id=134, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(42,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MothulaSprite', sprite_id=136, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(56,), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MothulasBeamSprite', sprite_id=137, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(56,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SpikeTrapSprite', sprite_id=138, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(40, 11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GibdoSprite', sprite_id=139, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(35,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ArrghusSprite', sprite_id=140, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(57,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ArrghusSpawnSprite', sprite_id=141, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(57,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='TerrorpinSprite', sprite_id=142, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(42,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SlimeSprite_JumpsOutOfTheFloor', sprite_id=143, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='WallmasterSprite', sprite_id=144, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(35,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(1, 2, 17, 33, 34, 50, 65, 66, 80, 81, 82, 96, 97, 98, 112, 113, 114, 128, 129, 130, 137, 153, 168, 169, 170, 184, 185, 186, 200, 201, 216, 217, 218, 51, 67, 83, 99, 115, 116, 117, 131, 132, 133, 7, 23, 39, 49, 119, 135, 167, 32, 48, 64, 176, 192, 208, 224, 9, 10, 11, 25, 26, 27, 42, 43, 58, 59, 74, 75, 90, 106, 6, 22, 38, 40, 52, 53, 54, 55, 56, 70, 84, 102, 118, 41, 57, 73, 86, 87, 88, 89, 103, 104, 68, 69, 100, 101, 171, 172, 187, 188, 203, 204, 219, 220, 14, 30, 31, 46, 62, 63, 78, 79, 94, 95, 110, 126, 127, 142, 158, 159, 174, 175, 190, 191, 206, 222, 144, 145, 146, 147, 151, 152, 160, 161, 162, 163, 177, 178, 179, 193, 194, 195, 209, 210, 4, 19, 20, 21, 35, 36, 164, 180, 181, 182, 183, 196, 197, 198, 199, 213, 214, 12, 13, 28, 29, 61, 76, 77, 91, 92, 93, 107, 108, 109, 123, 124, 125, 139, 140, 141, 149, 150, 155, 156, 157, 165, 166)), + EnemySpriteRequirementData(sprite_name='StalfosKnightSprite', sprite_id=145, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='HelmasaurKingSprite', sprite_id=146, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(58,), subgroup_3=(62,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BumperSprite', sprite_id=147, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SwimmersEvilSprite', sprite_id=148, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='EyeLaser_RightSprite', sprite_id=149, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='EyeLaser_LeftSprite', sprite_id=150, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='EyeLaser_DownSprite', sprite_id=151, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='EyeLaser_UpSprite', sprite_id=152, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82, 83), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='PengatorSprite', sprite_id=153, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(38,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='KyameronWaterSplashSprite', sprite_id=154, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(268,), dont_randomize_rooms=(40,), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='WizzrobeSprite', sprite_id=155, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(37, 41), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='VerminHorizontalSprite', sprite_id=156, boss=False, overlord=False, do_not_randomize=True, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='VerminVerticalSprite', sprite_id=157, boss=False, overlord=False, do_not_randomize=True, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Ostrich_HauntedGroveSprite', sprite_id=158, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(78,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FluteSprite', sprite_id=159, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Birds_HauntedGroveSprite', sprite_id=160, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(78,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FreezorSprite', sprite_id=161, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(38,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='KholdstareSprite', sprite_id=162, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(60,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='KholdstaresShellSprite', sprite_id=163, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FallingIceSprite', sprite_id=164, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(60,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BlueZazakSprite', sprite_id=165, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(40,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RedZazakSprite', sprite_id=166, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(40,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='StalfosSprite', sprite_id=167, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BomberFlyingCreaturesFromDarkworldSprite', sprite_id=168, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BomberFlyingCreaturesFromDarkworld2Sprite', sprite_id=169, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(210, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='PikitSprite', sprite_id=170, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MaidenSprite', sprite_id=171, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AppleSprite', sprite_id=172, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='LostOldManSprite', sprite_id=173, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(70,), subgroup_1=(73,), subgroup_2=(28,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='DownPipeSprite', sprite_id=174, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='UpPipeSprite', sprite_id=175, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RightPipeSprite', sprite_id=176, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='LeftPipeSprite', sprite_id=177, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GoodBee_AgainMaybeSprite', sprite_id=178, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='HylianInscriptionSprite', sprite_id=179, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ThiefsChestSprite', sprite_id=180, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(21,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BombSalesmanSprite', sprite_id=181, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(77,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='KikiSprite', sprite_id=182, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(25,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MaidenInBlindDungeonSprite', sprite_id=183, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MimicSprite', sprite_id=184, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FeudingFriendsOnDeathMountainSprite', sprite_id=185, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(20,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='WhirlpoolSprite', sprite_id=186, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(255, 274, 287)), + EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(271, 272)), + EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(272,)), + EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(79,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(280,)), + EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(291, 292)), + EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(291, 292)), + EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(14,), subgroup_1=(), subgroup_2=(74,), subgroup_3=(80,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(293,)), + EnemySpriteRequirementData(sprite_name='SalesmanChestgameGuy300RupeeGiverGuyChestGameThiefSprite', sprite_id=187, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(21,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=(286,)), + EnemySpriteRequirementData(sprite_name='DrunkInTheInnSprite', sprite_id=188, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(79,), subgroup_1=(77,), subgroup_2=(74,), subgroup_3=(80,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Vitreous_LargeEyeballSprite', sprite_id=189, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(61,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Vitreous_SmallEyeballSprite', sprite_id=190, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(61,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='VitreousLightningSprite', sprite_id=191, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(61,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CatFish_QuakeMedallionSprite', sprite_id=192, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(24,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AgahnimTeleportingZeldaToDarkworldSprite', sprite_id=193, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(85,), subgroup_1=(61,), subgroup_2=(66,), subgroup_3=(67,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BouldersSprite', sprite_id=194, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='Gibo_FloatingBlobSprite', sprite_id=195, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(40,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ThiefSprite', sprite_id=196, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(14, 21), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MedusaSprite', sprite_id=197, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FourWayFireballSpittersSprite', sprite_id=198, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='HokkuBokkuSprite', sprite_id=199, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BigFairyWhoHealsYouSprite', sprite_id=200, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(57,), subgroup_3=(54,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='TektiteSprite', sprite_id=201, boss=False, overlord=False, do_not_randomize=False, killable=True, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ChainChompSprite', sprite_id=202, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='TrinexxSprite', sprite_id=203, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(64,), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='AnotherPartOfTrinexxSprite', sprite_id=204, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(64,), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='YetAnotherPartOfTrinexxSprite', sprite_id=205, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(64,), subgroup_1=(), subgroup_2=(), subgroup_3=(63,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BlindTheThiefSprite', sprite_id=206, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(44,), subgroup_2=(59,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SwamolaSprite', sprite_id=207, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=True, is_object=False, absorbable=False, is_water_sprite=True, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(25,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='LynelSprite', sprite_id=208, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(20,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BunnyBeamSprite', sprite_id=209, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FloppingFishSprite', sprite_id=210, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='StalSprite', sprite_id=211, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='LandmineSprite', sprite_id=212, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(11, 22, 25, 30, 38, 39, 54, 63, 66, 64, 70, 73, 75, 78, 85, 87, 95, 101, 106, 116, 118, 125, 127, 131, 132, 133, 140, 141, 146, 149, 152, 155, 156, 157, 158, 160, 170, 175, 179, 186, 187, 188, 198, 203, 206, 208, 210, 213, 216, 220, 223, 228, 231, 238, 249, 253, 268), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='DiggingGameProprietorSprite', sprite_id=213, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(42,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GanonSprite', sprite_id=214, boss=True, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(33,), subgroup_1=(65,), subgroup_2=(69,), subgroup_3=(51,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CopyOfGanon_ExceptInvincibleSprite', sprite_id=215, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='HeartSprite', sprite_id=216, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='GreenRupeeSprite', sprite_id=217, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BlueRupeeSprite', sprite_id=218, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='RedRupeeSprite', sprite_id=219, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BombRefill1Sprite', sprite_id=220, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BombRefill4Sprite', sprite_id=221, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BombRefill8Sprite', sprite_id=222, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='SmallMagicRefillSprite', sprite_id=223, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FullMagicRefillSprite', sprite_id=224, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ArrowRefill5Sprite', sprite_id=225, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ArrowRefill10Sprite', sprite_id=226, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FairySprite', sprite_id=227, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='KeySprite', sprite_id=228, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=True, cannot_have_key=True, is_object=False, absorbable=True, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BigKeySprite', sprite_id=229, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='ShieldEaterSprite', sprite_id=230, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(27,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MushroomSprite', sprite_id=231, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='FakeMasterSwordSprite', sprite_id=232, boss=False, overlord=False, do_not_randomize=False, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(17,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MagicShopDude_HisItemsIncludingTheMagicPowderSprite', sprite_id=233, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=True, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(75,), subgroup_1=(), subgroup_2=(), subgroup_3=(90,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='HeartContainerSprite', sprite_id=234, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='HeartPieceSprite', sprite_id=235, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='BushesSprite', sprite_id=236, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatformSprite', sprite_id=237, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(39,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MantleSprite', sprite_id=238, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(93,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatform_Unused1Sprite', sprite_id=239, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatform_Unused2Sprite', sprite_id=240, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='CaneOfSomariaPlatform_Unused3Sprite', sprite_id=241, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='MedallionTabletSprite', sprite_id=242, boss=False, overlord=False, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=True, absorbable=False, is_water_sprite=False, is_enemy_sprite=False, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(18,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OW_OL_FallingRocks', sprite_id=244, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(16,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_CanonBalls_EP4Walls', sprite_id=258, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_CanonBalls_EPEntrance', sprite_id=259, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(46,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_StalfosHeadTrap', sprite_id=261, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_BombDrop_RopeTrap', sprite_id=262, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(28, 36), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_MovingFloor', sprite_id=263, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_SlimeDropper', sprite_id=264, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_Wallmaster', sprite_id=265, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(35,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_FloorDrop_Square', sprite_id=266, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_FloorDrop_Path', sprite_id=267, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_RightEvil_PirogusuSpawner', sprite_id=272, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_LeftEvil_PirogusuSpawner', sprite_id=273, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_DownEvil_PirogusuSpawner', sprite_id=274, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_UpEvil_PirogusuSpawner', sprite_id=275, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(34,), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_FlyingFloorTileTrap', sprite_id=276, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(82,), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_WizzrobeSpawner', sprite_id=277, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=False, never_use_overworld=False, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(37, 41), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_BlackSpawn_Zoro_BombHole', sprite_id=278, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(32,), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_4Skull_Trap_Pot', sprite_id=279, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_Stalfos_Spawn_Trap_EP', sprite_id=280, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(31,), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_ArmosKnight_Trigger', sprite_id=281, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), + EnemySpriteRequirementData(sprite_name='OL_BombDrop_BombTrap', sprite_id=282, boss=False, overlord=True, do_not_randomize=True, killable=False, npc=False, never_use_dungeon=True, never_use_overworld=True, cannot_have_key=False, is_object=False, absorbable=False, is_water_sprite=False, is_enemy_sprite=True, group_ids=(), subgroup_0=(), subgroup_1=(), subgroup_2=(), subgroup_3=(), parameters=None, special_glitched=False, excluded_rooms=(), dont_randomize_rooms=(), spawnable_rooms=()), +) diff --git a/worlds/alttp/enemizer_data/overworld_enemy_metadata.py b/worlds/alttp/enemizer_data/overworld_enemy_metadata.py new file mode 100644 index 0000000000..2c4582ae4c --- /dev/null +++ b/worlds/alttp/enemizer_data/overworld_enemy_metadata.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +from typing import NamedTuple, Optional + + +class OverworldGroupRequirementData(NamedTuple): + group_id: Optional[int] + subgroup_0: Optional[int] + subgroup_1: Optional[int] + subgroup_2: Optional[int] + subgroup_3: Optional[int] + areas: tuple[int, ...] + +AREA_IDS = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207) +DO_NOT_RANDOMIZE_AREA_IDS = frozenset((1, + 4, + 6, + 8, + 9, + 11, + 12, + 13, + 14, + 25, + 28, + 31, + 32, + 33, + 35, + 36, + 38, + 39, + 49, + 54, + 56, + 57, + 61, + 62, + 65, + 68, + 70, + 72, + 73, + 75, + 76, + 77, + 78, + 89, + 92, + 95, + 96, + 97, + 99, + 100, + 102, + 103, + 113, + 118, + 120, + 121, + 125, + 126, + 42, + 106, + 130, + 131, + 132, + 133, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 141, + 142, + 143, + 186, + 250, + 145, + 148, + 150, + 152, + 153, + 155, + 156, + 158, + 169, + 172, + 175, + 176, + 177, + 179, + 180, + 182, + 183, + 193, + 198, + 200, + 274, + 275, + 276, + 277, + 278, + 279, + 281, + 288)) + +FORCED_GROUP_REQUIREMENTS = ( + OverworldGroupRequirementData(group_id=7, subgroup_0=None, subgroup_1=None, subgroup_2=74, subgroup_3=None, areas=(2,)), + OverworldGroupRequirementData(group_id=16, subgroup_0=None, subgroup_1=None, subgroup_2=18, subgroup_3=16, areas=(3, 147)), + OverworldGroupRequirementData(group_id=7, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=17, areas=(10, 154)), + OverworldGroupRequirementData(group_id=4, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(15, 159)), + OverworldGroupRequirementData(group_id=3, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=14, areas=(20, 164)), + OverworldGroupRequirementData(group_id=1, subgroup_0=None, subgroup_1=None, subgroup_2=76, subgroup_3=63, areas=(27, 171)), + OverworldGroupRequirementData(group_id=6, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(34, 40, 178, 184)), + OverworldGroupRequirementData(group_id=8, subgroup_0=None, subgroup_1=None, subgroup_2=18, subgroup_3=None, areas=(48, 192)), + OverworldGroupRequirementData(group_id=10, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(58, 202)), + OverworldGroupRequirementData(group_id=22, subgroup_0=None, subgroup_1=None, subgroup_2=24, subgroup_3=None, areas=(79, 223)), + OverworldGroupRequirementData(group_id=21, subgroup_0=21, subgroup_1=None, subgroup_2=None, subgroup_3=21, areas=(98, 242)), + OverworldGroupRequirementData(group_id=27, subgroup_0=None, subgroup_1=42, subgroup_2=None, subgroup_3=None, areas=(104, 248)), + OverworldGroupRequirementData(group_id=13, subgroup_0=None, subgroup_1=None, subgroup_2=76, subgroup_3=None, areas=(22, 166)), + OverworldGroupRequirementData(group_id=29, subgroup_0=None, subgroup_1=77, subgroup_2=None, subgroup_3=21, areas=(105, 249)), + OverworldGroupRequirementData(group_id=15, subgroup_0=None, subgroup_1=None, subgroup_2=78, subgroup_3=None, areas=(42, 186)), + OverworldGroupRequirementData(group_id=17, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=76, areas=(106, 250)), + OverworldGroupRequirementData(group_id=12, subgroup_0=None, subgroup_1=None, subgroup_2=55, subgroup_3=54, areas=(128, 272)), + OverworldGroupRequirementData(group_id=14, subgroup_0=None, subgroup_1=None, subgroup_2=12, subgroup_3=68, areas=(129, 273)), + OverworldGroupRequirementData(group_id=26, subgroup_0=15, subgroup_1=None, subgroup_2=None, subgroup_3=None, areas=(146,)), + OverworldGroupRequirementData(group_id=23, subgroup_0=None, subgroup_1=None, subgroup_2=None, subgroup_3=25, areas=(94, 238)), +) diff --git a/worlds/alttp/enemizer_data/pot_shuffle_data.py b/worlds/alttp/enemizer_data/pot_shuffle_data.py new file mode 100644 index 0000000000..5b6ae17c99 --- /dev/null +++ b/worlds/alttp/enemizer_data/pot_shuffle_data.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +from typing import NamedTuple + + +class PotDataRecord(NamedTuple): + x: int + y: int + reserved: int + + +class PotRoomDataRecord(NamedTuple): + room_id: int + pots: tuple[PotDataRecord, ...] + items: tuple[int, ...] + +POT_ROOMS = ( + PotRoomDataRecord(room_id=4, pots=(PotDataRecord(x=162, y=25, reserved=0), PotDataRecord(x=152, y=25, reserved=0), PotDataRecord(x=152, y=22, reserved=0), PotDataRecord(x=162, y=22, reserved=0), PotDataRecord(x=240, y=19, reserved=0), PotDataRecord(x=204, y=19, reserved=0),), items=(10, 10)), + PotRoomDataRecord(room_id=9, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=12, y=12, reserved=0),), items=(1, 11, 136)), + PotRoomDataRecord(room_id=10, pots=(PotDataRecord(x=204, y=11, reserved=0), PotDataRecord(x=156, y=17, reserved=0), PotDataRecord(x=96, y=8, reserved=0), PotDataRecord(x=100, y=7, reserved=0), PotDataRecord(x=160, y=17, reserved=0), PotDataRecord(x=104, y=8, reserved=0), PotDataRecord(x=100, y=9, reserved=0),), items=(11, 11, 136)), + PotRoomDataRecord(room_id=17, pots=(PotDataRecord(x=152, y=19, reserved=0), PotDataRecord(x=152, y=15, reserved=0), PotDataRecord(x=144, y=15, reserved=0), PotDataRecord(x=10, y=15, reserved=0), PotDataRecord(x=144, y=19, reserved=0), PotDataRecord(x=160, y=19, reserved=0),), items=(11, 11, 11, 11)), + PotRoomDataRecord(room_id=21, pots=(PotDataRecord(x=96, y=4, reserved=0), PotDataRecord(x=100, y=4, reserved=0), PotDataRecord(x=104, y=4, reserved=0), PotDataRecord(x=108, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=12, y=6, reserved=0), PotDataRecord(x=16, y=6, reserved=0), PotDataRecord(x=20, y=6, reserved=0), PotDataRecord(x=70, y=11, reserved=0),), items=(1, 7, 9, 9, 10, 11, 12, 12, 13)), + PotRoomDataRecord(room_id=22, pots=(PotDataRecord(x=188, y=3, reserved=0), PotDataRecord(x=192, y=3, reserved=0), PotDataRecord(x=188, y=4, reserved=0), PotDataRecord(x=192, y=4, reserved=0), PotDataRecord(x=188, y=5, reserved=0), PotDataRecord(x=192, y=5, reserved=0), PotDataRecord(x=188, y=6, reserved=0), PotDataRecord(x=192, y=6, reserved=0), PotDataRecord(x=240, y=19, reserved=0),), items=(8, 9, 9, 10, 10, 11, 11, 12, 12)), + PotRoomDataRecord(room_id=26, pots=(PotDataRecord(x=232, y=19, reserved=0), PotDataRecord(x=212, y=19, reserved=0), PotDataRecord(x=28, y=5, reserved=0), PotDataRecord(x=32, y=5, reserved=0), PotDataRecord(x=28, y=27, reserved=0), PotDataRecord(x=32, y=27, reserved=0),), items=(10, 10, 10, 10)), + PotRoomDataRecord(room_id=33, pots=(PotDataRecord(x=100, y=28, reserved=0), PotDataRecord(x=168, y=24, reserved=0), PotDataRecord(x=48, y=28, reserved=0), PotDataRecord(x=82, y=28, reserved=0), PotDataRecord(x=160, y=20, reserved=0), PotDataRecord(x=104, y=28, reserved=0),), items=(11, 12, 12)), + PotRoomDataRecord(room_id=35, pots=(PotDataRecord(x=86, y=26, reserved=0), PotDataRecord(x=90, y=26, reserved=0), PotDataRecord(x=94, y=26, reserved=0), PotDataRecord(x=98, y=26, reserved=0), PotDataRecord(x=102, y=26, reserved=0),), items=(1, 10, 11)), + PotRoomDataRecord(room_id=36, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=12, y=12, reserved=0), PotDataRecord(x=48, y=12, reserved=0),), items=(1, 11, 12, 7)), + PotRoomDataRecord(room_id=38, pots=(PotDataRecord(x=28, y=4, reserved=0), PotDataRecord(x=12, y=8, reserved=0), PotDataRecord(x=150, y=19, reserved=2), PotDataRecord(x=22, y=26, reserved=2), PotDataRecord(x=220, y=26, reserved=0),), items=(7, 9, 10, 12, 136)), + PotRoomDataRecord(room_id=39, pots=(PotDataRecord(x=214, y=19, reserved=0), PotDataRecord(x=214, y=20, reserved=0), PotDataRecord(x=166, y=20, reserved=0), PotDataRecord(x=214, y=21, reserved=0), PotDataRecord(x=40, y=28, reserved=0), PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=80, y=28, reserved=0), PotDataRecord(x=84, y=28, reserved=0), PotDataRecord(x=102, y=17, reserved=0), PotDataRecord(x=98, y=17, reserved=0), PotDataRecord(x=106, y=17, reserved=0), PotDataRecord(x=166, y=21, reserved=0), PotDataRecord(x=166, y=19, reserved=0), PotDataRecord(x=92, y=12, reserved=0), PotDataRecord(x=160, y=12, reserved=0),), items=(1, 1, 10, 11, 7, 7)), + PotRoomDataRecord(room_id=43, pots=(PotDataRecord(x=16, y=5, reserved=2), PotDataRecord(x=44, y=5, reserved=2), PotDataRecord(x=16, y=6, reserved=2), PotDataRecord(x=44, y=6, reserved=2), PotDataRecord(x=16, y=7, reserved=2), PotDataRecord(x=44, y=7, reserved=2), PotDataRecord(x=146, y=21, reserved=0), PotDataRecord(x=170, y=21, reserved=0), PotDataRecord(x=146, y=22, reserved=0), PotDataRecord(x=170, y=22, reserved=0),), items=(9, 9, 10, 10, 10, 10, 11, 11, 11, 136)), + PotRoomDataRecord(room_id=47, pots=(PotDataRecord(x=28, y=7, reserved=0), PotDataRecord(x=32, y=7, reserved=0), PotDataRecord(x=28, y=9, reserved=0), PotDataRecord(x=32, y=9, reserved=0), PotDataRecord(x=172, y=19, reserved=0), PotDataRecord(x=180, y=19, reserved=0), PotDataRecord(x=104, y=27, reserved=0), PotDataRecord(x=104, y=28, reserved=0),), items=(7, 7, 7, 7, 11, 11, 11, 11)), + PotRoomDataRecord(room_id=53, pots=(PotDataRecord(x=60, y=6, reserved=1), PotDataRecord(x=20, y=8, reserved=0), PotDataRecord(x=24, y=8, reserved=0), PotDataRecord(x=28, y=8, reserved=0), PotDataRecord(x=32, y=8, reserved=0), PotDataRecord(x=36, y=8, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=76, y=23, reserved=1), PotDataRecord(x=88, y=23, reserved=1), PotDataRecord(x=100, y=27, reserved=1), PotDataRecord(x=242, y=28, reserved=1), PotDataRecord(x=240, y=22, reserved=1), PotDataRecord(x=76, y=28, reserved=1),), items=(7, 7, 7, 7, 7, 8, 11)), + PotRoomDataRecord(room_id=54, pots=(PotDataRecord(x=108, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=10, y=16, reserved=0), PotDataRecord(x=114, y=16, reserved=0),), items=(8, 10, 11)), + PotRoomDataRecord(room_id=55, pots=(PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=60, y=6, reserved=0),), items=(8,)), + PotRoomDataRecord(room_id=56, pots=(PotDataRecord(x=164, y=12, reserved=0), PotDataRecord(x=164, y=13, reserved=0), PotDataRecord(x=164, y=18, reserved=0), PotDataRecord(x=164, y=19, reserved=0),), items=(8, 7, 10, 10)), + PotRoomDataRecord(room_id=57, pots=(PotDataRecord(x=12, y=20, reserved=0), PotDataRecord(x=100, y=22, reserved=0), PotDataRecord(x=100, y=26, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(9, 9, 11, 12)), + PotRoomDataRecord(room_id=60, pots=(PotDataRecord(x=24, y=8, reserved=0), PotDataRecord(x=64, y=12, reserved=0), PotDataRecord(x=20, y=14, reserved=0), PotDataRecord(x=68, y=18, reserved=0), PotDataRecord(x=96, y=19, reserved=0), PotDataRecord(x=64, y=20, reserved=0), PotDataRecord(x=64, y=26, reserved=0),), items=(1, 7, 7, 7, 7, 11, 12)), + PotRoomDataRecord(room_id=61, pots=(PotDataRecord(x=76, y=12, reserved=0), PotDataRecord(x=112, y=12, reserved=0), PotDataRecord(x=24, y=22, reserved=0), PotDataRecord(x=40, y=22, reserved=0), PotDataRecord(x=32, y=24, reserved=0), PotDataRecord(x=20, y=26, reserved=0), PotDataRecord(x=36, y=26, reserved=0),), items=(9, 7, 10, 10, 11, 11, 13)), + PotRoomDataRecord(room_id=62, pots=(PotDataRecord(x=96, y=6, reserved=0), PotDataRecord(x=100, y=6, reserved=0), PotDataRecord(x=88, y=10, reserved=0), PotDataRecord(x=92, y=10, reserved=0),), items=(10, 11, 12, 12)), + PotRoomDataRecord(room_id=63, pots=(PotDataRecord(x=12, y=25, reserved=0), PotDataRecord(x=20, y=25, reserved=0), PotDataRecord(x=12, y=26, reserved=0), PotDataRecord(x=20, y=26, reserved=0), PotDataRecord(x=12, y=27, reserved=0), PotDataRecord(x=20, y=27, reserved=0), PotDataRecord(x=28, y=23, reserved=0),), items=(1, 1, 8, 10, 10, 11, 136)), + PotRoomDataRecord(room_id=65, pots=(PotDataRecord(x=100, y=10, reserved=0), PotDataRecord(x=52, y=15, reserved=0), PotDataRecord(x=52, y=16, reserved=0), PotDataRecord(x=148, y=22, reserved=0),), items=(1, 11, 12, 12)), + PotRoomDataRecord(room_id=67, pots=(PotDataRecord(x=112, y=28, reserved=1), PotDataRecord(x=76, y=28, reserved=1), PotDataRecord(x=76, y=20, reserved=1), PotDataRecord(x=66, y=4, reserved=0), PotDataRecord(x=78, y=4, reserved=0), PotDataRecord(x=66, y=9, reserved=0), PotDataRecord(x=78, y=9, reserved=0), PotDataRecord(x=112, y=20, reserved=1),), items=(8, 9, 11, 11, 12)), + PotRoomDataRecord(room_id=69, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=108, y=11, reserved=0), PotDataRecord(x=48, y=12, reserved=0), PotDataRecord(x=220, y=16, reserved=0), PotDataRecord(x=236, y=16, reserved=0),), items=(9, 9, 11, 11, 12)), + PotRoomDataRecord(room_id=73, pots=(PotDataRecord(x=156, y=27, reserved=0), PotDataRecord(x=172, y=24, reserved=0), PotDataRecord(x=172, y=23, reserved=0), PotDataRecord(x=144, y=20, reserved=0), PotDataRecord(x=104, y=15, reserved=0), PotDataRecord(x=104, y=16, reserved=0), PotDataRecord(x=144, y=19, reserved=0), PotDataRecord(x=172, y=20, reserved=0), PotDataRecord(x=144, y=27, reserved=0), PotDataRecord(x=172, y=28, reserved=0), PotDataRecord(x=160, y=27, reserved=0),), items=(11, 11, 12, 12, 12, 12)), + PotRoomDataRecord(room_id=78, pots=(PotDataRecord(x=48, y=10, reserved=2), PotDataRecord(x=140, y=11, reserved=2), PotDataRecord(x=28, y=12, reserved=2), PotDataRecord(x=112, y=12, reserved=0),), items=(136, 11, 12)), + PotRoomDataRecord(room_id=83, pots=(PotDataRecord(x=92, y=11, reserved=0), PotDataRecord(x=96, y=11, reserved=0), PotDataRecord(x=100, y=11, reserved=0), PotDataRecord(x=104, y=11, reserved=0),), items=(8, 11, 11, 12)), + PotRoomDataRecord(room_id=84, pots=(PotDataRecord(x=186, y=25, reserved=0), PotDataRecord(x=186, y=26, reserved=0), PotDataRecord(x=186, y=27, reserved=0), PotDataRecord(x=186, y=28, reserved=0),), items=(7, 11, 11, 11)), + PotRoomDataRecord(room_id=86, pots=(PotDataRecord(x=100, y=6, reserved=1), PotDataRecord(x=96, y=10, reserved=1), PotDataRecord(x=92, y=10, reserved=1), PotDataRecord(x=48, y=20, reserved=1), PotDataRecord(x=20, y=6, reserved=0), PotDataRecord(x=40, y=6, reserved=0), PotDataRecord(x=24, y=7, reserved=0), PotDataRecord(x=36, y=7, reserved=0), PotDataRecord(x=12, y=8, reserved=0), PotDataRecord(x=48, y=8, reserved=0), PotDataRecord(x=24, y=9, reserved=0), PotDataRecord(x=36, y=9, reserved=0), PotDataRecord(x=20, y=10, reserved=0), PotDataRecord(x=40, y=10, reserved=0), PotDataRecord(x=12, y=20, reserved=1),), items=(7, 7, 11, 11, 8, 12, 12, 12, 12, 12, 12)), + PotRoomDataRecord(room_id=87, pots=(PotDataRecord(x=92, y=7, reserved=0), PotDataRecord(x=12, y=20, reserved=2), PotDataRecord(x=92, y=23, reserved=0), PotDataRecord(x=100, y=23, reserved=0), PotDataRecord(x=84, y=25, reserved=0), PotDataRecord(x=76, y=27, reserved=0), PotDataRecord(x=48, y=20, reserved=2), PotDataRecord(x=30, y=22, reserved=2),), items=(7, 10, 11, 12, 12, 12, 13, 136)), + PotRoomDataRecord(room_id=88, pots=(PotDataRecord(x=96, y=9, reserved=0), PotDataRecord(x=92, y=8, reserved=0), PotDataRecord(x=108, y=8, reserved=0), PotDataRecord(x=108, y=6, reserved=0), PotDataRecord(x=104, y=5, reserved=0), PotDataRecord(x=92, y=6, reserved=0), PotDataRecord(x=12, y=12, reserved=0), PotDataRecord(x=16, y=7, reserved=0), PotDataRecord(x=96, y=5, reserved=0), PotDataRecord(x=100, y=5, reserved=0), PotDataRecord(x=12, y=7, reserved=0), PotDataRecord(x=92, y=7, reserved=0), PotDataRecord(x=108, y=7, reserved=0), PotDataRecord(x=16, y=8, reserved=0), PotDataRecord(x=100, y=9, reserved=0), PotDataRecord(x=104, y=9, reserved=0),), items=(10, 10, 11, 11, 12, 12, 12, 12)), + PotRoomDataRecord(room_id=91, pots=(PotDataRecord(x=218, y=37, reserved=0), PotDataRecord(x=222, y=37, reserved=0), PotDataRecord(x=226, y=37, reserved=0),), items=(136,)), + PotRoomDataRecord(room_id=92, pots=(PotDataRecord(x=228, y=25, reserved=0), PotDataRecord(x=104, y=24, reserved=0), PotDataRecord(x=228, y=22, reserved=0), PotDataRecord(x=216, y=25, reserved=0), PotDataRecord(x=84, y=24, reserved=0), PotDataRecord(x=216, y=22, reserved=0), PotDataRecord(x=94, y=22, reserved=0), PotDataRecord(x=94, y=26, reserved=0),), items=(10, 13)), + PotRoomDataRecord(room_id=93, pots=(PotDataRecord(x=16, y=5, reserved=0), PotDataRecord(x=44, y=5, reserved=0), PotDataRecord(x=16, y=11, reserved=0), PotDataRecord(x=44, y=11, reserved=0), PotDataRecord(x=12, y=20, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=12, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(1, 7, 9, 9, 10, 10, 10, 12)), + PotRoomDataRecord(room_id=94, pots=(PotDataRecord(x=92, y=4, reserved=0), PotDataRecord(x=96, y=4, reserved=0), PotDataRecord(x=76, y=8, reserved=0), PotDataRecord(x=112, y=8, reserved=0),), items=(11, 11, 12, 12)), + PotRoomDataRecord(room_id=99, pots=(PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=12, y=8, reserved=0), PotDataRecord(x=48, y=12, reserved=0), PotDataRecord(x=48, y=8, reserved=0), PotDataRecord(x=12, y=12, reserved=0),), items=(8, 11)), + PotRoomDataRecord(room_id=100, pots=(PotDataRecord(x=12, y=22, reserved=0), PotDataRecord(x=16, y=22, reserved=0), PotDataRecord(x=20, y=22, reserved=0), PotDataRecord(x=36, y=28, reserved=0), PotDataRecord(x=40, y=28, reserved=0), PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(10, 10, 10, 10, 12, 12, 136)), + PotRoomDataRecord(room_id=102, pots=(PotDataRecord(x=48, y=37, reserved=0), PotDataRecord(x=52, y=37, reserved=0), PotDataRecord(x=56, y=37, reserved=0), PotDataRecord(x=84, y=5, reserved=0), PotDataRecord(x=104, y=5, reserved=0), PotDataRecord(x=48, y=38, reserved=0), PotDataRecord(x=52, y=38, reserved=0), PotDataRecord(x=56, y=38, reserved=0), PotDataRecord(x=84, y=6, reserved=0), PotDataRecord(x=104, y=6, reserved=0),), items=(7, 7, 9, 9, 9, 10, 10, 10, 11, 11)), + PotRoomDataRecord(room_id=103, pots=(PotDataRecord(x=22, y=26, reserved=0), PotDataRecord(x=18, y=22, reserved=0), PotDataRecord(x=92, y=9, reserved=0), PotDataRecord(x=84, y=28, reserved=0), PotDataRecord(x=12, y=7, reserved=0), PotDataRecord(x=48, y=7, reserved=0), PotDataRecord(x=96, y=19, reserved=0), PotDataRecord(x=74, y=20, reserved=0), PotDataRecord(x=18, y=23, reserved=0), PotDataRecord(x=18, y=26, reserved=0), PotDataRecord(x=104, y=28, reserved=0),), items=(9, 11, 11, 11, 12, 12, 12)), + PotRoomDataRecord(room_id=104, pots=(PotDataRecord(x=84, y=14, reserved=0), PotDataRecord(x=84, y=13, reserved=0), PotDataRecord(x=88, y=12, reserved=0), PotDataRecord(x=88, y=6, reserved=0), PotDataRecord(x=88, y=5, reserved=0), PotDataRecord(x=88, y=4, reserved=0), PotDataRecord(x=64, y=17, reserved=0), PotDataRecord(x=64, y=15, reserved=0), PotDataRecord(x=64, y=7, reserved=0), PotDataRecord(x=88, y=7, reserved=0), PotDataRecord(x=64, y=16, reserved=0), PotDataRecord(x=64, y=24, reserved=0), PotDataRecord(x=64, y=25, reserved=0),), items=(11, 11, 11, 12, 12)), + PotRoomDataRecord(room_id=115, pots=(PotDataRecord(x=154, y=21, reserved=0), PotDataRecord(x=158, y=21, reserved=0), PotDataRecord(x=20, y=23, reserved=0), PotDataRecord(x=36, y=23, reserved=0), PotDataRecord(x=144, y=24, reserved=0), PotDataRecord(x=168, y=24, reserved=0), PotDataRecord(x=20, y=26, reserved=0), PotDataRecord(x=36, y=26, reserved=0), PotDataRecord(x=154, y=27, reserved=0), PotDataRecord(x=158, y=27, reserved=0),), items=(1, 1, 11, 11, 7, 7, 9, 9, 12, 136)), + PotRoomDataRecord(room_id=116, pots=(PotDataRecord(x=30, y=5, reserved=0), PotDataRecord(x=62, y=5, reserved=0), PotDataRecord(x=94, y=5, reserved=0), PotDataRecord(x=14, y=11, reserved=0), PotDataRecord(x=46, y=11, reserved=0), PotDataRecord(x=78, y=11, reserved=0), PotDataRecord(x=110, y=11, reserved=0),), items=(9, 9, 11, 11, 12, 12, 136)), + PotRoomDataRecord(room_id=117, pots=(PotDataRecord(x=148, y=22, reserved=0), PotDataRecord(x=160, y=22, reserved=0), PotDataRecord(x=172, y=22, reserved=0),), items=(9, 11, 12)), + PotRoomDataRecord(room_id=123, pots=(PotDataRecord(x=48, y=10, reserved=0), PotDataRecord(x=88, y=10, reserved=0), PotDataRecord(x=76, y=7, reserved=0), PotDataRecord(x=60, y=4, reserved=0), PotDataRecord(x=64, y=4, reserved=0),), items=(11, 8)), + PotRoomDataRecord(room_id=124, pots=(PotDataRecord(x=36, y=21, reserved=0), PotDataRecord(x=24, y=11, reserved=0), PotDataRecord(x=28, y=4, reserved=0), PotDataRecord(x=32, y=4, reserved=0),), items=(11, 11)), + PotRoomDataRecord(room_id=125, pots=(PotDataRecord(x=44, y=12, reserved=0), PotDataRecord(x=44, y=6, reserved=0), PotDataRecord(x=112, y=6, reserved=0), PotDataRecord(x=108, y=20, reserved=0), PotDataRecord(x=114, y=20, reserved=0), PotDataRecord(x=76, y=28, reserved=0),), items=(9, 10, 10, 11)), + PotRoomDataRecord(room_id=126, pots=(PotDataRecord(x=86, y=15, reserved=0), PotDataRecord(x=82, y=26, reserved=0), PotDataRecord(x=100, y=26, reserved=0), PotDataRecord(x=104, y=26, reserved=0),), items=(11, 12, 136)), + PotRoomDataRecord(room_id=130, pots=(PotDataRecord(x=50, y=5, reserved=0), PotDataRecord(x=50, y=10, reserved=0), PotDataRecord(x=76, y=50, reserved=0),), items=(11,)), + PotRoomDataRecord(room_id=131, pots=(PotDataRecord(x=76, y=4, reserved=0), PotDataRecord(x=80, y=4, reserved=0), PotDataRecord(x=76, y=28, reserved=0), PotDataRecord(x=80, y=28, reserved=0),), items=(1, 7, 9, 9)), + PotRoomDataRecord(room_id=132, pots=(PotDataRecord(x=64, y=17, reserved=0), PotDataRecord(x=60, y=17, reserved=0), PotDataRecord(x=80, y=14, reserved=0), PotDataRecord(x=44, y=14, reserved=0), PotDataRecord(x=100, y=6, reserved=0), PotDataRecord(x=24, y=6, reserved=0), PotDataRecord(x=24, y=7, reserved=0), PotDataRecord(x=100, y=7, reserved=0),), items=(9, 9)), + PotRoomDataRecord(room_id=135, pots=(PotDataRecord(x=12, y=11, reserved=0), PotDataRecord(x=76, y=20, reserved=0), PotDataRecord(x=112, y=20, reserved=0), PotDataRecord(x=16, y=12, reserved=0), PotDataRecord(x=40, y=12, reserved=0), PotDataRecord(x=32, y=12, reserved=0), PotDataRecord(x=24, y=12, reserved=0), PotDataRecord(x=16, y=11, reserved=0),), items=(12, 13)), + PotRoomDataRecord(room_id=139, pots=(PotDataRecord(x=76, y=20, reserved=0), PotDataRecord(x=76, y=12, reserved=1), PotDataRecord(x=32, y=23, reserved=1), PotDataRecord(x=28, y=23, reserved=1), PotDataRecord(x=112, y=12, reserved=1), PotDataRecord(x=32, y=9, reserved=1), PotDataRecord(x=76, y=28, reserved=0),), items=(8, 11, 12)), + PotRoomDataRecord(room_id=140, pots=(PotDataRecord(x=76, y=12, reserved=2), PotDataRecord(x=112, y=12, reserved=2), PotDataRecord(x=76, y=20, reserved=0), PotDataRecord(x=92, y=20, reserved=0), PotDataRecord(x=100, y=21, reserved=0), PotDataRecord(x=104, y=26, reserved=0), PotDataRecord(x=88, y=27, reserved=0),), items=(9, 10, 10, 10, 10, 12, 136)), + PotRoomDataRecord(room_id=145, pots=(PotDataRecord(x=84, y=4, reserved=0), PotDataRecord(x=104, y=4, reserved=0),), items=(11, 12)), + PotRoomDataRecord(room_id=150, pots=(PotDataRecord(x=14, y=18, reserved=0), PotDataRecord(x=32, y=5, reserved=0), PotDataRecord(x=32, y=17, reserved=0), PotDataRecord(x=32, y=24, reserved=0), PotDataRecord(x=76, y=21, reserved=0), PotDataRecord(x=112, y=21, reserved=0), PotDataRecord(x=14, y=24, reserved=0),), items=(11, 12, 12, 13)), + PotRoomDataRecord(room_id=155, pots=(PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=48, y=12, reserved=0),), items=(8, 12)), + PotRoomDataRecord(room_id=157, pots=(PotDataRecord(x=32, y=7, reserved=0), PotDataRecord(x=40, y=9, reserved=0), PotDataRecord(x=76, y=4, reserved=0), PotDataRecord(x=84, y=4, reserved=0),), items=(10, 12)), + PotRoomDataRecord(room_id=159, pots=(PotDataRecord(x=138, y=20, reserved=0), PotDataRecord(x=138, y=19, reserved=0), PotDataRecord(x=178, y=19, reserved=0), PotDataRecord(x=40, y=21, reserved=0), PotDataRecord(x=138, y=21, reserved=0), PotDataRecord(x=20, y=27, reserved=0), PotDataRecord(x=138, y=27, reserved=0), PotDataRecord(x=178, y=28, reserved=0), PotDataRecord(x=178, y=21, reserved=0), PotDataRecord(x=178, y=20, reserved=0), PotDataRecord(x=40, y=27, reserved=0), PotDataRecord(x=178, y=27, reserved=0), PotDataRecord(x=178, y=26, reserved=0), PotDataRecord(x=138, y=28, reserved=0), PotDataRecord(x=138, y=26, reserved=0), PotDataRecord(x=20, y=21, reserved=0),), items=(8, 11, 11, 11, 11, 11, 136)), + PotRoomDataRecord(room_id=161, pots=(PotDataRecord(x=96, y=27, reserved=0), PotDataRecord(x=92, y=21, reserved=0), PotDataRecord(x=150, y=6, reserved=0), PotDataRecord(x=100, y=11, reserved=0), PotDataRecord(x=104, y=12, reserved=0), PotDataRecord(x=108, y=13, reserved=0), PotDataRecord(x=112, y=14, reserved=0), PotDataRecord(x=96, y=23, reserved=0), PotDataRecord(x=76, y=28, reserved=0), PotDataRecord(x=112, y=28, reserved=0),), items=(8, 11, 11, 11, 12, 12)), + PotRoomDataRecord(room_id=168, pots=(PotDataRecord(x=138, y=28, reserved=0), PotDataRecord(x=178, y=28, reserved=0), PotDataRecord(x=178, y=19, reserved=0), PotDataRecord(x=138, y=19, reserved=0), PotDataRecord(x=30, y=24, reserved=0),), items=(1, 11)), + PotRoomDataRecord(room_id=169, pots=(PotDataRecord(x=12, y=19, reserved=0), PotDataRecord(x=112, y=19, reserved=0), PotDataRecord(x=144, y=43, reserved=0), PotDataRecord(x=236, y=43, reserved=0), PotDataRecord(x=144, y=44, reserved=0), PotDataRecord(x=236, y=44, reserved=0), PotDataRecord(x=16, y=20, reserved=0), PotDataRecord(x=108, y=20, reserved=0),), items=(11, 11, 11, 9, 9, 9)), + PotRoomDataRecord(room_id=170, pots=(PotDataRecord(x=212, y=10, reserved=2), PotDataRecord(x=232, y=10, reserved=2), PotDataRecord(x=232, y=5, reserved=2), PotDataRecord(x=212, y=5, reserved=2), PotDataRecord(x=94, y=8, reserved=2), PotDataRecord(x=108, y=55, reserved=0), PotDataRecord(x=108, y=56, reserved=0), PotDataRecord(x=108, y=57, reserved=0),), items=(11, 11, 11, 11, 136)), + PotRoomDataRecord(room_id=176, pots=(PotDataRecord(x=20, y=27, reserved=0), PotDataRecord(x=24, y=24, reserved=0), PotDataRecord(x=44, y=25, reserved=0), PotDataRecord(x=20, y=21, reserved=0), PotDataRecord(x=28, y=21, reserved=0), PotDataRecord(x=32, y=21, reserved=0), PotDataRecord(x=40, y=21, reserved=0), PotDataRecord(x=16, y=23, reserved=0), PotDataRecord(x=44, y=23, reserved=0), PotDataRecord(x=36, y=24, reserved=0), PotDataRecord(x=16, y=25, reserved=0), PotDataRecord(x=28, y=27, reserved=0), PotDataRecord(x=40, y=27, reserved=0), PotDataRecord(x=32, y=27, reserved=0),), items=(1, 1, 7, 7, 9, 9, 10, 10, 11, 11)), + PotRoomDataRecord(room_id=179, pots=(PotDataRecord(x=12, y=20, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(8, 12, 136)), + PotRoomDataRecord(room_id=180, pots=(PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(11, 13)), + PotRoomDataRecord(room_id=181, pots=(PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=112, y=15, reserved=0), PotDataRecord(x=76, y=16, reserved=0), PotDataRecord(x=112, y=16, reserved=0), PotDataRecord(x=112, y=17, reserved=0), PotDataRecord(x=112, y=28, reserved=0),), items=(7, 10, 11, 11, 13, 136)), + PotRoomDataRecord(room_id=184, pots=(PotDataRecord(x=96, y=13, reserved=0), PotDataRecord(x=88, y=16, reserved=0), PotDataRecord(x=104, y=16, reserved=0),), items=(11, 11, 136)), + PotRoomDataRecord(room_id=185, pots=(PotDataRecord(x=92, y=18, reserved=0), PotDataRecord(x=96, y=18, reserved=0), PotDataRecord(x=104, y=18, reserved=0), PotDataRecord(x=108, y=18, reserved=0),), items=(1, 1, 7, 7)), + PotRoomDataRecord(room_id=186, pots=(PotDataRecord(x=100, y=8, reserved=0), PotDataRecord(x=88, y=8, reserved=0), PotDataRecord(x=94, y=4, reserved=0), PotDataRecord(x=76, y=6, reserved=0), PotDataRecord(x=112, y=6, reserved=0), PotDataRecord(x=76, y=10, reserved=0), PotDataRecord(x=112, y=10, reserved=0), PotDataRecord(x=94, y=12, reserved=0),), items=(1, 1, 8, 11, 11, 12)), + PotRoomDataRecord(room_id=188, pots=(PotDataRecord(x=138, y=3, reserved=2), PotDataRecord(x=178, y=3, reserved=2), PotDataRecord(x=86, y=4, reserved=1), PotDataRecord(x=102, y=4, reserved=1), PotDataRecord(x=138, y=12, reserved=2), PotDataRecord(x=178, y=12, reserved=2), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=28, y=21, reserved=0), PotDataRecord(x=32, y=21, reserved=0), PotDataRecord(x=28, y=27, reserved=0), PotDataRecord(x=32, y=27, reserved=0), PotDataRecord(x=12, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(7, 7, 7, 7, 8, 10, 10, 10, 10, 10, 11, 11, 136)), + PotRoomDataRecord(room_id=191, pots=(PotDataRecord(x=40, y=20, reserved=0), PotDataRecord(x=44, y=20, reserved=0), PotDataRecord(x=48, y=20, reserved=0), PotDataRecord(x=40, y=28, reserved=0), PotDataRecord(x=44, y=28, reserved=0), PotDataRecord(x=48, y=28, reserved=0),), items=(9, 10, 11, 12, 12, 12)), + PotRoomDataRecord(room_id=192, pots=(PotDataRecord(x=48, y=10, reserved=0), PotDataRecord(x=12, y=14, reserved=0), PotDataRecord(x=12, y=26, reserved=0), PotDataRecord(x=28, y=27, reserved=0),), items=(1, 7, 10, 11)), + PotRoomDataRecord(room_id=194, pots=(PotDataRecord(x=180, y=7, reserved=0), PotDataRecord(x=100, y=46, reserved=0), PotDataRecord(x=68, y=48, reserved=0), PotDataRecord(x=64, y=52, reserved=0),), items=(1, 9, 12, 136)), + PotRoomDataRecord(room_id=196, pots=(PotDataRecord(x=84, y=9, reserved=0), PotDataRecord(x=24, y=14, reserved=0), PotDataRecord(x=56, y=17, reserved=0), PotDataRecord(x=84, y=17, reserved=0), PotDataRecord(x=12, y=21, reserved=0), PotDataRecord(x=76, y=23, reserved=0), PotDataRecord(x=48, y=25, reserved=0), PotDataRecord(x=12, y=26, reserved=0),), items=(1, 9, 12, 7, 10, 10, 11, 11)), + PotRoomDataRecord(room_id=199, pots=(PotDataRecord(x=12, y=10, reserved=0), PotDataRecord(x=12, y=11, reserved=0), PotDataRecord(x=12, y=22, reserved=0), PotDataRecord(x=12, y=28, reserved=0),), items=(9, 12, 11, 13)), + PotRoomDataRecord(room_id=201, pots=(PotDataRecord(x=30, y=22, reserved=0), PotDataRecord(x=94, y=22, reserved=0), PotDataRecord(x=60, y=22, reserved=0),), items=(1, 1, 136)), + PotRoomDataRecord(room_id=203, pots=(PotDataRecord(x=88, y=16, reserved=0), PotDataRecord(x=88, y=28, reserved=0),), items=(7, 11)), + PotRoomDataRecord(room_id=204, pots=(PotDataRecord(x=36, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=36, y=28, reserved=0), PotDataRecord(x=112, y=28, reserved=0),), items=(7, 11, 7, 10)), + PotRoomDataRecord(room_id=206, pots=(PotDataRecord(x=76, y=8, reserved=0), PotDataRecord(x=80, y=8, reserved=0), PotDataRecord(x=108, y=12, reserved=0), PotDataRecord(x=112, y=12, reserved=0), PotDataRecord(x=204, y=11, reserved=3),), items=(9, 12, 12, 10, 128)), + PotRoomDataRecord(room_id=208, pots=(PotDataRecord(x=158, y=5, reserved=0), PotDataRecord(x=140, y=11, reserved=0), PotDataRecord(x=42, y=13, reserved=0), PotDataRecord(x=48, y=16, reserved=0), PotDataRecord(x=176, y=20, reserved=0), PotDataRecord(x=146, y=23, reserved=0), PotDataRecord(x=12, y=28, reserved=0),), items=(1, 1, 7, 11, 11, 12, 12)), + PotRoomDataRecord(room_id=209, pots=(PotDataRecord(x=76, y=12, reserved=0), PotDataRecord(x=48, y=4, reserved=0), PotDataRecord(x=76, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=168, y=7, reserved=0), PotDataRecord(x=112, y=12, reserved=0),), items=(9, 1, 1, 1, 13)), + PotRoomDataRecord(room_id=214, pots=(PotDataRecord(x=92, y=22, reserved=0), PotDataRecord(x=96, y=22, reserved=0),), items=(10, 13)), + PotRoomDataRecord(room_id=216, pots=(PotDataRecord(x=202, y=8, reserved=0), PotDataRecord(x=242, y=8, reserved=0), PotDataRecord(x=202, y=10, reserved=0), PotDataRecord(x=242, y=10, reserved=0), PotDataRecord(x=202, y=12, reserved=0), PotDataRecord(x=242, y=12, reserved=0), PotDataRecord(x=92, y=24, reserved=0), PotDataRecord(x=96, y=24, reserved=0),), items=(9, 9, 9, 9, 9, 11, 11, 11)), + PotRoomDataRecord(room_id=218, pots=(PotDataRecord(x=24, y=23, reserved=0), PotDataRecord(x=36, y=23, reserved=0), PotDataRecord(x=24, y=25, reserved=0), PotDataRecord(x=36, y=25, reserved=0),), items=(9, 9, 11, 136)), + PotRoomDataRecord(room_id=219, pots=(PotDataRecord(x=12, y=4, reserved=0), PotDataRecord(x=62, y=19, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=88, y=16, reserved=0),), items=(7, 11)), + PotRoomDataRecord(room_id=220, pots=(PotDataRecord(x=56, y=4, reserved=0), PotDataRecord(x=112, y=4, reserved=0), PotDataRecord(x=68, y=16, reserved=0), PotDataRecord(x=12, y=28, reserved=0),), items=(7, 9, 10, 11)), + PotRoomDataRecord(room_id=235, pots=(PotDataRecord(x=206, y=8, reserved=0), PotDataRecord(x=210, y=8, reserved=0), PotDataRecord(x=88, y=14, reserved=0), PotDataRecord(x=92, y=14, reserved=0), PotDataRecord(x=96, y=14, reserved=0),), items=(7, 7, 11, 12, 12)), +) diff --git a/worlds/alttp/enemizer_data/symbols.py b/worlds/alttp/enemizer_data/symbols.py new file mode 100644 index 0000000000..903d2ae7d5 --- /dev/null +++ b/worlds/alttp/enemizer_data/symbols.py @@ -0,0 +1,171 @@ +from __future__ import annotations + +ENEMIZER_SYMBOLS = { + ':pos_1_0': 0x36975E, + ':pos_1_1': 0x36976D, + ':pos_1_10': 0x369819, + ':pos_1_11': 0x369828, + ':pos_1_12': 0x369837, + ':pos_1_13': 0x369845, + ':pos_1_14': 0x36984B, + ':pos_1_15': 0x369851, + ':pos_1_16': 0x369873, + ':pos_1_17': 0x369898, + ':pos_1_18': 0x36989E, + ':pos_1_19': 0x3698A4, + ':pos_1_2': 0x369787, + ':pos_1_20': 0x3698C6, + ':pos_1_21': 0x3698EB, + ':pos_1_22': 0x3698F1, + ':pos_1_23': 0x3698F7, + ':pos_1_24': 0x369919, + ':pos_1_25': 0x36993E, + ':pos_1_26': 0x369944, + ':pos_1_27': 0x36994A, + ':pos_1_28': 0x36996C, + ':pos_1_29': 0x369AA5, + ':pos_1_3': 0x369796, + ':pos_1_30': 0x36B796, + ':pos_1_4': 0x3697A5, + ':pos_1_5': 0x3697B4, + ':pos_1_6': 0x3697D5, + ':pos_1_7': 0x3697E8, + ':pos_1_8': 0x3697F7, + ':pos_1_9': 0x369806, + 'CheckIfLinkShouldDie': 0x3699B2, + 'CheckIfLinkShouldDie_dead': 0x3699BB, + 'CheckIfLinkShouldDie_done': 0x3699BD, + 'Check_for_Blind_Fight': 0x1DA085, + 'CopyShield': 0x36B713, + 'CopyShield_loop_copy': 0x36B729, + 'CopyShield_shield_positon_gfx': 0x36B74B, + 'CopySword': 0x36B6D1, + 'CopySword_loop_copy': 0x36B6E7, + 'CopySword_sword_positon_gfx': 0x36B709, + 'DMAKholdstare': 0x3695A5, + 'DMATrinexx': 0x369617, + 'Dungeon_ResetSprites': 0x09C114, + 'EnemizerCodeStart': 0x3694F5, + 'EnemizerFlags': 0x368100, + 'EnemizerFlags_agahnim_fun_balls': 0x368104, + 'EnemizerFlags_close_blind_door': 0x368101, + 'EnemizerFlags_enable_mimic_override': 0x368105, + 'EnemizerFlags_enable_terrorpin_ai_fix': 0x368106, + 'EnemizerFlags_moldorm_eye_count': 0x368102, + 'EnemizerFlags_randomize_bushes': 0x368100, + 'EnemizerFlags_randomize_sprites': 0x368103, + 'EnemizerTablesStart': 0x368000, + 'Ext_OnBossDeath': 0x29803C, + 'Ext_OnDungeonCompleted': 0x29804B, + 'Ext_OnDungeonEnter': 0x298041, + 'Ext_OnDungeonExit': 0x298046, + 'Ext_OnFairyRevive': 0x298019, + 'Ext_OnFileCreate': 0x298000, + 'Ext_OnFileLoad': 0x298005, + 'Ext_OnFileSave': 0x29800A, + 'Ext_OnIemMenuOpen': 0x298023, + 'Ext_OnItemChange': 0x29802D, + 'Ext_OnItemMenuClose': 0x298028, + 'Ext_OnMapUse': 0x298014, + 'Ext_OnPlayerAttack': 0x298037, + 'Ext_OnPlayerDamaged': 0x298032, + 'Ext_OnPlayerDeath': 0x29800F, + 'Ext_OnYItemUse': 0x29801E, + 'Ext_OnZeldaRescued': 0x298050, + 'FixTerrorpin': 0x36971A, + 'FixTerrorpin_new': 0x369728, + 'GFX_Kholdstare_Shell': 0x36C79A, + 'GFX_Trinexx_Shell': 0x36D79A, + 'GFX_Trinexx_Shell2': 0x36DF9A, + 'GetRandomInt': 0x0DBA71, + 'Initialize_Blind_Fight': 0x1DA090, + 'Kholdstare_Draw': 0x0DD97F, + 'LoadFile': 0x369A87, + 'LoadNewSoundFx': 0x369A9E, + 'LoadOverworldSprites': 0x36B753, + 'Module_LoadFile_indoors': 0x028118, + 'Moldorm_UpdateOamPosition': 0x3699E7, + 'Moldorm_UpdateOamPosition_more_eyes': 0x3699ED, + 'NMIHookAction': 0x36956C, + 'NMIHookAction_loadKholdstare': 0x369584, + 'NMIHookAction_loadTrinexx': 0x369590, + 'NMIHookAction_return': 0x36959A, + 'NMIHookReturn': 0x0080D5, + 'NewLoadSoundBank': 0x369A98, + 'NewLoadSoundBank_Intro': 0x369A92, + 'OnInitFileSelect': 0x3696FA, + 'OnInitFileSelect_continue': 0x36970F, + 'Player_Main': 0x078000, + 'Sound_LoadSongBank': 0x008888, + 'Sound_SetSfx3PanLong': 0x0DBB8A, + 'Sound_SetSfxPanWithPlayerCoords': 0x0DBB67, + 'Spawn_Bees': 0x36B75D, + 'Spawn_Bees_done': 0x36B779, + 'SpritePrep_Eyegore': 0x1EC6FA, + 'SpritePrep_EyegoreNew': 0x369A1A, + 'SpritePrep_EyegoreNew_mimic': 0x369A31, + 'SpritePrep_EyegoreNew_new': 0x369A25, + 'Sprite_ResetAll': 0x09C44E, + 'Sprite_SpawnDynamically': 0x1DF65D, + 'VitreousKeyReset': 0x36B77A, + 'boss_move': 0x369743, + 'boss_move_loop_bottom_left': 0x369935, + 'boss_move_loop_bottom_left2': 0x369963, + 'boss_move_loop_bottom_right': 0x3698E2, + 'boss_move_loop_bottom_right2': 0x369910, + 'boss_move_loop_middle': 0x36983C, + 'boss_move_loop_middle2': 0x36986A, + 'boss_move_loop_top_right': 0x36988F, + 'boss_move_loop_top_right2': 0x3698BD, + 'boss_move_move_to_bottom_left': 0x369933, + 'boss_move_move_to_bottom_right': 0x3698E0, + 'boss_move_move_to_middle': 0x36983A, + 'boss_move_move_to_top_right': 0x36988D, + 'boss_move_no_blind_door': 0x3697D2, + 'boss_move_no_change': 0x369863, + 'boss_move_no_change2': 0x3698B6, + 'boss_move_no_change3': 0x369909, + 'boss_move_no_change4': 0x36995C, + 'boss_move_no_change_ov': 0x369885, + 'boss_move_no_change_ov2': 0x3698D8, + 'boss_move_no_change_ov3': 0x36992B, + 'boss_move_no_change_ov4': 0x36997E, + 'boss_move_return': 0x369986, + 'change_heartcontainer_position': 0x3699BE, + 'change_heartcontainer_position_not_moldorm_room': 0x3699E1, + 'check_blind_boss_room': 0x36B782, + 'check_special_action': 0x369731, + 'check_special_action_no_special_action': 0x36973E, + 'enemizer_info_table': 0x368000, + 'linkIsDead': 0x0780D5, + 'linkNotDead': 0x0780F7, + 'modified_room_object_table': 0x36B79A, + 'moved_room_header_bank_value_address': 0x368374, + 'newKodongoCollision': 0x369A02, + 'newKodongoCollision_continue': 0x369A19, + 'new_kholdstare_code': 0x369987, + 'new_kholdstare_code_already_iced': 0x369997, + 'new_trinexx_code': 0x36999C, + 'new_trinexx_code_already_rocked': 0x3699AC, + 'notItemSprite_Mimic': 0x369A66, + 'notItemSprite_Mimic_changeSpriteId': 0x369A7A, + 'notItemSprite_Mimic_continue': 0x369A82, + 'notItemSprite_Mimic_reloadSpriteIdAndSkipMimic': 0x369A7F, + 'resetSprite_Mimic': 0x369A4E, + 'resetSprite_Mimic_notMimic': 0x369A60, + 'room_header_table': 0x368375, + 'shieldgfx': 0x36AAD1, + 'sprite_bush_spawn': 0x3694F5, + 'sprite_bush_spawn_continue': 0x36950F, + 'sprite_bush_spawn_dontGoPhase2': 0x369565, + 'sprite_bush_spawn_item_table': 0x369525, + 'sprite_bush_spawn_newSpriteSpawn': 0x36954C, + 'sprite_bush_spawn_not_random': 0x36953B, + 'sprite_bush_spawn_not_random_old': 0x36950B, + 'sprite_bush_spawn_return': 0x369568, + 'sprite_bush_spawn_table': 0x368120, + 'sprite_bush_spawn_table_dungeons': 0x368248, + 'sprite_bush_spawn_table_overworld': 0x368120, + 'sprite_bush_spawn_table_random_sprites': 0x368370, + 'swordgfx': 0x369AD1, +} diff --git a/worlds/alttp/test/TestEnemizerPatches.py b/worlds/alttp/test/TestEnemizerPatches.py new file mode 100644 index 0000000000..4aa09bb62e --- /dev/null +++ b/worlds/alttp/test/TestEnemizerPatches.py @@ -0,0 +1,308 @@ +import unittest +from types import SimpleNamespace + +from worlds.alttp.EnemizerPatches import ( + ARROW_REFILL_5_SPRITE_ID, + BOSS_GFX_SHEET_INDEXES, + BOSS_PATCH_DATA, + DAMAGE_GROUP_TABLE_ADDRESS, + DUNGEON_BOSS_PATCH_DATA, + ENEMY_DAMAGE_TABLE_ADDRESS, + ENEMY_HP_TABLE_ADDRESS, + EXCLUDED_ENEMY_TABLE_SPRITE_IDS, + HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, + RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL, + RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS, + RETRO_RUPEE_REPLACEMENT_SPRITE_ID, + THIEF_DEFAULT_HP, + THIEF_SPRITE_ID, + TILE_TRAP_FLOOR_TILE_ADDRESS, + TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS, + TRINEXX_ICE_PROJECTILE_TILE_ADDRESS, + VANILLA_HIDDEN_ENEMY_CHANCE_POOL, + _apply_killable_thief, + _apply_randomized_tile_trap_floor_tile, + _get_enemizer_symbol, + _make_native_enemizer_rng, + _option_key, + patch_bosses, + _randomize_enemy_damage, + _randomize_enemy_health, + _set_enemizer_flag, + _shuffle_damage_groups, + _update_hidden_enemy_item_table_for_retro_mode, + apply_enemizer_base_patch, +) + + +class FakeRom: + def __init__(self, size: int = 0x400000) -> None: + self.buffer = bytearray(size) + + def read_byte(self, address: int) -> int: + return self.buffer[address] + + def read_bytes(self, startaddress: int, length: int) -> bytearray: + return self.buffer[startaddress:startaddress + length] + + def write_byte(self, address: int, value: int) -> None: + self.buffer[address] = value + + def write_bytes(self, startaddress: int, values) -> None: + self.buffer[startaddress:startaddress + len(values)] = values + + def write_int16(self, address: int, value: int) -> None: + self.write_bytes(address, (value & 0xFF, (value >> 8) & 0xFF)) + + +class TestEnemizerPatches(unittest.TestCase): + def test_enemizer_base_patch_applies_mimic_hooks(self) -> None: + rom = FakeRom() + + apply_enemizer_base_patch(rom) + + self.assertEqual(tuple(rom.read_bytes(0x307CB, 2)), (0xB6, 0x91)) + self.assertEqual(tuple(rom.read_bytes(0x311B6, 4)), (0x22, 0x1A, 0x9A, 0x36)) + self.assertEqual(tuple(rom.read_bytes(0x36C08, 5)), (0x22, 0x4E, 0x9A, 0x36, 0xEA)) + self.assertEqual(tuple(rom.read_bytes(0x36DA6, 4)), (0x22, 0x66, 0x9A, 0x36)) + self.assertEqual(tuple(rom.read_bytes(0xF0BB1, 2)), (0x95, 0xC7)) + self.assertEqual(tuple(rom.read_bytes(TRINEXX_ICE_FLOOR_ROUTINE_ADDRESS, 4)), (0xEA, 0xEA, 0xEA, 0xEA)) + self.assertEqual(tuple(rom.read_bytes(TRINEXX_ICE_PROJECTILE_TILE_ADDRESS, 2)), (0x00, 0x00)) + self.assertEqual(rom.read_byte(TILE_TRAP_FLOOR_TILE_ADDRESS), 0x00) + + def test_randomized_tile_trap_floor_tile_patch_is_separate(self) -> None: + rom = FakeRom() + + _apply_randomized_tile_trap_floor_tile(rom) + + self.assertEqual(tuple(rom.read_bytes(TRINEXX_ICE_PROJECTILE_TILE_ADDRESS, 2)), (0x88, 0x01)) + self.assertEqual(rom.read_byte(TILE_TRAP_FLOOR_TILE_ADDRESS), 0x12) + + def test_enemy_shuffle_enables_hidden_enemy_and_mimic_support(self) -> None: + rom = FakeRom() + world = self._build_world(enemy_shuffle=True, bush_shuffle=False) + + self._apply_native_enemizer_features(world, rom) + + self.assertEqual( + tuple(rom.read_bytes(HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, len(VANILLA_HIDDEN_ENEMY_CHANCE_POOL))), + VANILLA_HIDDEN_ENEMY_CHANCE_POOL, + ) + self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_randomize_bushes")), 0x01) + self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_randomize_sprites")), 0x01) + self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_enable_mimic_override")), 0x01) + self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_enable_terrorpin_ai_fix")), 0x01) + self.assertEqual(tuple(rom.read_bytes(0x1F2D5, 2)), (0x54, 0x9C)) + self.assertEqual(rom.read_byte(0x1F2E5), 0xB0) + self.assertEqual(rom.read_byte(0x1F2EB), 0xD0) + + def test_bush_shuffle_and_remaining_tables_are_patched_natively(self) -> None: + rom = FakeRom() + item_table_address = _get_enemizer_symbol("sprite_bush_spawn_item_table") + not_item_sprite_address = _get_enemizer_symbol("notItemSprite_Mimic") + rom.write_byte(RETRO_ARROW_REPLACEMENT_CHECK_ADDRESS, RETRO_RUPEE_REPLACEMENT_SPRITE_ID) + rom.write_byte(item_table_address + 5, ARROW_REFILL_5_SPRITE_ID) + rom.write_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID, 0x08) + + included_hp_sprite_id = 0x01 + included_damage_sprite_id = 0x02 + excluded_sprite_id = min(EXCLUDED_ENEMY_TABLE_SPRITE_IDS) + rom.write_byte(ENEMY_HP_TABLE_ADDRESS + included_hp_sprite_id, 0x06) + rom.write_byte(ENEMY_HP_TABLE_ADDRESS + excluded_sprite_id, 0x07) + rom.write_byte(ENEMY_DAMAGE_TABLE_ADDRESS + included_damage_sprite_id, 0x06) + rom.write_byte(ENEMY_DAMAGE_TABLE_ADDRESS + excluded_sprite_id, 0x05) + + world = self._build_world( + bush_shuffle=True, + killable_thieves=True, + enemy_health="hard", + enemy_damage="chaos", + ) + + self._apply_native_enemizer_features(world, rom) + + self.assertEqual( + tuple(rom.read_bytes(HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, len(RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL))), + RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL, + ) + self.assertEqual(rom.read_byte(item_table_address + 5), RETRO_RUPEE_REPLACEMENT_SPRITE_ID) + self.assertEqual(rom.read_byte(not_item_sprite_address + 4), THIEF_SPRITE_ID) + self.assertNotEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID), 0x08) + self.assertGreaterEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID), 2) + self.assertLess(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID), 25) + self.assertGreaterEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + included_hp_sprite_id), 2) + self.assertLess(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + included_hp_sprite_id), 25) + self.assertEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + excluded_sprite_id), 0x07) + self.assertIn(rom.read_byte(ENEMY_DAMAGE_TABLE_ADDRESS + included_damage_sprite_id), range(8)) + self.assertEqual(rom.read_byte(ENEMY_DAMAGE_TABLE_ADDRESS + excluded_sprite_id), 0x05) + for group_id in range(10): + group_address = DAMAGE_GROUP_TABLE_ADDRESS + (group_id * 3) + green_mail, blue_mail, red_mail = rom.read_bytes(group_address, 3) + self.assertIn(green_mail, range(64)) + self.assertIn(blue_mail, range(64)) + self.assertIn(red_mail, range(64)) + + def test_killable_thief_sets_default_hp_without_enemy_health_shuffle(self) -> None: + rom = FakeRom() + rom.write_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID, 0x08) + + world = self._build_world(killable_thieves=True) + + self._apply_native_enemizer_features(world, rom) + + self.assertEqual(rom.read_byte(ENEMY_HP_TABLE_ADDRESS + THIEF_SPRITE_ID), THIEF_DEFAULT_HP) + + def test_bush_shuffle_without_enemy_shuffle_does_not_enable_sprite_randomization_flags(self) -> None: + rom = FakeRom() + + self._apply_native_enemizer_features(self._build_world(bush_shuffle=True), rom) + + self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_randomize_bushes")), 0x01) + self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_randomize_sprites")), 0x00) + self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_enable_mimic_override")), 0x00) + self.assertEqual(rom.read_byte(_get_enemizer_symbol("EnemizerFlags_enable_terrorpin_ai_fix")), 0x00) + self.assertEqual(tuple(rom.read_bytes(0x1F2D5, 2)), (0x00, 0x00)) + self.assertEqual(rom.read_byte(0x1F2E5), 0x00) + self.assertEqual(rom.read_byte(0x1F2EB), 0x00) + + def test_non_chaos_enemy_damage_uses_expected_mail_scaling(self) -> None: + rom = FakeRom() + + self._apply_native_enemizer_features(self._build_world(enemy_damage="hard"), rom) + + for group_id in range(10): + group_address = DAMAGE_GROUP_TABLE_ADDRESS + (group_id * 3) + green_mail, blue_mail, red_mail = rom.read_bytes(group_address, 3) + self.assertEqual(blue_mail, green_mail * 3 // 4) + self.assertEqual(red_mail, green_mail * 3 // 8) + + def test_patch_bosses_overwrites_enemy_shuffle_boss_room_graphics(self) -> None: + rom = FakeRom() + dungeon_header_base = _get_enemizer_symbol("room_header_table") + eastern_dungeon_data = DUNGEON_BOSS_PATCH_DATA[("Eastern Palace", None)] + rom.write_byte(dungeon_header_base + (eastern_dungeon_data.room_id * 14) + 3, BOSS_PATCH_DATA["Armos"].graphics) + + for table_index in BOSS_GFX_SHEET_INDEXES.values(): + rom.write_byte(0x4FC0 + table_index, 0xAA) + rom.write_byte(0x509F + table_index, 0xBB) + rom.write_byte(0x517E + table_index, 0xCC) + + patch_bosses(self._build_boss_world({"Eastern Palace": "Vitreous"}), rom) + + eastern_boss_data = BOSS_PATCH_DATA["Vitreous"] + self.assertEqual( + tuple(rom.read_bytes(eastern_dungeon_data.sprite_pointer_address, 2)), + eastern_boss_data.pointer, + ) + self.assertEqual( + rom.read_byte(dungeon_header_base + (eastern_dungeon_data.room_id * 14) + 3), + eastern_boss_data.graphics, + ) + + for table_index in BOSS_GFX_SHEET_INDEXES.values(): + self.assertEqual(rom.read_byte(0x4FC0 + table_index), 0xAA) + self.assertEqual(rom.read_byte(0x509F + table_index), 0xBB) + self.assertEqual(rom.read_byte(0x517E + table_index), 0xCC) + + def test_native_enemizer_rng_is_deterministic_for_same_world_settings(self) -> None: + world = self._build_world(enemy_health="hard", enemy_damage="chaos", bush_shuffle=True) + + rng_a = _make_native_enemizer_rng(world) + rng_b = _make_native_enemizer_rng(world) + + self.assertEqual([rng_a.randrange(256) for _ in range(8)], [rng_b.randrange(256) for _ in range(8)]) + + @staticmethod + def _apply_native_enemizer_features(world: SimpleNamespace, rom: FakeRom) -> None: + enemy_shuffle_enabled = bool(world.options.enemy_shuffle) + bush_shuffle_enabled = bool(world.options.bush_shuffle) + enemy_health_key = _option_key(world.options.enemy_health) + enemy_damage_key = _option_key(world.options.enemy_damage) + + if enemy_shuffle_enabled or bush_shuffle_enabled: + _set_enemizer_flag(rom, "EnemizerFlags_randomize_bushes", True) + hidden_enemy_chance_pool = ( + RANDOMIZED_HIDDEN_ENEMY_CHANCE_POOL if bush_shuffle_enabled else VANILLA_HIDDEN_ENEMY_CHANCE_POOL + ) + rom.write_bytes(HIDDEN_ENEMY_CHANCE_POOL_ADDRESS, hidden_enemy_chance_pool) + _update_hidden_enemy_item_table_for_retro_mode(rom) + + if enemy_shuffle_enabled: + _set_enemizer_flag(rom, "EnemizerFlags_randomize_sprites", True) + _set_enemizer_flag(rom, "EnemizerFlags_enable_mimic_override", True) + _set_enemizer_flag(rom, "EnemizerFlags_enable_terrorpin_ai_fix", True) + rom.write_bytes(0x1F2D5, (0x54, 0x9C)) + rom.write_byte(0x1F2E5, 0xB0) + rom.write_byte(0x1F2EB, 0xD0) + + if world.options.killable_thieves: + _apply_killable_thief(rom) + + if enemy_health_key != "default" or enemy_damage_key != "default": + rng = _make_native_enemizer_rng(world) + else: + rng = None + + if enemy_health_key != "default": + assert rng is not None + _randomize_enemy_health(rom, rng, enemy_health_key) + + if enemy_damage_key != "default": + assert rng is not None + _randomize_enemy_damage(rom, rng, allow_zero_damage=True) + _shuffle_damage_groups(rom, rng, chaos_mode=enemy_damage_key == "chaos", allow_zero_damage=True) + + @staticmethod + def _build_world( + *, + enemy_shuffle: bool = False, + bush_shuffle: bool = False, + killable_thieves: bool = False, + enemy_health: str = "default", + enemy_damage: str = "default", + ) -> SimpleNamespace: + return SimpleNamespace( + player=1, + multiworld=SimpleNamespace(seed=12345, seed_name="native-enemizer-test"), + options=SimpleNamespace( + enemy_shuffle=enemy_shuffle, + bush_shuffle=bush_shuffle, + killable_thieves=killable_thieves, + enemy_health=SimpleNamespace(current_key=enemy_health), + enemy_damage=SimpleNamespace(current_key=enemy_damage), + ), + ) + + @staticmethod + def _build_boss_world(boss_overrides: dict[str, str] | None = None) -> SimpleNamespace: + boss_overrides = boss_overrides or {} + + def boss(name: str) -> SimpleNamespace: + return SimpleNamespace(enemizer_name=name) + + return SimpleNamespace( + options=SimpleNamespace(mode="open"), + dungeons={ + "Eastern Palace": SimpleNamespace(boss=boss(boss_overrides.get("Eastern Palace", "Armos"))), + "Desert Palace": SimpleNamespace(boss=boss(boss_overrides.get("Desert Palace", "Lanmola"))), + "Tower of Hera": SimpleNamespace(boss=boss(boss_overrides.get("Tower of Hera", "Moldorm"))), + "Palace of Darkness": SimpleNamespace(boss=boss(boss_overrides.get("Palace of Darkness", "Helmasaur"))), + "Swamp Palace": SimpleNamespace(boss=boss(boss_overrides.get("Swamp Palace", "Arrghus"))), + "Skull Woods": SimpleNamespace(boss=boss(boss_overrides.get("Skull Woods", "Mothula"))), + "Thieves Town": SimpleNamespace(boss=boss(boss_overrides.get("Thieves Town", "Blind"))), + "Ice Palace": SimpleNamespace(boss=boss(boss_overrides.get("Ice Palace", "Kholdstare"))), + "Misery Mire": SimpleNamespace(boss=boss(boss_overrides.get("Misery Mire", "Vitreous"))), + "Turtle Rock": SimpleNamespace(boss=boss(boss_overrides.get("Turtle Rock", "Trinexx"))), + "Ganons Tower": SimpleNamespace( + bosses={ + "bottom": boss(boss_overrides.get("Ganons Tower Bottom", "Armos")), + "middle": boss(boss_overrides.get("Ganons Tower Middle", "Lanmola")), + "top": boss(boss_overrides.get("Ganons Tower Top", "Moldorm")), + } + ), + }, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/worlds/alttp/test/TestEnemyShuffle.py b/worlds/alttp/test/TestEnemyShuffle.py new file mode 100644 index 0000000000..c16d85b4eb --- /dev/null +++ b/worlds/alttp/test/TestEnemyShuffle.py @@ -0,0 +1,834 @@ +import unittest +from types import SimpleNamespace +import random + +from worlds.alttp.EnemyShuffle import ( + DungeonEnemyRoom, + DungeonEnemySprite, + DungeonSpriteGroup, + EnemyShuffleState, + EnemySpriteRequirement, + OverworldEnemyArea, + OverworldEnemySprite, + RandomizedDungeonEnemyRoom, + RandomizedDungeonEnemySprite, + RandomizedOverworldEnemyArea, + RandomizedOverworldEnemySprite, + WALLMASTER_SPRITE_ID, + _load_dungeon_sprite_metadata, + _read_room_sprites, + get_possible_dungeon_sprite_groups, + _get_requirements_for_usable_dungeon_enemies, + _get_requirements_for_usable_overworld_enemies, + _get_randomizable_sprites_in_room, + _apply_selected_boss_group_requirements, + _randomize_overworld_groups, + _randomize_room_sprites, + _setup_required_overworld_groups, + can_spawn_in_room, + validate_enemy_shuffle_state, +) + + +class TestEnemyShuffleValidation(unittest.TestCase): + def test_curated_room_sprite_addresses_exclude_hera_basement_key_slot(self) -> None: + room_id = 135 + sprite_table_address = 0x4E397 + rom_bytes = bytearray(0x4E3C0) + rom_bytes[sprite_table_address] = 0 + room_135_sprite_records = ( + (0x4E398, 0x05, 0x14, 0x18), + (0x4E39B, 0x07, 0x1A, 0x18), + (0x4E39E, 0x0B, 0x13, 0x18), + (0x4E3A1, 0x19, 0x06, 0x18), + (0x4E3A4, 0x08, 0xE7, 0x14), + (0x4E3A7, 0x04, 0x17, 0x1E), + (0x4E3AA, 0x0C, 0x03, 0x1E), + (0x4E3AD, 0x15, 0x04, 0x1E), + (0x4E3B0, 0x17, 0x0B, 0xA7), + (0x4E3B3, 0x18, 0x19, 0xA7), + (0x4E3B6, 0x19, 0x04, 0xA7), + (0x4E3B9, 0x1A, 0x08, 0xE4), + (0x4E3BC, 0x1C, 0x15, 0xA7), + ) + for address, byte_0, byte_1, sprite_id in room_135_sprite_records: + rom_bytes[address] = byte_0 + rom_bytes[address + 1] = byte_1 + rom_bytes[address + 2] = sprite_id + rom_bytes[0x4E3BF] = 0xFF + + sprites = _read_room_sprites(rom_bytes, room_id, sprite_table_address, _load_dungeon_sprite_metadata()) + sprite_addresses = {sprite.address for sprite in sprites} + + self.assertNotIn(0x4E3B9, sprite_addresses) + self.assertIn(0x4E3B6, sprite_addresses) + self.assertFalse(any(sprite.has_key for sprite in sprites)) + + def test_curated_room_sprite_addresses_deduplicate_duplicate_slots(self) -> None: + room_id = 125 + sprite_table_address = 0x4E2CA + metadata = _load_dungeon_sprite_metadata() + max_sprite_id_address = max(metadata["room_sprite_id_addresses"][room_id]) + rom_bytes = bytearray(max_sprite_id_address + 2) + rom_bytes[sprite_table_address] = 0 + for offset, sprite_id_address in enumerate(metadata["room_sprite_id_addresses"][room_id]): + address = sprite_id_address - 2 + sprite_id = 0x80 if offset % 2 == 0 else 0x81 + rom_bytes[address] = 0 + rom_bytes[address + 1] = 0 + rom_bytes[address + 2] = sprite_id + + sprites = _read_room_sprites(rom_bytes, room_id, sprite_table_address, metadata) + sprite_addresses = [sprite.address for sprite in sprites] + + self.assertEqual(len(sprite_addresses), len(set(sprite_addresses))) + + def test_rejects_non_killable_shutter_room(self) -> None: + room = DungeonEnemyRoom( + room_id=1, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x10, is_overlord=False, has_key=False), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=True, + is_water_room=False, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={1: room}, + randomized_dungeon_rooms={ + 1: RandomizedDungeonEnemyRoom( + room_id=1, + room_header_address=0, + sprite_table_address=0, + original_graphics_block_id=1, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + RandomizedDungeonEnemySprite( + address=0x1000, + byte_0=0, + byte_1=0, + original_sprite_id=0x10, + sprite_id=0x11, + is_overlord=False, + has_key=False, + ), + ), + skipped_randomization=False, + ) + }, + sprite_requirements=( + self._requirement(0x10, killable=True, subgroup_0=(1,)), + self._requirement(0x11, killable=False, subgroup_0=(1,)), + ), + ) + + with self.assertRaises(ValueError): + validate_enemy_shuffle_state(state, is_standard_mode=False) + + def test_rejects_water_enemy_in_non_water_room(self) -> None: + room = DungeonEnemyRoom( + room_id=165, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=True, + is_water_room=False, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={165: room}, + randomized_dungeon_rooms={ + 165: RandomizedDungeonEnemyRoom( + room_id=165, + room_header_address=0, + sprite_table_address=0, + original_graphics_block_id=1, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + RandomizedDungeonEnemySprite( + address=0x1000, + byte_0=0, + byte_1=0, + original_sprite_id=0x20, + sprite_id=0x81, + is_overlord=False, + has_key=False, + ), + ), + skipped_randomization=False, + ) + }, + sprite_requirements=( + self._requirement(0x20, killable=True, subgroup_0=(1,)), + self._requirement(0x81, killable=True, subgroup_0=(1,), is_water_sprite=True), + ), + ) + + with self.assertRaisesRegex(ValueError, "water enemy"): + validate_enemy_shuffle_state(state, is_standard_mode=False) + + def test_rejects_multiple_flopping_fish(self) -> None: + area = OverworldEnemyArea( + area_id=0x10, + sprite_table_address=0, + graphics_block_address=0, + graphics_block_id=1, + bush_sprite_id=0x20, + sprites=( + OverworldEnemySprite(address=0x2000, y_coord=0, x_coord=0, sprite_id=0x20), + OverworldEnemySprite(address=0x2003, y_coord=0, x_coord=0, sprite_id=0x21), + ), + do_not_randomize=False, + ) + state = self._build_state( + overworld_areas={0x10: area}, + randomized_overworld_areas={ + 0x10: RandomizedOverworldEnemyArea( + area_id=0x10, + sprite_table_address=0, + graphics_block_address=0, + original_graphics_block_id=1, + graphics_block_id=1, + original_bush_sprite_id=0x20, + bush_sprite_id=0xD2, + sprites=( + RandomizedOverworldEnemySprite( + address=0x2000, + y_coord=0, + x_coord=0, + original_sprite_id=0x20, + sprite_id=0xD2, + ), + RandomizedOverworldEnemySprite( + address=0x2003, + y_coord=0, + x_coord=0, + original_sprite_id=0x21, + sprite_id=0xD2, + ), + ), + skipped_randomization=False, + ) + }, + sprite_requirements=( + self._requirement(0x20, group_ids=(1,)), + self._requirement(0x21, group_ids=(1,)), + self._requirement(0x22, group_ids=(1,)), + self._requirement(0xD2, group_ids=(1,)), + ), + ) + + with self.assertRaises(ValueError): + validate_enemy_shuffle_state(state, is_standard_mode=False) + + def test_allows_multiple_flopping_fish_when_no_other_sprite_is_possible(self) -> None: + area = OverworldEnemyArea( + area_id=0x10, + sprite_table_address=0, + graphics_block_address=0, + graphics_block_id=1, + bush_sprite_id=0x20, + sprites=( + OverworldEnemySprite(address=0x2000, y_coord=0, x_coord=0, sprite_id=0x20), + OverworldEnemySprite(address=0x2003, y_coord=0, x_coord=0, sprite_id=0x21), + ), + do_not_randomize=False, + ) + state = self._build_state( + overworld_areas={0x10: area}, + randomized_overworld_areas={ + 0x10: RandomizedOverworldEnemyArea( + area_id=0x10, + sprite_table_address=0, + graphics_block_address=0, + original_graphics_block_id=1, + graphics_block_id=1, + original_bush_sprite_id=0x20, + bush_sprite_id=0xD2, + sprites=( + RandomizedOverworldEnemySprite( + address=0x2000, + y_coord=0, + x_coord=0, + original_sprite_id=0x20, + sprite_id=0xD2, + ), + RandomizedOverworldEnemySprite( + address=0x2003, + y_coord=0, + x_coord=0, + original_sprite_id=0x21, + sprite_id=0xD2, + ), + ), + skipped_randomization=False, + ) + }, + sprite_requirements=( + self._requirement(0x20, group_ids=(2,)), + self._requirement(0x21, group_ids=(2,)), + self._requirement(0xD2, group_ids=(1,)), + ), + ) + + validate_enemy_shuffle_state(state, is_standard_mode=False) + + def test_excludes_absorbables_from_usable_enemy_pools(self) -> None: + state = self._build_state( + sprite_requirements=( + self._requirement(0x10, subgroup_0=(1,)), + self._requirement(0xE3, subgroup_0=(1,), absorbable=True), + self._requirement(0x20, subgroup_0=(1,), never_use_dungeon=True), + self._requirement(0x21, subgroup_0=(1,), never_use_overworld=True), + ), + ) + + self.assertEqual( + [requirement.sprite_id for requirement in _get_requirements_for_usable_dungeon_enemies(state)], + [0x10, 0x21], + ) + self.assertEqual( + [requirement.sprite_id for requirement in _get_requirements_for_usable_overworld_enemies(state)], + [0x10, 0x20], + ) + + def test_key_enemy_replacements_exclude_moblins(self) -> None: + room = DungeonEnemyRoom( + room_id=1, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x12, is_overlord=False, has_key=True), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=False, + is_water_room=False, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={1: room}, + sprite_requirements=( + self._requirement(0x12, killable=True, subgroup_0=(1,), cannot_have_key=True), + self._requirement(0x13, killable=True, subgroup_0=(1,)), + ), + ) + selected_group = state.sprite_groups[0x41] + + randomized_room = _randomize_room_sprites( + SimpleNamespace(random=random.Random(0)), + state, + room, + selected_group, + False, + ) + + self.assertEqual(randomized_room.sprites[0].sprite_id, 0x13) + + def test_shutter_water_room_prefers_killable_water_enemy(self) -> None: + room = DungeonEnemyRoom( + room_id=40, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x8A, is_overlord=False, has_key=False), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=True, + is_water_room=True, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={40: room}, + sprite_requirements=( + self._requirement(0x8A, killable=False, subgroup_2=(34,)), + self._requirement(0x81, killable=True, subgroup_2=(34,), is_water_sprite=True), + self._requirement(0x9A, killable=False, subgroup_2=(34,), is_water_sprite=True), + ), + ) + selected_group = state.sprite_groups[0x41] + selected_group.subgroup_2 = 34 + + randomized_room = _randomize_room_sprites( + SimpleNamespace(random=random.Random(0)), + state, + room, + selected_group, + False, + ) + + self.assertEqual(randomized_room.sprites[0].sprite_id, 0x81) + + def test_non_water_shutter_room_replacements_exclude_water_enemies(self) -> None: + room = DungeonEnemyRoom( + room_id=165, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=True, + is_water_room=False, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={165: room}, + sprite_requirements=( + self._requirement(0x20, killable=False, subgroup_0=(1,)), + self._requirement(0x81, killable=True, subgroup_0=(1,), is_water_sprite=True), + self._requirement(0x22, killable=True, subgroup_0=(1,)), + ), + ) + + randomized_room = _randomize_room_sprites( + SimpleNamespace(random=random.Random(1)), + state, + room, + state.sprite_groups[0x41], + False, + ) + + self.assertEqual(randomized_room.sprites[0].sprite_id, 0x22) + + def test_non_water_shutter_group_selection_requires_non_water_killable_enemy(self) -> None: + room = DungeonEnemyRoom( + room_id=165, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=True, + is_water_room=False, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={165: room}, + sprite_requirements=( + self._requirement(0x20, killable=False, subgroup_0=(1,)), + self._requirement(0x81, killable=True, subgroup_0=(1,), is_water_sprite=True), + ), + ) + + self.assertEqual(get_possible_dungeon_sprite_groups(state, room), tuple()) + + def test_wallmaster_cannot_spawn_in_high_room_ids(self) -> None: + room = DungeonEnemyRoom( + room_id=0x100, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=tuple(), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=False, + is_water_room=False, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + + self.assertFalse(can_spawn_in_room(self._requirement(WALLMASTER_SPRITE_ID), room)) + + def test_room_specific_do_not_randomize_sprites_are_not_updated(self) -> None: + room = DungeonEnemyRoom( + room_id=7, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x30, is_overlord=False, has_key=False), + DungeonEnemySprite(address=0x1003, byte_0=0, byte_1=0, sprite_id=0x31, is_overlord=False, has_key=False), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=False, + is_water_room=False, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={7: room}, + sprite_requirements=( + self._requirement(0x30, subgroup_0=(1,), dont_randomize_rooms=(7,)), + self._requirement(0x31, subgroup_0=(1,)), + ), + ) + + self.assertEqual( + [sprite.sprite_id for sprite in _get_randomizable_sprites_in_room(state, room)], + [0x31], + ) + + def test_water_rooms_only_use_water_enemies(self) -> None: + room = DungeonEnemyRoom( + room_id=1, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=False, + is_water_room=True, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={1: room}, + sprite_requirements=( + self._requirement(0x20, subgroup_0=(1,)), + self._requirement(0x21, subgroup_0=(1,), is_water_sprite=True), + self._requirement(0x22, subgroup_0=(1,), is_water_sprite=True), + ), + ) + + randomized_room = _randomize_room_sprites( + SimpleNamespace(random=random.Random(0)), + state, + room, + state.sprite_groups[0x41], + False, + ) + + self.assertIn(randomized_room.sprites[0].sprite_id, {0x21, 0x22}) + + def test_dungeon_group_selection_excludes_groups_without_enemy_requirements(self) -> None: + room = DungeonEnemyRoom( + room_id=1, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=False), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=False, + is_water_room=False, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={1: room}, + sprite_requirements=(self._requirement(0x20, subgroup_0=(1,)),), + ) + state.sprite_groups[0x42] = DungeonSpriteGroup( + group_id=0x42, + dungeon_group_id=2, + subgroup_0=0, + subgroup_1=0, + subgroup_2=0, + subgroup_3=0, + ) + + possible_groups = get_possible_dungeon_sprite_groups(state, room) + + self.assertEqual([group.group_id for group in possible_groups], [0x41]) + + def test_key_room_group_selection_excludes_groups_without_room_spawnable_key_enemies(self) -> None: + room = DungeonEnemyRoom( + room_id=61, + room_header_address=0, + sprite_table_address=0, + graphics_block_id=1, + tag_1=0, + tag_2=0, + sort_sprites_value=0, + sprites=( + DungeonEnemySprite(address=0x1000, byte_0=0, byte_1=0, sprite_id=0x20, is_overlord=False, has_key=True), + ), + required_group_id=None, + required_subgroup_0=tuple(), + required_subgroup_1=tuple(), + required_subgroup_2=tuple(), + required_subgroup_3=tuple(), + is_shutter_room=False, + is_water_room=False, + do_not_randomize=False, + no_special_enemies_standard=False, + ) + state = self._build_state( + dungeon_rooms={61: room}, + sprite_requirements=( + self._requirement(0x20, subgroup_0=(1,)), + self._requirement(0x50, killable=True, subgroup_1=(32,), excluded_rooms=(61,)), + self._requirement(0x9C, killable=True, subgroup_1=(32,), cannot_have_key=True), + self._requirement(0x51, killable=True, subgroup_1=(33,)), + ), + ) + state.sprite_groups[0x41] = DungeonSpriteGroup( + group_id=0x41, + dungeon_group_id=1, + subgroup_0=1, + subgroup_1=32, + subgroup_2=1, + subgroup_3=1, + ) + state.sprite_groups[0x42] = DungeonSpriteGroup( + group_id=0x42, + dungeon_group_id=2, + subgroup_0=1, + subgroup_1=33, + subgroup_2=1, + subgroup_3=1, + ) + + possible_groups = get_possible_dungeon_sprite_groups(state, room) + + self.assertEqual([group.group_id for group in possible_groups], [0x42]) + + def test_overworld_group_randomization_preserves_forced_subgroups(self) -> None: + sprite_groups = { + 7: DungeonSpriteGroup(group_id=7, dungeon_group_id=-57, subgroup_0=1, subgroup_1=2, subgroup_2=3, subgroup_3=4), + } + + _setup_required_overworld_groups( + sprite_groups, + ( + SimpleNamespace( + group_id=7, + subgroup_0=None, + subgroup_1=None, + subgroup_2=None, + subgroup_3=17, + areas=(0x02,), + ), + ), + ) + _randomize_overworld_groups(SimpleNamespace(random=random.Random(0)), sprite_groups) + + group = sprite_groups[7] + self.assertEqual(group.subgroup_3, 17) + self.assertIn(group.subgroup_0, {22, 31, 47, 14}) + self.assertIn(group.subgroup_1, {44, 30, 32}) + self.assertIn(group.subgroup_2, {12, 18, 23, 24, 28, 46, 34, 35, 39, 40, 38, 41, 36, 37, 42}) + + def test_selected_boss_group_requirements_override_shared_boss_graphics_group(self) -> None: + sprite_groups = { + 0x56: DungeonSpriteGroup( + group_id=0x56, + dungeon_group_id=22, + subgroup_0=1, + subgroup_1=1, + subgroup_2=60, + subgroup_3=49, + ), + } + sprite_requirements = ( + self._requirement(162, subgroup_2=(60,)), + self._requirement(189, subgroup_3=(61,)), + ) + + _apply_selected_boss_group_requirements( + self._build_boss_world({"Eastern Palace": "Vitreous"}), + sprite_groups, + sprite_requirements, + ) + + group = sprite_groups[0x56] + self.assertEqual(group.subgroup_2, 60) + self.assertEqual(group.subgroup_3, 61) + self.assertTrue(group.preserve_subgroup_2) + self.assertTrue(group.preserve_subgroup_3) + + @staticmethod + def _requirement( + sprite_id: int, + *, + killable: bool = False, + subgroup_0: tuple[int, ...] = tuple(), + subgroup_1: tuple[int, ...] = tuple(), + subgroup_2: tuple[int, ...] = tuple(), + subgroup_3: tuple[int, ...] = tuple(), + group_ids: tuple[int, ...] = tuple(), + absorbable: bool = False, + never_use_dungeon: bool = False, + never_use_overworld: bool = False, + cannot_have_key: bool = False, + is_water_sprite: bool = False, + excluded_rooms: tuple[int, ...] = tuple(), + dont_randomize_rooms: tuple[int, ...] = tuple(), + ) -> EnemySpriteRequirement: + return EnemySpriteRequirement( + sprite_name=f"sprite_{sprite_id:02x}", + sprite_id=sprite_id, + boss=False, + overlord=False, + do_not_randomize=False, + killable=killable, + npc=False, + never_use_dungeon=never_use_dungeon, + never_use_overworld=never_use_overworld, + cannot_have_key=cannot_have_key, + is_object=False, + absorbable=absorbable, + is_water_sprite=is_water_sprite, + is_enemy_sprite=True, + group_ids=group_ids, + subgroup_0=subgroup_0, + subgroup_1=subgroup_1, + subgroup_2=subgroup_2, + subgroup_3=subgroup_3, + parameters=None, + special_glitched=False, + excluded_rooms=excluded_rooms, + dont_randomize_rooms=dont_randomize_rooms, + spawnable_rooms=tuple(), + ) + + @staticmethod + def _build_state( + *, + dungeon_rooms=None, + overworld_areas=None, + randomized_dungeon_rooms=None, + randomized_overworld_areas=None, + sprite_requirements=tuple(), + ) -> EnemyShuffleState: + sprite_groups = { + 1: DungeonSpriteGroup(group_id=1, dungeon_group_id=-63, subgroup_0=1, subgroup_1=1, subgroup_2=1, subgroup_3=1), + 0x41: DungeonSpriteGroup(group_id=0x41, dungeon_group_id=1, subgroup_0=1, subgroup_1=1, subgroup_2=1, subgroup_3=1), + } + return EnemyShuffleState( + dungeon_rooms=dungeon_rooms or {}, + overworld_areas=overworld_areas or {}, + sprite_groups=sprite_groups, + sprite_requirements=sprite_requirements, + room_group_requirements=tuple(), + overworld_group_requirements=tuple(), + shutter_room_ids=frozenset(), + water_room_ids=frozenset(), + dont_randomize_room_ids=frozenset(), + no_special_enemies_standard_room_ids=frozenset(), + boss_room_ids=frozenset(), + dont_randomize_overworld_area_ids=frozenset(), + randomized_dungeon_rooms=randomized_dungeon_rooms or {}, + randomized_overworld_areas=randomized_overworld_areas or {}, + ) + + @staticmethod + def _build_boss_world(boss_overrides: dict[str, str] | None = None) -> SimpleNamespace: + boss_overrides = boss_overrides or {} + + def boss(name: str) -> SimpleNamespace: + return SimpleNamespace(enemizer_name=name) + + return SimpleNamespace( + options=SimpleNamespace(mode="open"), + dungeons={ + "Eastern Palace": SimpleNamespace(boss=boss(boss_overrides.get("Eastern Palace", "Armos"))), + "Desert Palace": SimpleNamespace(boss=boss(boss_overrides.get("Desert Palace", "Lanmola"))), + "Tower of Hera": SimpleNamespace(boss=boss(boss_overrides.get("Tower of Hera", "Moldorm"))), + "Palace of Darkness": SimpleNamespace(boss=boss(boss_overrides.get("Palace of Darkness", "Helmasaur"))), + "Swamp Palace": SimpleNamespace(boss=boss(boss_overrides.get("Swamp Palace", "Arrghus"))), + "Skull Woods": SimpleNamespace(boss=boss(boss_overrides.get("Skull Woods", "Mothula"))), + "Thieves Town": SimpleNamespace(boss=boss(boss_overrides.get("Thieves Town", "Blind"))), + "Ice Palace": SimpleNamespace(boss=boss(boss_overrides.get("Ice Palace", "Kholdstare"))), + "Misery Mire": SimpleNamespace(boss=boss(boss_overrides.get("Misery Mire", "Vitreous"))), + "Turtle Rock": SimpleNamespace(boss=boss(boss_overrides.get("Turtle Rock", "Trinexx"))), + "Ganons Tower": SimpleNamespace( + bosses={ + "bottom": boss(boss_overrides.get("Ganons Tower Bottom", "Armos")), + "middle": boss(boss_overrides.get("Ganons Tower Middle", "Lanmola")), + "top": boss(boss_overrides.get("Ganons Tower Top", "Moldorm")), + } + ), + }, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/worlds/alttp/test/TestPotShuffle.py b/worlds/alttp/test/TestPotShuffle.py new file mode 100644 index 0000000000..5d63d5d182 --- /dev/null +++ b/worlds/alttp/test/TestPotShuffle.py @@ -0,0 +1,56 @@ +import random +import unittest +from types import SimpleNamespace + +from worlds.alttp.PotShuffle import ( + POT_KEY, + POT_HOLE, + generate_pot_shuffle, + get_unique_pot_item_position, +) + + +class TestPotShuffle(unittest.TestCase): + def test_reserved_key_rooms_only_place_actual_keys(self) -> None: + for seed in range(10): + world = SimpleNamespace( + random=random.Random(seed), + options=SimpleNamespace(retro_bow=False), + ) + shuffled_pots = generate_pot_shuffle(world) + conveyor_cross_keys = [ + pot for pot in shuffled_pots[0x8B] + if pot.item == POT_KEY + ] + self.assertEqual(len(conveyor_cross_keys), 1) + + def test_get_unique_pot_item_position_returns_single_match(self) -> None: + world = SimpleNamespace( + random=random.Random(0), + options=SimpleNamespace(retro_bow=False), + ) + shuffled_pots = generate_pot_shuffle(world) + + self.assertEqual( + get_unique_pot_item_position(shuffled_pots, 0x36, POT_KEY), + (114, 16), + ) + + def test_reserved_hole_room_keeps_hole_fixed(self) -> None: + for seed in range(25): + world = SimpleNamespace( + random=random.Random(seed), + options=SimpleNamespace(retro_bow=False), + ) + shuffled_pots = generate_pot_shuffle(world) + hole_positions = [ + (pot.x, pot.y) + for pot in shuffled_pots[206] + if pot.item == POT_HOLE + ] + + self.assertEqual(hole_positions, [(204, 11)]) + + +if __name__ == "__main__": + unittest.main()