diff --git a/BaseClasses.py b/BaseClasses.py index df8ac02071..d32749f5f1 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -40,6 +40,7 @@ class MultiWorld(): plando_connections: List worlds: Dict[int, auto_world] groups: Dict[int, Group] + regions: List[Region] itempool: List[Item] is_race: bool = False precollected_items: Dict[int, List[Item]] @@ -50,6 +51,9 @@ class MultiWorld(): non_local_items: Dict[int, Options.NonLocalItems] progression_balancing: Dict[int, Options.ProgressionBalancing] completion_condition: Dict[int, Callable[[CollectionState], bool]] + indirect_connections: Dict[Region, Set[Entrance]] + exclude_locations: Dict[int, Options.ExcludeLocations] + class AttributeProxy(): def __init__(self, rule): @@ -87,6 +91,7 @@ class MultiWorld(): self.customitemarray = [] self.shuffle_ganon = True self.spoiler = Spoiler(self) + self.indirect_connections = {} self.fix_trock_doors = self.AttributeProxy( lambda player: self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted') self.fix_skullwoods_exit = self.AttributeProxy( @@ -295,6 +300,13 @@ class MultiWorld(): def get_file_safe_player_name(self, player: int) -> str: return ''.join(c for c in self.get_player_name(player) if c not in '<>:"/\\|?*') + def get_out_file_name_base(self, player: int) -> str: + """ the base name (without file extension) for each player's output file for a seed """ + return f"AP_{self.seed_name}_P{player}" \ + + (f"_{self.get_file_safe_player_name(player).replace(' ', '_')}" + if (self.player_name[player] != f"Player{player}") + else '') + def initialize_regions(self, regions=None): for region in regions if regions else self.regions: region.world = self @@ -404,6 +416,11 @@ class MultiWorld(): def clear_entrance_cache(self): self._cached_entrances = None + def register_indirect_condition(self, region: Region, entrance: Entrance): + """Report that access to this Region can result in unlocking this Entrance, + state.can_reach(Region) in the Entrance's traversal condition, as opposed to pure transition logic.""" + self.indirect_connections.setdefault(region, set()).add(entrance) + def get_locations(self) -> List[Location]: if self._cached_locations is None: self._cached_locations = [location for region in self.regions for location in region.locations] @@ -529,7 +546,7 @@ class MultiWorld(): beatable_fulfilled = False - def location_conditition(location: Location): + def location_condition(location: Location): """Determine if this location has to be accessible, location is already filtered by location_relevant""" if location.player in players["minimal"]: return False @@ -546,7 +563,7 @@ class MultiWorld(): def all_done(): """Check if all access rules are fulfilled""" if beatable_fulfilled: - if any(location_conditition(location) for location in locations): + if any(location_condition(location) for location in locations): return False # still locations required to be collected return True @@ -608,7 +625,6 @@ class CollectionState(): self.collect(item, True) def update_reachable_regions(self, player: int): - from worlds.alttp.EntranceShuffle import indirect_connections self.stale[player] = False rrp = self.reachable_regions[player] bc = self.blocked_connections[player] @@ -616,7 +632,7 @@ class CollectionState(): start = self.world.get_region('Menu', player) # init on first call - this can't be done on construction since the regions don't exist yet - if not start in rrp: + if start not in rrp: rrp.add(start) bc.update(start.exits) queue.extend(start.exits) @@ -636,8 +652,7 @@ class CollectionState(): self.path[new_region] = (new_region.name, self.path.get(connection, None)) # Retry connections if the new region can unblock them - if new_region.name in indirect_connections: - new_entrance = self.world.get_entrance(indirect_connections[new_region.name], player) + for new_entrance in self.world.indirect_connections.get(new_region, set()): if new_entrance in bc and new_entrance not in queue: queue.append(new_entrance) @@ -993,7 +1008,7 @@ class Entrance: return False - def connect(self, region: Region, addresses=None, target=None): + def connect(self, region: Region, addresses: Any = None, target: Any = None) -> None: self.connected_region = region self.target = target self.addresses = addresses @@ -1081,7 +1096,7 @@ class Location: show_in_spoiler: bool = True progress_type: LocationProgressType = LocationProgressType.DEFAULT always_allow = staticmethod(lambda item, state: False) - access_rule = staticmethod(lambda state: True) + access_rule: Callable[[CollectionState], bool] = staticmethod(lambda state: True) item_rule = staticmethod(lambda item: True) item: Optional[Item] = None diff --git a/CommonClient.py b/CommonClient.py index 94d4359dd1..2940ceed31 100644 --- a/CommonClient.py +++ b/CommonClient.py @@ -132,12 +132,12 @@ class CommonContext: # defaults starting_reconnect_delay: int = 5 current_reconnect_delay: int = starting_reconnect_delay - command_processor: type(CommandProcessor) = ClientCommandProcessor + command_processor: typing.Type[CommandProcessor] = ClientCommandProcessor ui = None - ui_task: typing.Optional[asyncio.Task] = None - input_task: typing.Optional[asyncio.Task] = None - keep_alive_task: typing.Optional[asyncio.Task] = None - server_task: typing.Optional[asyncio.Task] = None + ui_task: typing.Optional["asyncio.Task[None]"] = None + input_task: typing.Optional["asyncio.Task[None]"] = None + keep_alive_task: typing.Optional["asyncio.Task[None]"] = None + server_task: typing.Optional["asyncio.Task[None]"] = None server: typing.Optional[Endpoint] = None server_version: Version = Version(0, 0, 0) current_energy_link_value: int = 0 # to display in UI, gets set by server @@ -146,7 +146,7 @@ class CommonContext: # remaining type info slot_info: typing.Dict[int, NetworkSlot] - server_address: str + server_address: typing.Optional[str] password: typing.Optional[str] hint_cost: typing.Optional[int] player_names: typing.Dict[int, str] @@ -154,6 +154,7 @@ class CommonContext: # locations locations_checked: typing.Set[int] # local state locations_scouted: typing.Set[int] + items_received: typing.List[NetworkItem] missing_locations: typing.Set[int] # server state checked_locations: typing.Set[int] # server state server_locations: typing.Set[int] # all locations the server knows of, missing_location | checked_locations @@ -163,7 +164,7 @@ class CommonContext: # current message box through kvui _messagebox = None - def __init__(self, server_address, password): + def __init__(self, server_address: typing.Optional[str], password: typing.Optional[str]) -> None: # server state self.server_address = server_address self.username = None @@ -243,7 +244,8 @@ class CommonContext: if self.server_task is not None: await self.server_task - async def send_msgs(self, msgs): + async def send_msgs(self, msgs: typing.List[typing.Any]) -> None: + """ `msgs` JSON serializable """ if not self.server or not self.server.socket.open or self.server.socket.closed: return await self.server.socket.send(encode(msgs)) @@ -271,7 +273,7 @@ class CommonContext: logger.info('Enter slot name:') self.auth = await self.console_input() - async def send_connect(self, **kwargs): + async def send_connect(self, **kwargs: typing.Any) -> None: payload = { 'cmd': 'Connect', 'password': self.password, 'name': self.auth, 'version': Utils.version_tuple, @@ -282,7 +284,7 @@ class CommonContext: payload.update(kwargs) await self.send_msgs([payload]) - async def console_input(self): + async def console_input(self) -> str: self.input_requests += 1 return await self.input_queue.get() @@ -390,7 +392,7 @@ class CommonContext: # DeathLink hooks - def on_deathlink(self, data: dict): + def on_deathlink(self, data: typing.Dict[str, typing.Any]) -> None: """Gets dispatched when a new DeathLink is triggered by another linked player.""" self.last_death_link = max(data["time"], self.last_death_link) text = data.get("cause", "") @@ -477,7 +479,7 @@ async def keep_alive(ctx: CommonContext, seconds_between_checks=100): seconds_elapsed = 0 -async def server_loop(ctx: CommonContext, address=None): +async def server_loop(ctx: CommonContext, address: typing.Optional[str] = None) -> None: if ctx.server and ctx.server.socket: logger.error('Already connected') return @@ -722,7 +724,7 @@ async def console_loop(ctx: CommonContext): logger.exception(e) -def get_base_parser(description=None): +def get_base_parser(description: typing.Optional[str] = None): import argparse parser = argparse.ArgumentParser(description=description) parser.add_argument('--connect', default=None, help='Address of the multiworld host.') diff --git a/Fill.py b/Fill.py index c62eaabde8..4b095eb108 100644 --- a/Fill.py +++ b/Fill.py @@ -4,9 +4,10 @@ import collections import itertools from collections import Counter, deque -from BaseClasses import CollectionState, Location, LocationProgressType, MultiWorld, Item +from BaseClasses import CollectionState, Location, LocationProgressType, MultiWorld, Item, ItemClassification from worlds.AutoWorld import call_all +from worlds.generic.Rules import add_item_rule class FillError(RuntimeError): @@ -209,6 +210,34 @@ def fast_fill(world: MultiWorld, return item_pool[placing:], fill_locations[placing:] +def accessibility_corrections(world: MultiWorld, state: CollectionState, locations, pool=[]): + maximum_exploration_state = sweep_from_pool(state, pool) + minimal_players = {player for player in world.player_ids if world.accessibility[player] == "minimal"} + unreachable_locations = [location for location in world.get_locations() if location.player in minimal_players and + not location.can_reach(maximum_exploration_state)] + for location in unreachable_locations: + if (location.item is not None and location.item.advancement and location.address is not None and not + location.locked and location.item.player not in minimal_players): + pool.append(location.item) + state.remove(location.item) + location.item = None + location.event = False + if location in state.events: + state.events.remove(location) + locations.append(location) + + if pool: + fill_restrictive(world, state, locations, pool) + + +def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locations): + maximum_exploration_state = sweep_from_pool(state, []) + unreachable_locations = [location for location in locations if not location.can_reach(maximum_exploration_state)] + for location in unreachable_locations: + add_item_rule(location, lambda item: not ((item.classification & 0b0011) and + world.accessibility[item.player] != 'minimal')) + + def distribute_items_restrictive(world: MultiWorld) -> None: fill_locations = sorted(world.get_unfilled_locations()) world.random.shuffle(fill_locations) @@ -239,7 +268,15 @@ def distribute_items_restrictive(world: MultiWorld) -> None: defaultlocations = locations[LocationProgressType.DEFAULT] excludedlocations = locations[LocationProgressType.EXCLUDED] - fill_restrictive(world, world.state, prioritylocations, progitempool, lock=True) + prioritylocations_lock = prioritylocations.copy() + + fill_restrictive(world, world.state, prioritylocations, progitempool) + accessibility_corrections(world, world.state, prioritylocations, progitempool) + + for location in prioritylocations_lock: + if location.item: + location.locked = True + if prioritylocations: defaultlocations = prioritylocations + defaultlocations @@ -248,6 +285,9 @@ def distribute_items_restrictive(world: MultiWorld) -> None: if progitempool: raise FillError( f'Not enough locations for progress items. There are {len(progitempool)} more items than locations') + accessibility_corrections(world, world.state, defaultlocations) + + inaccessible_location_rules(world, world.state, defaultlocations) remaining_fill(world, excludedlocations, filleritempool) if excludedlocations: diff --git a/Generate.py b/Generate.py index f048e54383..57d060d4d4 100644 --- a/Generate.py +++ b/Generate.py @@ -377,7 +377,7 @@ def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any: if option_key in options: if options[option_key].supports_weighting: return get_choice(option_key, category_dict) - return options[option_key] + return category_dict[option_key] if game == "A Link to the Past": # TODO wow i hate this if option_key in {"glitches_required", "dark_room_logic", "entrance_shuffle", "goals", "triforce_pieces_mode", "triforce_pieces_percentage", "triforce_pieces_available", "triforce_pieces_extra", diff --git a/Launcher.py b/Launcher.py index 8a3d53f866..9f9aaa4fb2 100644 --- a/Launcher.py +++ b/Launcher.py @@ -132,7 +132,7 @@ components: Iterable[Component] = ( Component('Text Client', 'CommonClient', 'ArchipelagoTextClient'), # SNI Component('SNI Client', 'SNIClient', - file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3')), + file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3', '.apsmw')), Component('LttP Adjuster', 'LttPAdjuster'), # Factorio Component('Factorio Client', 'FactorioClient'), diff --git a/LttPAdjuster.py b/LttPAdjuster.py index 469e8920b3..9fab226c67 100644 --- a/LttPAdjuster.py +++ b/LttPAdjuster.py @@ -139,7 +139,7 @@ def adjust(args): vanillaRom = args.baserom if not os.path.exists(vanillaRom) and not os.path.isabs(vanillaRom): vanillaRom = local_path(vanillaRom) - if os.path.splitext(args.rom)[-1].lower() in {'.apbp', '.aplttp'}: + if os.path.splitext(args.rom)[-1].lower() == '.aplttp': import Patch meta, args.rom = Patch.create_rom_file(args.rom) @@ -195,7 +195,7 @@ def adjustGUI(): romEntry2 = Entry(romDialogFrame, textvariable=romVar2) def RomSelect2(): - rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".apbp")), ("All Files", "*")]) + rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc", ".aplttp")), ("All Files", "*")]) romVar2.set(rom) romSelectButton2 = Button(romDialogFrame, text='Select Rom', command=RomSelect2) @@ -725,7 +725,7 @@ def get_rom_options_frame(parent=None): vars.auto_apply = StringVar(value=adjuster_settings.auto_apply) autoApplyFrame = Frame(romOptionsFrame) autoApplyFrame.grid(row=9, column=0, columnspan=2, sticky=W) - filler = Label(autoApplyFrame, text="Automatically apply last used settings on opening .apbp files") + filler = Label(autoApplyFrame, text="Automatically apply last used settings on opening .aplttp files") filler.pack(side=TOP, expand=True, fill=X) askRadio = Radiobutton(autoApplyFrame, text='Ask', variable=vars.auto_apply, value='ask') askRadio.pack(side=LEFT, padx=5, pady=5) diff --git a/ModuleUpdate.py b/ModuleUpdate.py index 17eb0906b1..1fe7030e4e 100644 --- a/ModuleUpdate.py +++ b/ModuleUpdate.py @@ -13,10 +13,12 @@ update_ran = getattr(sys, "frozen", False) # don't run update if environment is if not update_ran: for entry in os.scandir(os.path.join(local_dir, "worlds")): - if entry.is_dir(): - req_file = os.path.join(entry.path, "requirements.txt") - if os.path.exists(req_file): - requirements_files.add(req_file) + # skip .* (hidden / disabled) folders + if not entry.name.startswith("."): + if entry.is_dir(): + req_file = os.path.join(entry.path, "requirements.txt") + if os.path.exists(req_file): + requirements_files.add(req_file) def update_command(): diff --git a/NetUtils.py b/NetUtils.py index 1e7d66d824..513ab074fc 100644 --- a/NetUtils.py +++ b/NetUtils.py @@ -100,7 +100,7 @@ _encode = JSONEncoder( ).encode -def encode(obj): +def encode(obj: typing.Any) -> str: return _encode(_scan_for_TypedTuples(obj)) diff --git a/OoTClient.py b/OoTClient.py index fbe2b35d1a..b3c58612f3 100644 --- a/OoTClient.py +++ b/OoTClient.py @@ -5,7 +5,8 @@ import multiprocessing import subprocess from asyncio import StreamReader, StreamWriter -from CommonClient import CommonContext, server_loop, gui_enabled, console_loop, \ +# CommonClient import first to trigger ModuleUpdater +from CommonClient import CommonContext, server_loop, gui_enabled, \ ClientCommandProcessor, logger, get_base_parser import Utils from worlds import network_data_package diff --git a/Options.py b/Options.py index 49f044d8cd..567ac8dbc6 100644 --- a/Options.py +++ b/Options.py @@ -165,6 +165,7 @@ class FreeText(Option): class NumericOption(Option[int], numbers.Integral): + default = 0 # note: some of the `typing.Any`` here is a result of unresolved issue in python standards # `int` is not a `numbers.Integral` according to the official typestubs # (even though isinstance(5, numbers.Integral) == True) @@ -628,7 +629,7 @@ class VerifyKeys: class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys): - default = {} + default: typing.Dict[str, typing.Any] = {} supports_weighting = False def __init__(self, value: typing.Dict[str, typing.Any]): @@ -659,7 +660,7 @@ class ItemDict(OptionDict): class OptionList(Option[typing.List[typing.Any]], VerifyKeys): - default = [] + default: typing.List[typing.Any] = [] supports_weighting = False def __init__(self, value: typing.List[typing.Any]): diff --git a/Patch.py b/Patch.py index aaa4fc2404..4ff0e9602a 100644 --- a/Patch.py +++ b/Patch.py @@ -1,266 +1,33 @@ from __future__ import annotations -import shutil -import json -import bsdiff4 -import yaml import os -import lzma -import threading -import concurrent.futures -import zipfile import sys -from typing import Tuple, Optional, Dict, Any, Union, BinaryIO +from typing import Tuple, Optional, TypedDict -import ModuleUpdate -ModuleUpdate.update() +if __name__ == "__main__": + import ModuleUpdate + ModuleUpdate.update() -import Utils - -current_patch_version = 5 +from worlds.Files import AutoPatchRegister, APDeltaPatch -class AutoPatchRegister(type): - patch_types: Dict[str, APDeltaPatch] = {} - file_endings: Dict[str, APDeltaPatch] = {} - - def __new__(cls, name: str, bases, dct: Dict[str, Any]): - # construct class - new_class = super().__new__(cls, name, bases, dct) - if "game" in dct: - AutoPatchRegister.patch_types[dct["game"]] = new_class - if not dct["patch_file_ending"]: - raise Exception(f"Need an expected file ending for {name}") - AutoPatchRegister.file_endings[dct["patch_file_ending"]] = new_class - return new_class - - @staticmethod - def get_handler(file: str) -> Optional[type(APDeltaPatch)]: - for file_ending, handler in AutoPatchRegister.file_endings.items(): - if file.endswith(file_ending): - return handler - - -class APContainer: - """A zipfile containing at least archipelago.json""" - version: int = current_patch_version - compression_level: int = 9 - compression_method: int = zipfile.ZIP_DEFLATED - game: Optional[str] = None - - # instance attributes: - path: Optional[str] - player: Optional[int] - player_name: str - server: str - - def __init__(self, path: Optional[str] = None, player: Optional[int] = None, - player_name: str = "", server: str = ""): - self.path = path - self.player = player - self.player_name = player_name - self.server = server - - def write(self, file: Optional[Union[str, BinaryIO]] = None): - if not self.path and not file: - raise FileNotFoundError(f"Cannot write {self.__class__.__name__} due to no path provided.") - with zipfile.ZipFile(file if file else self.path, "w", self.compression_method, True, self.compression_level) \ - as zf: - if file: - self.path = zf.filename - self.write_contents(zf) - - def write_contents(self, opened_zipfile: zipfile.ZipFile): - manifest = self.get_manifest() - try: - manifest = json.dumps(manifest) - except Exception as e: - raise Exception(f"Manifest {manifest} did not convert to json.") from e - else: - opened_zipfile.writestr("archipelago.json", manifest) - - def read(self, file: Optional[Union[str, BinaryIO]] = None): - """Read data into patch object. file can be file-like, such as an outer zip file's stream.""" - if not self.path and not file: - raise FileNotFoundError(f"Cannot read {self.__class__.__name__} due to no path provided.") - with zipfile.ZipFile(file if file else self.path, "r") as zf: - if file: - self.path = zf.filename - self.read_contents(zf) - - def read_contents(self, opened_zipfile: zipfile.ZipFile): - with opened_zipfile.open("archipelago.json", "r") as f: - manifest = json.load(f) - if manifest["compatible_version"] > self.version: - raise Exception(f"File (version: {manifest['compatible_version']}) too new " - f"for this handler (version: {self.version})") - self.player = manifest["player"] - self.server = manifest["server"] - self.player_name = manifest["player_name"] - - def get_manifest(self) -> dict: - return { - "server": self.server, # allow immediate connection to server in multiworld. Empty string otherwise - "player": self.player, - "player_name": self.player_name, - "game": self.game, - # minimum version of patch system expected for patching to be successful - "compatible_version": 4, - "version": current_patch_version, - } - - -class APDeltaPatch(APContainer, metaclass=AutoPatchRegister): - """An APContainer that additionally has delta.bsdiff4 - containing a delta patch to get the desired file, often a rom.""" - - hash = Optional[str] # base checksum of source file - patch_file_ending: str = "" - delta: Optional[bytes] = None - result_file_ending: str = ".sfc" - source_data: bytes - - def __init__(self, *args, patched_path: str = "", **kwargs): - self.patched_path = patched_path - super(APDeltaPatch, self).__init__(*args, **kwargs) - - def get_manifest(self) -> dict: - manifest = super(APDeltaPatch, self).get_manifest() - manifest["base_checksum"] = self.hash - manifest["result_file_ending"] = self.result_file_ending - manifest["patch_file_ending"] = self.patch_file_ending - return manifest - - @classmethod - def get_source_data(cls) -> bytes: - """Get Base data""" - raise NotImplementedError() - - @classmethod - def get_source_data_with_cache(cls) -> bytes: - if not hasattr(cls, "source_data"): - cls.source_data = cls.get_source_data() - return cls.source_data - - def write_contents(self, opened_zipfile: zipfile.ZipFile): - super(APDeltaPatch, self).write_contents(opened_zipfile) - # write Delta - opened_zipfile.writestr("delta.bsdiff4", - bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read()), - compress_type=zipfile.ZIP_STORED) # bsdiff4 is a format with integrated compression - - def read_contents(self, opened_zipfile: zipfile.ZipFile): - super(APDeltaPatch, self).read_contents(opened_zipfile) - self.delta = opened_zipfile.read("delta.bsdiff4") - - def patch(self, target: str): - """Base + Delta -> Patched""" - if not self.delta: - self.read() - result = bsdiff4.patch(self.get_source_data_with_cache(), self.delta) - with open(target, "wb") as f: - f.write(result) - - -# legacy patch handling follows: GAME_ALTTP = "A Link to the Past" GAME_SM = "Super Metroid" GAME_SOE = "Secret of Evermore" GAME_SMZ3 = "SMZ3" GAME_DKC3 = "Donkey Kong Country 3" -supported_games = {"A Link to the Past", "Super Metroid", "Secret of Evermore", "SMZ3", "Donkey Kong Country 3"} -preferred_endings = { - GAME_ALTTP: "apbp", - GAME_SM: "apm3", - GAME_SOE: "apsoe", - GAME_SMZ3: "apsmz", - GAME_DKC3: "apdkc3" -} +GAME_SMW = "Super Mario World" -def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAME_ALTTP) -> bytes: - if game == GAME_ALTTP: - from worlds.alttp.Rom import LTTPJPN10HASH as HASH - elif game == GAME_SM: - from worlds.sm.Rom import SMJUHASH as HASH - elif game == GAME_SOE: - from worlds.soe.Patch import USHASH as HASH - elif game == GAME_SMZ3: - from worlds.alttp.Rom import LTTPJPN10HASH as ALTTPHASH - from worlds.sm.Rom import SMJUHASH as SMHASH - HASH = ALTTPHASH + SMHASH - elif game == GAME_DKC3: - from worlds.dkc3.Rom import USHASH as HASH - else: - raise RuntimeError(f"Selected game {game} for base rom not found.") - patch = yaml.dump({"meta": metadata, - "patch": patch, - "game": game, - # minimum version of patch system expected for patching to be successful - "compatible_version": 3, - "version": current_patch_version, - "base_checksum": HASH}) - return patch.encode(encoding="utf-8-sig") +class RomMeta(TypedDict): + server: str + player: Optional[int] + player_name: str -def generate_patch(rom: bytes, metadata: Optional[dict] = None, game: str = GAME_ALTTP) -> bytes: - if metadata is None: - metadata = {} - patch = bsdiff4.diff(get_base_rom_data(game), rom) - return generate_yaml(patch, metadata, game) - - -def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str = None, - player: int = 0, player_name: str = "", game: str = GAME_ALTTP) -> str: - meta = {"server": server, # allow immediate connection to server in multiworld. Empty string otherwise - "player_id": player, - "player_name": player_name} - bytes = generate_patch(load_bytes(rom_file_to_patch), - meta, - game) - target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + ( - ".apbp" if game == GAME_ALTTP - else ".apsmz" if game == GAME_SMZ3 - else ".apdkc3" if game == GAME_DKC3 - else ".apm3") - write_lzma(bytes, target) - return target - - -def create_rom_bytes(patch_file: str, ignore_version: bool = False) -> Tuple[dict, str, bytearray]: - data = Utils.parse_yaml(lzma.decompress(load_bytes(patch_file)).decode("utf-8-sig")) - game_name = data["game"] - if not ignore_version and data["compatible_version"] > current_patch_version: - raise RuntimeError("Patch file is incompatible with this patcher, likely an update is required.") - patched_data = bsdiff4.patch(get_base_rom_data(game_name), data["patch"]) - rom_hash = patched_data[int(0x7FC0):int(0x7FD5)] - data["meta"]["hash"] = "".join(chr(x) for x in rom_hash) - target = os.path.splitext(patch_file)[0] + ".sfc" - return data["meta"], target, patched_data - - -def get_base_rom_data(game: str): - if game == GAME_ALTTP: - from worlds.alttp.Rom import get_base_rom_bytes - elif game == "alttp": # old version for A Link to the Past - from worlds.alttp.Rom import get_base_rom_bytes - elif game == GAME_SM: - from worlds.sm.Rom import get_base_rom_bytes - elif game == GAME_SOE: - from worlds.soe.Patch import get_base_rom_path - get_base_rom_bytes = lambda: bytes(read_rom(open(get_base_rom_path(), "rb"))) - elif game == GAME_SMZ3: - from worlds.smz3.Rom import get_base_rom_bytes - elif game == GAME_DKC3: - from worlds.dkc3.Rom import get_base_rom_bytes - else: - raise RuntimeError("Selected game for base rom not found.") - return get_base_rom_bytes() - - -def create_rom_file(patch_file: str) -> Tuple[dict, str]: +def create_rom_file(patch_file: str) -> Tuple[RomMeta, str]: auto_handler = AutoPatchRegister.get_handler(patch_file) if auto_handler: handler: APDeltaPatch = auto_handler(patch_file) @@ -269,171 +36,10 @@ def create_rom_file(patch_file: str) -> Tuple[dict, str]: return {"server": handler.server, "player": handler.player, "player_name": handler.player_name}, target - else: - data, target, patched_data = create_rom_bytes(patch_file) - with open(target, "wb") as f: - f.write(patched_data) - return data, target - - -def update_patch_data(patch_data: bytes, server: str = "") -> bytes: - data = Utils.parse_yaml(lzma.decompress(patch_data).decode("utf-8-sig")) - data["meta"]["server"] = server - bytes = generate_yaml(data["patch"], data["meta"], data["game"]) - return lzma.compress(bytes) - - -def load_bytes(path: str) -> bytes: - with open(path, "rb") as f: - return f.read() - - -def write_lzma(data: bytes, path: str): - with lzma.LZMAFile(path, 'wb') as f: - f.write(data) - - -def read_rom(stream, strip_header=True) -> bytearray: - """Reads rom into bytearray and optionally strips off any smc header""" - buffer = bytearray(stream.read()) - if strip_header and len(buffer) % 0x400 == 0x200: - return buffer[0x200:] - return buffer + raise NotImplementedError(f"No Handler for {patch_file} found.") if __name__ == "__main__": - host = Utils.get_public_ipv4() - options = Utils.get_options()['server_options'] - if options['host']: - host = options['host'] - - address = f"{host}:{options['port']}" - ziplock = threading.Lock() - print(f"Host for patches to be created is {address}") - with concurrent.futures.ThreadPoolExecutor() as pool: - for rom in sys.argv: - try: - if rom.endswith(".sfc"): - print(f"Creating patch for {rom}") - result = pool.submit(create_patch_file, rom, address) - result.add_done_callback(lambda task: print(f"Created patch {task.result()}")) - - elif rom.endswith(".apbp"): - print(f"Applying patch {rom}") - data, target = create_rom_file(rom) - #romfile, adjusted = Utils.get_adjuster_settings(target) - adjuster_settings = Utils.get_adjuster_settings(GAME_ALTTP) - adjusted = False - if adjuster_settings: - import pprint - from worlds.alttp.Rom import get_base_rom_path - adjuster_settings.rom = target - adjuster_settings.baserom = get_base_rom_path() - adjuster_settings.world = None - whitelist = {"music", "menuspeed", "heartbeep", "heartcolor", "ow_palettes", "quickswap", - "uw_palettes", "sprite", "sword_palettes", "shield_palettes", "hud_palettes", - "reduceflashing", "deathlink"} - printed_options = {name: value for name, value in vars(adjuster_settings).items() if name in whitelist} - if hasattr(adjuster_settings, "sprite_pool"): - sprite_pool = {} - for sprite in getattr(adjuster_settings, "sprite_pool"): - if sprite in sprite_pool: - sprite_pool[sprite] += 1 - else: - sprite_pool[sprite] = 1 - if sprite_pool: - printed_options["sprite_pool"] = sprite_pool - - adjust_wanted = str('no') - if not hasattr(adjuster_settings, 'auto_apply') or 'ask' in adjuster_settings.auto_apply: - adjust_wanted = input(f"Last used adjuster settings were found. Would you like to apply these? \n" - f"{pprint.pformat(printed_options)}\n" - f"Enter yes, no, always or never: ") - if adjuster_settings.auto_apply == 'never': # never adjust, per user request - adjust_wanted = 'no' - elif adjuster_settings.auto_apply == 'always': - adjust_wanted = 'yes' - - if adjust_wanted and "never" in adjust_wanted: - adjuster_settings.auto_apply = 'never' - Utils.persistent_store("adjuster", GAME_ALTTP, adjuster_settings) - - elif adjust_wanted and "always" in adjust_wanted: - adjuster_settings.auto_apply = 'always' - Utils.persistent_store("adjuster", GAME_ALTTP, adjuster_settings) - - if adjust_wanted and adjust_wanted.startswith("y"): - if hasattr(adjuster_settings, "sprite_pool"): - from LttPAdjuster import AdjusterWorld - adjuster_settings.world = AdjusterWorld(getattr(adjuster_settings, "sprite_pool")) - - adjusted = True - import LttPAdjuster - _, romfile = LttPAdjuster.adjust(adjuster_settings) - - if hasattr(adjuster_settings, "world"): - delattr(adjuster_settings, "world") - else: - adjusted = False - if adjusted: - try: - shutil.move(romfile, target) - romfile = target - except Exception as e: - print(e) - print(f"Created rom {romfile if adjusted else target}.") - if 'server' in data: - Utils.persistent_store("servers", data['hash'], data['server']) - print(f"Host is {data['server']}") - elif rom.endswith(".apm3"): - print(f"Applying patch {rom}") - data, target = create_rom_file(rom) - print(f"Created rom {target}.") - if 'server' in data: - Utils.persistent_store("servers", data['hash'], data['server']) - print(f"Host is {data['server']}") - elif rom.endswith(".apsmz"): - print(f"Applying patch {rom}") - data, target = create_rom_file(rom) - print(f"Created rom {target}.") - if 'server' in data: - Utils.persistent_store("servers", data['hash'], data['server']) - print(f"Host is {data['server']}") - elif rom.endswith(".apdkc3"): - print(f"Applying patch {rom}") - data, target = create_rom_file(rom) - print(f"Created rom {target}.") - if 'server' in data: - Utils.persistent_store("servers", data['hash'], data['server']) - print(f"Host is {data['server']}") - - elif rom.endswith(".zip"): - print(f"Updating host in patch files contained in {rom}") - - - def _handle_zip_file_entry(zfinfo: zipfile.ZipInfo, server: str): - data = zfr.read(zfinfo) - if zfinfo.filename.endswith(".apbp") or \ - zfinfo.filename.endswith(".apm3") or \ - zfinfo.filename.endswith(".apdkc3"): - data = update_patch_data(data, server) - with ziplock: - zfw.writestr(zfinfo, data) - return zfinfo.filename - - - futures = [] - with zipfile.ZipFile(rom, "r") as zfr: - updated_zip = os.path.splitext(rom)[0] + "_updated.zip" - with zipfile.ZipFile(updated_zip, "w", compression=zipfile.ZIP_DEFLATED, - compresslevel=9) as zfw: - for zfname in zfr.namelist(): - futures.append(pool.submit(_handle_zip_file_entry, zfr.getinfo(zfname), address)) - for future in futures: - print(f"File {future.result()} added to {os.path.split(updated_zip)[1]}") - - except: - import traceback - - traceback.print_exc() - input("Press enter to close.") + for file in sys.argv[1:]: + meta_data, result_file = create_rom_file(file) + print(f"Patch with meta-data {meta_data} was written to {result_file}") diff --git a/README.md b/README.md index c8362dddd0..a82282037b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Currently, the following games are supported: * Starcraft 2: Wings of Liberty * Donkey Kong Country 3 * Dark Souls 3 +* Super Mario World For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/SNIClient.py b/SNIClient.py index 477cde86a2..188822bce7 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -15,10 +15,13 @@ import typing from json import loads, dumps -from Utils import init_logging, messagebox +# CommonClient import first to trigger ModuleUpdater +from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser + +import Utils if __name__ == "__main__": - init_logging("SNIClient", exception_logger="Client") + Utils.init_logging("SNIClient", exception_logger="Client") import colorama import websockets @@ -28,9 +31,8 @@ from worlds.alttp import Regions, Shops from worlds.alttp.Rom import ROM_PLAYER_LIMIT from worlds.sm.Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT from worlds.smz3.Rom import ROM_PLAYER_LIMIT as SMZ3_ROM_PLAYER_LIMIT -import Utils -from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser -from Patch import GAME_ALTTP, GAME_SM, GAME_SMZ3, GAME_DKC3 +from Patch import GAME_ALTTP, GAME_SM, GAME_SMZ3, GAME_DKC3, GAME_SMW + snes_logger = logging.getLogger("SNES") @@ -236,6 +238,10 @@ async def deathlink_kill_player(ctx: Context): snes_buffered_write(ctx, WRAM_START + 0x0A50, bytes([255])) # deal 255 of damage at next opportunity if not ctx.death_link_allow_survive: snes_buffered_write(ctx, WRAM_START + 0x09D6, bytes([0, 0])) # set current reserve to 0 + elif ctx.game == GAME_SMW: + from worlds.smw.Client import deathlink_kill_player as smw_deathlink_kill_player + await smw_deathlink_kill_player(ctx) + await snes_flush_writes(ctx) await asyncio.sleep(1) @@ -1041,6 +1047,9 @@ async def game_watcher(ctx: Context): from worlds.dkc3.Client import dkc3_rom_init init_handled = await dkc3_rom_init(ctx) + if not init_handled: + from worlds.smw.Client import smw_rom_init + init_handled = await smw_rom_init(ctx) if not init_handled: game_name = await snes_read(ctx, SM_ROMNAME_START, 5) if game_name is None: @@ -1299,6 +1308,9 @@ async def game_watcher(ctx: Context): elif ctx.game == GAME_DKC3: from worlds.dkc3.Client import dkc3_game_watcher await dkc3_game_watcher(ctx) + elif ctx.game == GAME_SMW: + from worlds.smw.Client import smw_game_watcher + await smw_game_watcher(ctx) async def run_game(romfile): @@ -1326,20 +1338,18 @@ async def main(): try: meta, romfile = Patch.create_rom_file(args.diff_file) except Exception as e: - messagebox('Error', str(e), True) + Utils.messagebox('Error', str(e), True) raise - if "server" in meta: - args.connect = meta["server"] + args.connect = meta["server"] logging.info(f"Wrote rom file to {romfile}") if args.diff_file.endswith(".apsoe"): import webbrowser - webbrowser.open("http://www.evermizer.com/apclient/" + - (f"#server={meta['server']}" if "server" in meta else "")) + webbrowser.open(f"http://www.evermizer.com/apclient/#server={meta['server']}") logging.info("Starting Evermizer Client in your Browser...") import time time.sleep(3) sys.exit() - elif args.diff_file.endswith((".apbp", ".apz3", ".aplttp")): + elif args.diff_file.endswith(".aplttp"): adjustedromfile, adjusted = get_alttp_settings(romfile) asyncio.create_task(run_game(adjustedromfile if adjusted else romfile)) else: diff --git a/Starcraft2Client.py b/Starcraft2Client.py index d91adffb08..c1eed74b4c 100644 --- a/Starcraft2Client.py +++ b/Starcraft2Client.py @@ -12,21 +12,9 @@ import typing import queue from pathlib import Path -import nest_asyncio -import sc2 -from sc2.bot_ai import BotAI -from sc2.data import Race -from sc2.main import run_game -from sc2.player import Bot - -import NetUtils -from MultiServer import mark_raw +# CommonClient import first to trigger ModuleUpdater +from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser from Utils import init_logging, is_windows -from worlds.sc2wol import SC2WoLWorld -from worlds.sc2wol.Items import lookup_id_to_name, item_table, ItemData, type_flaggroups -from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET -from worlds.sc2wol.MissionTables import lookup_id_to_mission -from worlds.sc2wol.Regions import MissionInfo if __name__ == "__main__": init_logging("SC2Client", exception_logger="Client") @@ -34,10 +22,21 @@ if __name__ == "__main__": logger = logging.getLogger("Client") sc2_logger = logging.getLogger("Starcraft2") -import colorama +import nest_asyncio +import sc2 +from sc2.bot_ai import BotAI +from sc2.data import Race +from sc2.main import run_game +from sc2.player import Bot +from worlds.sc2wol import SC2WoLWorld +from worlds.sc2wol.Items import lookup_id_to_name, item_table, ItemData, type_flaggroups +from worlds.sc2wol.Locations import SC2WOL_LOC_ID_OFFSET +from worlds.sc2wol.MissionTables import lookup_id_to_mission +from worlds.sc2wol.Regions import MissionInfo -from NetUtils import ClientStatus, RawJSONtoTextParser -from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser +import colorama +from NetUtils import ClientStatus, NetworkItem, RawJSONtoTextParser +from MultiServer import mark_raw nest_asyncio.apply() max_bonus: int = 8 @@ -357,8 +356,9 @@ class SC2Context(CommonContext): self.ui = SC2Manager(self) self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") - - Builder.load_file(Utils.local_path(os.path.dirname(SC2WoLWorld.__file__), "Starcraft2.kv")) + import pkgutil + data = pkgutil.get_data(SC2WoLWorld.__module__, "Starcraft2.kv").decode() + Builder.load_string(data) async def shutdown(self): await super(SC2Context, self).shutdown() @@ -440,8 +440,8 @@ wol_default_categories = [ ] -def calculate_items(items: typing.List[NetUtils.NetworkItem]) -> typing.List[int]: - network_item: NetUtils.NetworkItem +def calculate_items(items: typing.List[NetworkItem]) -> typing.List[int]: + network_item: NetworkItem accumulators: typing.List[int] = [0 for _ in type_flaggroups] for network_item in items: diff --git a/Utils.py b/Utils.py index c362131d75..707415453a 100644 --- a/Utils.py +++ b/Utils.py @@ -11,6 +11,8 @@ import io import collections import importlib import logging +from typing import BinaryIO + from yaml import load, load_all, dump, SafeLoader try: @@ -217,8 +219,11 @@ def get_public_ipv6() -> str: return ip +OptionsType = typing.Dict[str, typing.Dict[str, typing.Any]] + + @cache_argsless -def get_default_options() -> dict: +def get_default_options() -> OptionsType: # Refer to host.yaml for comments as to what all these options mean. options = { "general_options": { @@ -285,12 +290,17 @@ def get_default_options() -> dict: "sni": "SNI", "rom_start": True, }, + "smw_options": { + "rom_file": "Super Mario World (USA).sfc", + "sni": "SNI", + "rom_start": True, + }, } return options -def update_options(src: dict, dest: dict, filename: str, keys: list) -> dict: +def update_options(src: dict, dest: dict, filename: str, keys: list) -> OptionsType: for key, value in src.items(): new_keys = keys.copy() new_keys.append(key) @@ -310,9 +320,9 @@ def update_options(src: dict, dest: dict, filename: str, keys: list) -> dict: @cache_argsless -def get_options() -> dict: +def get_options() -> OptionsType: filenames = ("options.yaml", "host.yaml") - locations = [] + locations: typing.List[str] = [] if os.path.join(os.getcwd()) != local_path(): locations += filenames # use files from cwd only if it's not the local_path locations += [user_path(filename) for filename in filenames] @@ -353,7 +363,7 @@ def persistent_load() -> typing.Dict[str, dict]: return storage -def get_adjuster_settings(game_name: str): +def get_adjuster_settings(game_name: str) -> typing.Dict[str, typing.Any]: adjuster_settings = persistent_load().get("adjuster", {}).get(game_name, {}) return adjuster_settings @@ -392,7 +402,8 @@ class RestrictedUnpickler(pickle.Unpickler): # Options and Plando are unpickled by WebHost -> Generate if module == "worlds.generic" and name in {"PlandoItem", "PlandoConnection"}: return getattr(self.generic_properties_module, name) - if module.endswith("Options"): + # pep 8 specifies that modules should have "all-lowercase names" (options, not Options) + if module.lower().endswith("options"): if module == "Options": mod = self.options_module else: @@ -623,3 +634,11 @@ def title_sorted(data: typing.Sequence, key=None, ignore: typing.Set = frozenset else: return element.lower() return sorted(data, key=lambda i: sorter(key(i)) if key else sorter(i)) + + +def read_snes_rom(stream: BinaryIO, strip_header: bool = True) -> bytearray: + """Reads rom into bytearray and optionally strips off any smc header""" + buffer = bytearray(stream.read()) + if strip_header and len(buffer) % 0x400 == 0x200: + return buffer[0x200:] + return buffer diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index b7bf4e38d1..f9c49c5a20 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -10,7 +10,6 @@ from flask_compress import Compress from werkzeug.routing import BaseConverter from Utils import title_sorted -from .models import * UPLOAD_FOLDER = os.path.relpath('uploads') LOGS_FOLDER = os.path.relpath('logs') @@ -73,8 +72,10 @@ def register(): """Import submodules, triggering their registering on flask routing. Note: initializes worlds subsystem.""" # has automatic patch integration - import Patch - app.jinja_env.filters['supports_apdeltapatch'] = lambda game_name: game_name in Patch.AutoPatchRegister.patch_types + import worlds.AutoWorld + import worlds.Files + app.jinja_env.filters['supports_apdeltapatch'] = lambda game_name: \ + game_name in worlds.Files.AutoPatchRegister.patch_types from WebHostLib.customserver import run_server_process # to trigger app routing picking up on it diff --git a/WebHostLib/api/generate.py b/WebHostLib/api/generate.py index faad50e1c6..45cca66ef7 100644 --- a/WebHostLib/api/generate.py +++ b/WebHostLib/api/generate.py @@ -7,7 +7,8 @@ from . import api_endpoints from flask import request, session, url_for from pony.orm import commit -from WebHostLib import app, Generation, STATE_QUEUED, Seed, STATE_ERROR +from WebHostLib import app +from WebHostLib.models import Generation, STATE_QUEUED, Seed, STATE_ERROR from WebHostLib.check import get_yaml_data, roll_options from WebHostLib.generate import get_meta diff --git a/WebHostLib/downloads.py b/WebHostLib/downloads.py index c3a373c2e9..0386d1b0ae 100644 --- a/WebHostLib/downloads.py +++ b/WebHostLib/downloads.py @@ -5,8 +5,9 @@ from io import BytesIO from flask import send_file, Response, render_template from pony.orm import select -from Patch import update_patch_data, preferred_endings, AutoPatchRegister -from WebHostLib import app, Slot, Room, Seed, cache +from worlds.Files import AutoPatchRegister +from . import app, cache +from .models import Slot, Room, Seed @app.route("/dl_patch//") @@ -41,12 +42,7 @@ def download_patch(room_id, patch_id): new_file.seek(0) return send_file(new_file, as_attachment=True, download_name=fname) else: - patch_data = update_patch_data(patch.data, server=f"{app.config['PATCH_TARGET']}:{last_port}") - patch_data = BytesIO(patch_data) - - fname = f"P{patch.player_id}_{patch.player_name}_{app.jinja_env.filters['suuid'](room_id)}." \ - f"{preferred_endings[patch.game]}" - return send_file(patch_data, as_attachment=True, download_name=fname) + return "Old Patch file, no longer compatible." @app.route("/dl_spoiler/") diff --git a/WebHostLib/misc.py b/WebHostLib/misc.py index 03cd03b624..6978e27c28 100644 --- a/WebHostLib/misc.py +++ b/WebHostLib/misc.py @@ -151,7 +151,7 @@ def favicon(): @app.route('/discord') def discord(): - return redirect("https://discord.gg/archipelago") + return redirect("https://discord.gg/8Z65BR2") @app.route('/datapackage') diff --git a/WebHostLib/options.py b/WebHostLib/options.py index daa742d90e..6807d54689 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -64,7 +64,10 @@ def create(): for game_name, world in AutoWorldRegister.world_types.items(): - all_options = {**Options.per_game_common_options, **world.option_definitions} + all_options: typing.Dict[str, Options.AssembleOptions] = { + **Options.per_game_common_options, + **world.option_definitions + } with open(local_path("WebHostLib", "templates", "options.yaml")) as f: file_data = f.read() res = Template(file_data).render( diff --git a/WebHostLib/static/assets/faq/faq_en.md b/WebHostLib/static/assets/faq/faq_en.md index cd144d7eff..6ad50a50f6 100644 --- a/WebHostLib/static/assets/faq/faq_en.md +++ b/WebHostLib/static/assets/faq/faq_en.md @@ -46,7 +46,7 @@ the website is not required to generate them. ## How do I get started? If you are ready to start randomizing games, or want to start playing your favorite randomizer with others, please join -our discord server at the [Archipelago Discord](https://discord.gg/archipelago). There are always people ready to answer +our discord server at the [Archipelago Discord](https://discord.gg/8Z65BR2). There are always people ready to answer any questions you might have. ## What are some common terms I should know? diff --git a/WebHostLib/templates/timespinnerTracker.html b/WebHostLib/templates/timespinnerTracker.html index bd589e1068..82565316ab 100644 --- a/WebHostLib/templates/timespinnerTracker.html +++ b/WebHostLib/templates/timespinnerTracker.html @@ -41,7 +41,7 @@ {% endif %} - {% if 'FacebookMode' in options %} + {% if 'EyeSpy' in options %} {% else %} diff --git a/WebHostLib/tracker.py b/WebHostLib/tracker.py index fb5df81c9a..8bbf7465d3 100644 --- a/WebHostLib/tracker.py +++ b/WebHostLib/tracker.py @@ -8,7 +8,8 @@ import datetime from uuid import UUID from worlds.alttp import Items -from WebHostLib import app, cache, Room +from . import app, cache +from .models import Room from Utils import restricted_loads from worlds import lookup_any_item_id_to_name, lookup_any_location_id_to_name from MultiServer import Context diff --git a/WebHostLib/upload.py b/WebHostLib/upload.py index 00825df47b..173411bb64 100644 --- a/WebHostLib/upload.py +++ b/WebHostLib/upload.py @@ -1,6 +1,5 @@ import typing import zipfile -import lzma import json import base64 import MultiServer @@ -10,9 +9,10 @@ from io import BytesIO from flask import request, flash, redirect, url_for, session, render_template from pony.orm import flush, select -from WebHostLib import app, Seed, Room, Slot -from Utils import parse_yaml, VersionException, __version__ -from Patch import preferred_endings, AutoPatchRegister +from . import app +from .models import Seed, Room, Slot +from Utils import VersionException, __version__ +from worlds.Files import AutoPatchRegister from NetUtils import NetworkSlot, SlotType banned_zip_contents = (".sfc",) @@ -22,7 +22,7 @@ def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, s if not owner: owner = session["_id"] infolist = zfile.infolist() - slots = set() + slots: typing.Set[Slot] = set() spoiler = "" multidata = None for file in infolist: @@ -38,17 +38,6 @@ def upload_zip_to_db(zfile: zipfile.ZipFile, owner=None, meta={"race": False}, s player_name=patch.player_name, player_id=patch.player, game=patch.game)) - elif file.filename.endswith(tuple(preferred_endings.values())): - data = zfile.open(file, "r").read() - yaml_data = parse_yaml(lzma.decompress(data).decode("utf-8-sig")) - if yaml_data["version"] < 2: - return "Old format cannot be uploaded (outdated .apbp)" - metadata = yaml_data["meta"] - - slots.add(Slot(data=data, - player_name=metadata["player_name"], - player_id=metadata["player_id"], - game=yaml_data["game"])) elif file.filename.endswith(".apmc"): data = zfile.open(file, "r").read() diff --git a/data/basepatch.apbp b/data/basepatch.apbp deleted file mode 100644 index 2a30d9f8c2..0000000000 Binary files a/data/basepatch.apbp and /dev/null differ diff --git a/data/basepatch.bsdiff4 b/data/basepatch.bsdiff4 new file mode 100644 index 0000000000..a578b248f5 Binary files /dev/null and b/data/basepatch.bsdiff4 differ diff --git a/docs/adding games.md b/docs/adding games.md index 69f2bfb01f..e6b6195ec8 100644 --- a/docs/adding games.md +++ b/docs/adding games.md @@ -221,7 +221,7 @@ Starting with version 4 of the APBP format, this is a ZIP file containing metada files required by the game / patching process. For ROM-based games the ZIP will include a `delta.bsdiff4` which is the bsdiff between the original and the randomized ROM. -To make using APBP easy, they can be generated by inheriting from `Patch.APDeltaPatch`. +To make using APBP easy, they can be generated by inheriting from `worlds.Files.APDeltaPatch`. ### Mod files Games which support modding will usually just let you drag and drop the mod’s files into a folder somewhere. @@ -230,7 +230,7 @@ They can either be generic and modify the game using a seed or `slot_data` from generated per seed. If the mod is generated by AP and is installed from a ZIP file, it may be possible to include APBP metadata for easy -integration into the Webhost by inheriting from `Patch.APContainer`. +integration into the Webhost by inheriting from `worlds.Files.APContainer`. ## Archipelago Integration diff --git a/docs/network protocol.md b/docs/network protocol.md index 3315ddec2d..0e7a53f3cf 100644 --- a/docs/network protocol.md +++ b/docs/network protocol.md @@ -21,7 +21,7 @@ There are also a number of community-supported libraries available that implemen | | [Archipelago SNIClient](https://github.com/ArchipelagoMW/Archipelago/blob/main/SNIClient.py) | For Super Nintendo Game Support; Utilizes [SNI](https://github.com/alttpo/sni). | | JVM (Java / Kotlin) | [Archipelago.MultiClient.Java](https://github.com/ArchipelagoMW/Archipelago.MultiClient.Java) | | | .NET (C# / C++ / F# / VB.NET) | [Archipelago.MultiClient.Net](https://www.nuget.org/packages/Archipelago.MultiClient.Net) | | -| C++ | [apclientpp](https://github.com/black-sliver/apclientpp) | almost-header-only | +| C++ | [apclientpp](https://github.com/black-sliver/apclientpp) | header-only | | | [APCpp](https://github.com/N00byKing/APCpp) | CMake | | JavaScript / TypeScript | [archipelago.js](https://www.npmjs.com/package/archipelago.js) | Browser and Node.js Supported | | Haxe | [hxArchipelago](https://lib.haxe.org/p/hxArchipelago) | | @@ -371,7 +371,7 @@ Used to write data to the server's data storage, that data can then be shared ac | ------ | ----- | ------ | | key | str | The key to manipulate. | | default | any | The default value to use in case the key has no value on the server. | -| want_reply | bool | If set, the server will send a [SetReply](#SetReply) response back to the client. | +| want_reply | bool | If true, the server will send a [SetReply](#SetReply) response back to the client. | | operations | list\[[DataStorageOperation](#DataStorageOperation)\] | Operations to apply to the value, multiple operations can be present and they will be executed in order of appearance. | Additional arguments sent in this package will also be added to the [SetReply](#SetReply) package it triggers. diff --git a/docs/running from source.md b/docs/running from source.md index 39addd0a28..24486146f8 100644 --- a/docs/running from source.md +++ b/docs/running from source.md @@ -16,6 +16,10 @@ Then run any of the starting point scripts, like Generate.py, and the included M required modules and after pressing enter proceed to install everything automatically. After this, you should be able to run the programs. + * With yaml(s) in the `Players` folder, `Generate.py` will generate the multiworld archive. + * `MultiServer.py`, with the filename of the generated archive as a command line parameter, will host the multiworld locally. + * `--log_network` is a command line parameter useful for debugging. + ## Windows diff --git a/host.yaml b/host.yaml index 901e6cd727..b114135520 100644 --- a/host.yaml +++ b/host.yaml @@ -138,3 +138,12 @@ dkc3_options: # True for operating system default program # Alternatively, a path to a program to open the .sfc file with rom_start: true +smw_options: + # File name of the SMW US rom + rom_file: "Super Mario World (USA).sfc" + # Set this to your SNI folder location if you want the MultiClient to attempt an auto start, does nothing if not found + sni: "SNI" + # Set this to false to never autostart a rom (such as after patching) + # True for operating system default program + # Alternatively, a path to a program to open the .sfc file with + rom_start: true diff --git a/inno_setup.iss b/inno_setup.iss index cfdfec7ba8..9a2a40444e 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -55,6 +55,7 @@ Name: "core"; Description: "Core Files"; Types: full hosting playing Name: "generator"; Description: "Generator"; Types: full hosting Name: "generator/sm"; Description: "Super Metroid ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning Name: "generator/dkc3"; Description: "Donkey Kong Country 3 ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning +Name: "generator/smw"; Description: "Super Mario World ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning Name: "generator/soe"; Description: "Secret of Evermore ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning Name: "generator/lttp"; Description: "A Link to the Past ROM Setup and Enemizer"; Types: full hosting; ExtraDiskSpaceRequired: 5191680 Name: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 100663296; Flags: disablenouninstallwarning @@ -64,6 +65,7 @@ Name: "client/sni"; Description: "SNI Client"; Types: full playing Name: "client/sni/lttp"; Description: "SNI Client - A Link to the Past Patch Setup"; Types: full playing; Flags: disablenouninstallwarning Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing; Flags: disablenouninstallwarning Name: "client/sni/dkc3"; Description: "SNI Client - Donkey Kong Country 3 Patch Setup"; Types: full playing; Flags: disablenouninstallwarning +Name: "client/sni/smw"; Description: "SNI Client - Super Mario World Patch Setup"; Types: full playing; Flags: disablenouninstallwarning Name: "client/factorio"; Description: "Factorio"; Types: full playing Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278 Name: "client/oot"; Description: "Ocarina of Time"; Types: full playing @@ -79,6 +81,7 @@ NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-mod Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/sni/lttp or generator/lttp Source: "{code:GetSMROMPath}"; DestDir: "{app}"; DestName: "Super Metroid (JU).sfc"; Flags: external; Components: client/sni/sm or generator/sm Source: "{code:GetDKC3ROMPath}"; DestDir: "{app}"; DestName: "Donkey Kong Country 3 - Dixie Kong's Double Trouble! (USA) (En,Fr).sfc"; Flags: external; Components: client/sni/dkc3 or generator/dkc3 +Source: "{code:GetSMWROMPath}"; DestDir: "{app}"; DestName: "Super Mario World (USA).sfc"; Flags: external; Components: client/sni/smw or generator/smw Source: "{code:GetSoEROMPath}"; DestDir: "{app}"; DestName: "Secret of Evermore (USA).sfc"; Flags: external; Components: generator/soe Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: client/oot or generator/oot Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs @@ -151,6 +154,11 @@ Root: HKCR; Subkey: "{#MyAppName}dkc3patch"; ValueData: "Arc Root: HKCR; Subkey: "{#MyAppName}dkc3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: "{#MyAppName}dkc3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: ".apsmw"; ValueData: "{#MyAppName}smwpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smwpatch"; ValueData: "Archipelago Super Mario World Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smwpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}smwpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni + Root: HKCR; Subkey: ".apsmz3"; ValueData: "{#MyAppName}smz3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: "{#MyAppName}smz3patch"; ValueData: "Archipelago SMZ3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: "{#MyAppName}smz3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni @@ -217,6 +225,9 @@ var SMRomFilePage: TInputFileWizardPage; var dkc3rom: string; var DKC3RomFilePage: TInputFileWizardPage; +var smwrom: string; +var SMWRomFilePage: TInputFileWizardPage; + var soerom: string; var SoERomFilePage: TInputFileWizardPage; @@ -308,6 +319,8 @@ begin Result := not (SMROMFilePage.Values[0] = '') else if (assigned(DKC3ROMFilePage)) and (CurPageID = DKC3ROMFilePage.ID) then Result := not (DKC3ROMFilePage.Values[0] = '') + else if (assigned(SMWROMFilePage)) and (CurPageID = SMWROMFilePage.ID) then + Result := not (SMWROMFilePage.Values[0] = '') else if (assigned(SoEROMFilePage)) and (CurPageID = SoEROMFilePage.ID) then Result := not (SoEROMFilePage.Values[0] = '') else if (assigned(OoTROMFilePage)) and (CurPageID = OoTROMFilePage.ID) then @@ -364,6 +377,22 @@ begin Result := ''; end; +function GetSMWROMPath(Param: string): string; +begin + if Length(smwrom) > 0 then + Result := smwrom + else if Assigned(SMWRomFilePage) then + begin + R := CompareStr(GetSNESMD5OfFile(SMWROMFilePage.Values[0]), 'cdd3c8c37322978ca8669b34bc89c804') + if R <> 0 then + MsgBox('Super Mario World ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := SMWROMFilePage.Values[0] + end + else + Result := ''; + end; + function GetSoEROMPath(Param: string): string; begin if Length(soerom) > 0 then @@ -412,6 +441,10 @@ begin if Length(dkc3rom) = 0 then DKC3RomFilePage:= AddRomPage('Donkey Kong Country 3 - Dixie Kong''s Double Trouble! (USA) (En,Fr).sfc'); + smwrom := CheckRom('Super Mario World (USA).sfc', 'cdd3c8c37322978ca8669b34bc89c804'); + if Length(smwrom) = 0 then + SMWRomFilePage:= AddRomPage('Super Mario World (USA).sfc'); + soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a'); if Length(soerom) = 0 then SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc'); @@ -427,6 +460,8 @@ begin Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); if (assigned(DKC3ROMFilePage)) and (PageID = DKC3ROMFilePage.ID) then Result := not (WizardIsComponentSelected('client/sni/dkc3') or WizardIsComponentSelected('generator/dkc3')); + if (assigned(SMWROMFilePage)) and (PageID = SMWROMFilePage.ID) then + Result := not (WizardIsComponentSelected('client/sni/smw') or WizardIsComponentSelected('generator/smw')); if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/soe')); if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then diff --git a/test/general/TestReachability.py b/test/general/TestReachability.py index 2cadf9d29a..d638b56e8d 100644 --- a/test/general/TestReachability.py +++ b/test/general/TestReachability.py @@ -20,7 +20,7 @@ class TestBase(unittest.TestCase): for location in world.get_locations(): if location.name not in excluded: with self.subTest("Location should be reached", location=location): - self.assertTrue(location.can_reach(state)) + self.assertTrue(location.can_reach(state), f"{location.name} unreachable") with self.subTest("Completion Condition"): self.assertTrue(world.can_beat_game(state)) diff --git a/test/minor_glitches/TestMinor.py b/test/minor_glitches/TestMinor.py index 81c09cfb27..41cb13161a 100644 --- a/test/minor_glitches/TestMinor.py +++ b/test/minor_glitches/TestMinor.py @@ -23,10 +23,8 @@ class TestMinor(TestBase): self.world.set_default_common_options() self.world.logic[1] = "minorglitches" self.world.difficulty_requirements[1] = difficulties['normal'] - create_regions(self.world, 1) - create_dungeons(self.world, 1) - create_shops(self.world, 1) - link_entrances(self.world, 1) + self.world.worlds[1].er_seed = 0 + self.world.worlds[1].create_regions() self.world.worlds[1].create_items() self.world.required_medallions[1] = ['Ether', 'Quake'] self.world.itempool.extend(get_dungeon_item_pool(self.world)) diff --git a/test/owg/TestVanillaOWG.py b/test/owg/TestVanillaOWG.py index e5489117a7..a4367fb55e 100644 --- a/test/owg/TestVanillaOWG.py +++ b/test/owg/TestVanillaOWG.py @@ -24,10 +24,8 @@ class TestVanillaOWG(TestBase): self.world.set_default_common_options() self.world.difficulty_requirements[1] = difficulties['normal'] self.world.logic[1] = "owglitches" - create_regions(self.world, 1) - create_dungeons(self.world, 1) - create_shops(self.world, 1) - link_entrances(self.world, 1) + self.world.worlds[1].er_seed = 0 + self.world.worlds[1].create_regions() self.world.worlds[1].create_items() self.world.required_medallions[1] = ['Ether', 'Quake'] self.world.itempool.extend(get_dungeon_item_pool(self.world)) diff --git a/test/vanilla/TestVanilla.py b/test/vanilla/TestVanilla.py index e5ee73406a..c9fa3f763b 100644 --- a/test/vanilla/TestVanilla.py +++ b/test/vanilla/TestVanilla.py @@ -22,10 +22,8 @@ class TestVanilla(TestBase): self.world.set_default_common_options() self.world.logic[1] = "noglitches" self.world.difficulty_requirements[1] = difficulties['normal'] - create_regions(self.world, 1) - create_dungeons(self.world, 1) - create_shops(self.world, 1) - link_entrances(self.world, 1) + self.world.worlds[1].er_seed = 0 + self.world.worlds[1].create_regions() self.world.worlds[1].create_items() self.world.required_medallions[1] = ['Ether', 'Quake'] self.world.itempool.extend(get_dungeon_item_pool(self.world)) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index db72ca6a95..5dea03481f 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -3,9 +3,9 @@ from __future__ import annotations import logging import sys import pathlib -from typing import Dict, FrozenSet, Set, Tuple, List, Optional, TextIO, Any, Callable, Union, TYPE_CHECKING +from typing import Dict, FrozenSet, Set, Tuple, List, Optional, TextIO, Any, Callable, Type, Union, TYPE_CHECKING -from Options import Option +from Options import AssembleOptions from BaseClasses import CollectionState if TYPE_CHECKING: @@ -13,7 +13,7 @@ if TYPE_CHECKING: class AutoWorldRegister(type): - world_types: Dict[str, type(World)] = {} + world_types: Dict[str, Type[World]] = {} def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoWorldRegister: if "web" in dct: @@ -120,7 +120,7 @@ class World(metaclass=AutoWorldRegister): """A World object encompasses a game's Items, Locations, Rules and additional data or functionality required. A Game should have its own subclass of World in which it defines the required data structures.""" - option_definitions: Dict[str, Option[Any]] = {} # link your Options mapping + option_definitions: Dict[str, AssembleOptions] = {} # link your Options mapping game: str # name the game topology_present: bool = False # indicate if world type has any meaningful layout/pathing @@ -229,7 +229,8 @@ class World(metaclass=AutoWorldRegister): pass def post_fill(self) -> None: - """Optional Method that is called after regular fill. Can be used to do adjustments before output generation.""" + """Optional Method that is called after regular fill. Can be used to do adjustments before output generation. + This happens before progression balancing, so the items may not be in their final locations yet.""" def generate_output(self, output_directory: str) -> None: """This method gets called from a threadpool, do not use world.random here. @@ -237,7 +238,9 @@ class World(metaclass=AutoWorldRegister): pass def fill_slot_data(self) -> Dict[str, Any]: # json of WebHostLib.models.Slot - """Fill in the slot_data field in the Connected network package.""" + """Fill in the `slot_data` field in the `Connected` network package. + This is a way the generator can give custom data to the client. + The client will receive this as JSON in the `Connected` response.""" return {} def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]): diff --git a/worlds/Files.py b/worlds/Files.py new file mode 100644 index 0000000000..6f81a0e2ea --- /dev/null +++ b/worlds/Files.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +import json +import zipfile + +from typing import ClassVar, Dict, Tuple, Any, Optional, Union, BinaryIO + +import bsdiff4 + + +class AutoPatchRegister(type): + patch_types: ClassVar[Dict[str, AutoPatchRegister]] = {} + file_endings: ClassVar[Dict[str, AutoPatchRegister]] = {} + + def __new__(mcs, name: str, bases: Tuple[type, ...], dct: Dict[str, Any]) -> AutoPatchRegister: + # construct class + new_class = super().__new__(mcs, name, bases, dct) + if "game" in dct: + AutoPatchRegister.patch_types[dct["game"]] = new_class + if not dct["patch_file_ending"]: + raise Exception(f"Need an expected file ending for {name}") + AutoPatchRegister.file_endings[dct["patch_file_ending"]] = new_class + return new_class + + @staticmethod + def get_handler(file: str) -> Optional[AutoPatchRegister]: + for file_ending, handler in AutoPatchRegister.file_endings.items(): + if file.endswith(file_ending): + return handler + return None + + +current_patch_version: int = 5 + + +class APContainer: + """A zipfile containing at least archipelago.json""" + version: int = current_patch_version + compression_level: int = 9 + compression_method: int = zipfile.ZIP_DEFLATED + game: Optional[str] = None + + # instance attributes: + path: Optional[str] + player: Optional[int] + player_name: str + server: str + + def __init__(self, path: Optional[str] = None, player: Optional[int] = None, + player_name: str = "", server: str = ""): + self.path = path + self.player = player + self.player_name = player_name + self.server = server + + def write(self, file: Optional[Union[str, BinaryIO]] = None) -> None: + zip_file = file if file else self.path + if not zip_file: + raise FileNotFoundError(f"Cannot write {self.__class__.__name__} due to no path provided.") + with zipfile.ZipFile(zip_file, "w", self.compression_method, True, self.compression_level) \ + as zf: + if file: + self.path = zf.filename + self.write_contents(zf) + + def write_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + manifest = self.get_manifest() + try: + manifest_str = json.dumps(manifest) + except Exception as e: + raise Exception(f"Manifest {manifest} did not convert to json.") from e + else: + opened_zipfile.writestr("archipelago.json", manifest_str) + + def read(self, file: Optional[Union[str, BinaryIO]] = None) -> None: + """Read data into patch object. file can be file-like, such as an outer zip file's stream.""" + zip_file = file if file else self.path + if not zip_file: + raise FileNotFoundError(f"Cannot read {self.__class__.__name__} due to no path provided.") + with zipfile.ZipFile(zip_file, "r") as zf: + if file: + self.path = zf.filename + self.read_contents(zf) + + def read_contents(self, opened_zipfile: zipfile.ZipFile) -> None: + with opened_zipfile.open("archipelago.json", "r") as f: + manifest = json.load(f) + if manifest["compatible_version"] > self.version: + raise Exception(f"File (version: {manifest['compatible_version']}) too new " + f"for this handler (version: {self.version})") + self.player = manifest["player"] + self.server = manifest["server"] + self.player_name = manifest["player_name"] + + def get_manifest(self) -> Dict[str, Any]: + return { + "server": self.server, # allow immediate connection to server in multiworld. Empty string otherwise + "player": self.player, + "player_name": self.player_name, + "game": self.game, + # minimum version of patch system expected for patching to be successful + "compatible_version": 4, + "version": current_patch_version, + } + + +class APDeltaPatch(APContainer, metaclass=AutoPatchRegister): + """An APContainer that additionally has delta.bsdiff4 + containing a delta patch to get the desired file, often a rom.""" + + hash: Optional[str] # base checksum of source file + patch_file_ending: str = "" + delta: Optional[bytes] = None + result_file_ending: str = ".sfc" + source_data: bytes + + def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None: + self.patched_path = patched_path + super(APDeltaPatch, self).__init__(*args, **kwargs) + + def get_manifest(self) -> Dict[str, Any]: + manifest = super(APDeltaPatch, self).get_manifest() + manifest["base_checksum"] = self.hash + manifest["result_file_ending"] = self.result_file_ending + manifest["patch_file_ending"] = self.patch_file_ending + return manifest + + @classmethod + def get_source_data(cls) -> bytes: + """Get Base data""" + raise NotImplementedError() + + @classmethod + def get_source_data_with_cache(cls) -> bytes: + if not hasattr(cls, "source_data"): + cls.source_data = cls.get_source_data() + return cls.source_data + + def write_contents(self, opened_zipfile: zipfile.ZipFile): + super(APDeltaPatch, self).write_contents(opened_zipfile) + # write Delta + opened_zipfile.writestr("delta.bsdiff4", + bsdiff4.diff(self.get_source_data_with_cache(), open(self.patched_path, "rb").read()), + compress_type=zipfile.ZIP_STORED) # bsdiff4 is a format with integrated compression + + def read_contents(self, opened_zipfile: zipfile.ZipFile): + super(APDeltaPatch, self).read_contents(opened_zipfile) + self.delta = opened_zipfile.read("delta.bsdiff4") + + def patch(self, target: str): + """Base + Delta -> Patched""" + if not self.delta: + self.read() + result = bsdiff4.patch(self.get_source_data_with_cache(), self.delta) + with open(target, "wb") as f: + f.write(result) diff --git a/worlds/__init__.py b/worlds/__init__.py index e36eb275a3..039f12eb93 100644 --- a/worlds/__init__.py +++ b/worlds/__init__.py @@ -1,7 +1,9 @@ import importlib -import zipimport import os +import sys import typing +import warnings +import zipimport folder = os.path.dirname(__file__) @@ -39,7 +41,14 @@ world_sources.sort() for world_source in world_sources: if world_source.is_zip: importer = zipimport.zipimporter(os.path.join(folder, world_source.path)) - importer.load_module(world_source.path.split(".", 1)[0]) + spec = importer.find_spec(world_source.path.split(".", 1)[0]) + mod = importlib.util.module_from_spec(spec) + mod.__package__ = f"worlds.{mod.__package__}" + mod.__name__ = f"worlds.{mod.__name__}" + sys.modules[mod.__name__] = mod + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", message="__package__ != __spec__.parent") + importer.exec_module(mod) else: importlib.import_module(f".{world_source.path}", "worlds") diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 9ca3a355e1..24a1588c52 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -1,11 +1,12 @@ from __future__ import annotations import Utils -from Patch import read_rom +import worlds.AutoWorld +import worlds.Files -LTTPJPN10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '9952c2a3ec1b421e408df0d20c8f0c7f' -ROM_PLAYER_LIMIT = 255 +LTTPJPN10HASH: str = "03a63945398191337e896e5771f77173" +RANDOMIZERBASEHASH: str = "9952c2a3ec1b421e408df0d20c8f0c7f" +ROM_PLAYER_LIMIT: int = 255 import io import json @@ -34,7 +35,7 @@ from worlds.alttp.Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts DeathMountain_texts, \ LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, \ SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names -from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen, parse_yaml +from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to_pc, is_frozen, parse_yaml, read_snes_rom from worlds.alttp.Items import ItemFactory, item_table, item_name_groups, progression_items from worlds.alttp.EntranceShuffle import door_addresses from worlds.alttp.Options import smallkey_shuffle @@ -57,13 +58,13 @@ class LocalRom(object): self.orig_buffer = None with open(file, 'rb') as stream: - self.buffer = read_rom(stream) + self.buffer = read_snes_rom(stream) if patch: self.patch_base_rom() self.orig_buffer = self.buffer.copy() if vanillaRom: with open(vanillaRom, 'rb') as vanillaStream: - self.orig_buffer = read_rom(vanillaStream) + self.orig_buffer = read_snes_rom(vanillaStream) def read_byte(self, address: int) -> int: return self.buffer[address] @@ -123,29 +124,24 @@ class LocalRom(object): return expected == buffermd5.hexdigest() def patch_base_rom(self): - if os.path.isfile(local_path('basepatch.sfc')): - with open(local_path('basepatch.sfc'), 'rb') as stream: + if os.path.isfile(user_path('basepatch.sfc')): + with open(user_path('basepatch.sfc'), 'rb') as stream: buffer = bytearray(stream.read()) if self.verify(buffer): self.buffer = buffer - if not os.path.exists(local_path('data', 'basepatch.apbp')): - Patch.create_patch_file(local_path('basepatch.sfc')) return - if not os.path.isfile(local_path('data', 'basepatch.apbp')): - raise RuntimeError('Base patch unverified. Unable to continue.') + with open(local_path("data", "basepatch.bsdiff4"), "rb") as f: + delta = f.read() - if os.path.isfile(local_path('data', 'basepatch.apbp')): - _, target, buffer = Patch.create_rom_bytes(local_path('data', 'basepatch.apbp'), ignore_version=True) - if self.verify(buffer): - self.buffer = bytearray(buffer) - with open(user_path('basepatch.sfc'), 'wb') as stream: - stream.write(buffer) - return - raise RuntimeError('Base patch unverified. Unable to continue.') - - raise RuntimeError('Could not find Base Patch. Unable to continue.') + buffer = bsdiff4.patch(get_base_rom_bytes(), delta) + if self.verify(buffer): + self.buffer = bytearray(buffer) + with open(user_path('basepatch.sfc'), 'wb') as stream: + stream.write(buffer) + return + raise RuntimeError('Base patch unverified. Unable to continue.') def write_crc(self): crc = (sum(self.buffer[:0x7FDC] + self.buffer[0x7FE0:]) + 0x01FE) & 0xFFFF @@ -544,7 +540,7 @@ class Sprite(): def get_vanilla_sprite_data(self): file_name = get_base_rom_path() - base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + base_rom_bytes = bytes(read_snes_rom(open(file_name, "rb"))) Sprite.sprite = base_rom_bytes[0x80000:0x87000] Sprite.palette = base_rom_bytes[0xDD308:0xDD380] Sprite.glove_palette = base_rom_bytes[0xDEDF5:0xDEDF9] @@ -2906,7 +2902,7 @@ hash_alphabet = [ ] -class LttPDeltaPatch(Patch.APDeltaPatch): +class LttPDeltaPatch(worlds.Files.APDeltaPatch): hash = LTTPJPN10HASH game = "A Link to the Past" patch_file_ending = ".aplttp" @@ -2920,7 +2916,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: file_name = get_base_rom_path(file_name) - base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + base_rom_bytes = bytes(read_snes_rom(open(file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(base_rom_bytes) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index bbdd941127..169d21ab8c 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -7,7 +7,7 @@ import typing import Utils from BaseClasses import Item, CollectionState, Tutorial from .Dungeons import create_dungeons -from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect +from .EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect, indirect_connections 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 @@ -19,7 +19,7 @@ from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enem from .Rules import set_rules from .Shops import create_shops, ShopSlotFill from .SubClasses import ALttPItem -from ..AutoWorld import World, WebWorld, LogicMixin +from worlds.AutoWorld import World, WebWorld, LogicMixin lttp_logger = logging.getLogger("A Link to the Past") @@ -223,6 +223,10 @@ class ALTTPWorld(World): world.random = old_random plando_connect(world, player) + for region_name, entrance_name in indirect_connections.items(): + world.register_indirect_condition(self.world.get_region(region_name, player), + self.world.get_entrance(entrance_name, player)) + def collect_item(self, state: CollectionState, item: Item, remove=False): item_name = item.name if item_name.startswith('Progressive '): @@ -396,11 +400,7 @@ class ALTTPWorld(World): deathlink=world.death_link[player], allowcollect=world.allow_collect[player]) - outfilepname = f'_P{player}' - outfilepname += f"_{world.get_file_safe_player_name(player).replace(' ', '_')}" \ - if world.player_name[player] != 'Player%d' % player else '' - - rompath = os.path.join(output_directory, f'AP_{world.seed_name}{outfilepname}.sfc') + rompath = os.path.join(output_directory, f"{self.world.get_out_file_name_base(self.player)}.sfc") rom.write_to_file(rompath) patch = LttPDeltaPatch(os.path.splitext(rompath)[0]+LttPDeltaPatch.patch_file_ending, player=player, player_name=world.player_name[player], patched_path=rompath) diff --git a/worlds/alttp/docs/multiworld_de.md b/worlds/alttp/docs/multiworld_de.md index 877f4abe83..417bb8acff 100644 --- a/worlds/alttp/docs/multiworld_de.md +++ b/worlds/alttp/docs/multiworld_de.md @@ -6,7 +6,7 @@ - [SNI](https://github.com/alttpo/sni/releases) (Integriert in Archipelago) - Hardware oder Software zum Laden und Abspielen von SNES Rom-Dateien fähig zu einer Internetverbindung - Ein Emulator, der mit SNI verbinden kann - ([snes9x Multitroid](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + ([snes9x rr](https://github.com/gocha/snes9x-rr/releases), [BizHawk](http://tasvideos.org/BizHawk.html)) - Ein SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), oder andere kompatible Hardware - Die Japanische Zelda 1.0 ROM-Datei, mit folgendem Namen: `Zelda no Densetsu - Kamigami no Triforce (Japan).sfc` @@ -93,7 +93,7 @@ Wenn der client den Emulator automatisch gestartet hat, wird SNI ebenfalls im Hi Mal ist, wird möglicherweise ein Fenster angezeigt, wo man bestätigen muss, dass das Programm durch die Windows Firewall kommunizieren darf. -##### snes9x Multitroid +##### snes9x-rr 1. Lade die Entsprechende ROM-Datei, wenn sie nicht schon automatisch geladen wurde. 2. Klicke auf den Reiter "File" oben im MenĂ¼ und wähle **Lua Scripting** diff --git a/worlds/alttp/docs/multiworld_en.md b/worlds/alttp/docs/multiworld_en.md index 1e6b2d1044..e758edc22c 100644 --- a/worlds/alttp/docs/multiworld_en.md +++ b/worlds/alttp/docs/multiworld_en.md @@ -75,7 +75,7 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. -##### snes9x Multitroid +##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. 2. Click on the File menu and hover on **Lua Scripting** diff --git a/worlds/alttp/docs/multiworld_es.md b/worlds/alttp/docs/multiworld_es.md index da0b9d99bf..ca33f796c2 100644 --- a/worlds/alttp/docs/multiworld_es.md +++ b/worlds/alttp/docs/multiworld_es.md @@ -12,7 +12,7 @@ - [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Incluido en Multiworld Utilities) - Hardware o software capaz de cargar y ejecutar archivos de ROM de SNES - Un emulador capaz de ejecutar scripts Lua - ([snes9x Multitroid](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + ([snes9x rr](https://github.com/gocha/snes9x-rr/releases), [BizHawk](http://tasvideos.org/BizHawk.html), o [RetroArch](https://retroarch.com?page=platforms) 1.10.1 o mĂ¡s nuevo). O, - Un flashcart SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), o otro hardware compatible @@ -111,13 +111,13 @@ automĂ¡ticamente el cliente, y ademas creara la rom en el mismo directorio donde Cuando el cliente se lance automĂ¡ticamente, QUsb2Snes deberĂ­a haberse ejecutado tambiĂ©n. Si es la primera vez que lo ejecutas, puedes ser que el firewall de Windows te pregunte si le permites la comunicaciĂ³n. -##### snes9x Multitroid +##### snes9x-rr 1. Carga tu fichero de ROM, si no lo has hecho ya 2. Abre el menu "File" y situa el raton en **Lua Scripting** 3. Haz click en **New Lua Script Window...** 4. En la nueva ventana, haz click en **Browse...** -5. Navega hacia el directorio donde este situado snes9x Multitroid, entra en el directorio `lua`, y +5. Navega hacia el directorio donde este situado snes9x-rr, entra en el directorio `lua`, y escoge `multibridge.lua` 6. Observa que se ha asignado un nombre al dispositivo, y el cliente muestra "SNES Device: Connected", con el mismo nombre en la esquina superior izquierda. diff --git a/worlds/alttp/docs/multiworld_fr.md b/worlds/alttp/docs/multiworld_fr.md index 4a8ca5902e..380a010232 100644 --- a/worlds/alttp/docs/multiworld_fr.md +++ b/worlds/alttp/docs/multiworld_fr.md @@ -12,7 +12,7 @@ - [QUsb2Snes](https://github.com/Skarsnik/QUsb2snes/releases) (Inclus dans les utilitaires prĂ©cĂ©dents) - Une solution logicielle ou matĂ©rielle capable de charger et de lancer des fichiers ROM de SNES - Un Ă©mulateur capable d'Ă©xĂ©cuter des scripts Lua - ([snes9x Multitroid](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + ([snes9x rr](https://github.com/gocha/snes9x-rr/releases), [BizHawk](http://tasvideos.org/BizHawk.html)) - Un SD2SNES, [FXPak Pro](https://krikzz.com/store/home/54-fxpak-pro.html), ou une autre solution matĂ©rielle compatible @@ -112,13 +112,13 @@ Quand le client se lance automatiquement, QUsb2Snes devrait se lancer automatiqu c'est la première fois qu'il dĂ©marre, il vous sera peut-Ăªtre demandĂ© de l'autoriser Ă  communiquer Ă  travers le pare-feu Windows. -##### snes9x Multitroid +##### snes9x-rr 1. Chargez votre ROM si ce n'est pas dĂ©jĂ  fait. 2. Cliquez sur le menu "File" et survolez l'option **Lua Scripting** 3. Cliquez alors sur **New Lua Script Window...** 4. Dans la nouvelle fenĂªtre, sĂ©lectionnez **Browse...** -5. Dirigez vous vers le dossier oĂ¹ vous avez extrait snes9x Multitroid, allez dans le dossier `lua`, puis +5. Dirigez vous vers le dossier oĂ¹ vous avez extrait snes9x-rr, allez dans le dossier `lua`, puis choisissez `multibridge.lua` 6. Remarquez qu'un nom vous a Ă©tĂ© assignĂ©, et que l'interface Web affiche "SNES Device: Connected", avec ce mĂªme nom dans le coin en haut Ă  gauche. diff --git a/worlds/dkc3/Rom.py b/worlds/dkc3/Rom.py index 2bb5221a60..6308e95860 100644 --- a/worlds/dkc3/Rom.py +++ b/worlds/dkc3/Rom.py @@ -1,5 +1,6 @@ import Utils -from Patch import read_rom, APDeltaPatch +from Utils import read_snes_rom +from worlds.Files import APDeltaPatch from .Locations import lookup_id_to_name, all_locations from .Levels import level_list, level_dict @@ -440,13 +441,13 @@ class LocalRom(object): self.orig_buffer = None with open(file, 'rb') as stream: - self.buffer = read_rom(stream) + self.buffer = read_snes_rom(stream) #if patch: # self.patch_rom() # self.orig_buffer = self.buffer.copy() #if vanillaRom: # with open(vanillaRom, 'rb') as vanillaStream: - # self.orig_buffer = read_rom(vanillaStream) + # self.orig_buffer = read_snes_rom(vanillaStream) def read_bit(self, address: int, bit_number: int) -> bool: bitflag = (1 << bit_number) @@ -724,7 +725,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: file_name = get_base_rom_path(file_name) - base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + base_rom_bytes = bytes(read_snes_rom(open(file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(base_rom_bytes) diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py index 5c575b85b5..1389f83efd 100644 --- a/worlds/dkc3/__init__.py +++ b/worlds/dkc3/__init__.py @@ -146,11 +146,7 @@ class DKC3World(World): self.active_level_list.append(LocationName.rocket_rush_region) - outfilepname = f'_P{player}' - outfilepname += f"_{world.player_name[player].replace(' ', '_')}" \ - if world.player_name[player] != 'Player%d' % player else '' - - rompath = os.path.join(output_directory, f'AP_{world.seed_name}{outfilepname}.sfc') + rompath = os.path.join(output_directory, f"{self.world.get_out_file_name_base(self.player)}.sfc") rom.write_to_file(rompath) self.rom_name = rom.name diff --git a/worlds/dkc3/docs/setup_en.md b/worlds/dkc3/docs/setup_en.md index 471248deb8..0ebe189a1e 100644 --- a/worlds/dkc3/docs/setup_en.md +++ b/worlds/dkc3/docs/setup_en.md @@ -7,8 +7,7 @@ - Hardware or software capable of loading and playing SNES ROM files - An emulator capable of connecting to SNI such as: - - snes9x Multitroid - from: [snes9x Multitroid Download](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) - RetroArch 1.10.3 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other @@ -81,7 +80,7 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. -##### snes9x Multitroid +##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. 2. Click on the File menu and hover on **Lua Scripting** diff --git a/worlds/factorio/Mod.py b/worlds/factorio/Mod.py index 89666ffbdd..018816d90a 100644 --- a/worlds/factorio/Mod.py +++ b/worlds/factorio/Mod.py @@ -11,6 +11,8 @@ import shutil import Utils import Patch +import worlds.AutoWorld +import worlds.Files from . import Options from .Technologies import tech_table, recipes, free_sample_exclusions, progressive_technology_table, \ @@ -57,7 +59,7 @@ recipe_time_ranges = { } -class FactorioModFile(Patch.APContainer): +class FactorioModFile(worlds.Files.APContainer): game = "Factorio" compression_method = zipfile.ZIP_DEFLATED # Factorio can't load LZMA archives diff --git a/worlds/generic/Rules.py b/worlds/generic/Rules.py index f53c417e1c..9b338e4d70 100644 --- a/worlds/generic/Rules.py +++ b/worlds/generic/Rules.py @@ -1,6 +1,6 @@ import typing -from BaseClasses import LocationProgressType +from BaseClasses import LocationProgressType, MultiWorld if typing.TYPE_CHECKING: import BaseClasses @@ -37,14 +37,14 @@ def locality_rules(world, player: int): forbid_items_for_player(location, world.non_local_items[player].value, player) -def exclusion_rules(world, player: int, exclude_locations: typing.Set[str]): +def exclusion_rules(world: MultiWorld, player: int, exclude_locations: typing.Set[str]) -> None: for loc_name in exclude_locations: try: location = world.get_location(loc_name, player) except KeyError as e: # failed to find the given location. Check if it's a legitimate location if loc_name not in world.worlds[player].location_name_to_id: raise Exception(f"Unable to exclude location {loc_name} in player {player}'s world.") from e - else: + else: add_item_rule(location, lambda i: not (i.advancement or i.useful)) location.progress_type = LocationProgressType.EXCLUDED diff --git a/worlds/minecraft/__init__.py b/worlds/minecraft/__init__.py index 6e7addb2d0..fd5752bd40 100644 --- a/worlds/minecraft/__init__.py +++ b/worlds/minecraft/__init__.py @@ -150,7 +150,7 @@ class MinecraftWorld(World): def generate_output(self, output_directory: str): data = self._get_mc_data() - filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_file_safe_player_name(self.player)}.apmc" + filename = f"{self.world.get_out_file_name_base(self.player)}.apmc" with open(os.path.join(output_directory, filename), 'wb') as f: f.write(b64encode(bytes(json.dumps(data), 'utf-8'))) diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py index b65882c874..2536c3d4c9 100644 --- a/worlds/oot/__init__.py +++ b/worlds/oot/__init__.py @@ -819,7 +819,7 @@ class OOTWorld(World): # Seed hint RNG, used for ganon text lines also self.hint_rng = self.world.slot_seeds[self.player] - outfile_name = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_file_safe_player_name(self.player)}" + outfile_name = self.world.get_out_file_name_base(self.player) rom = Rom(file=get_options()['oot_options']['rom_file']) if self.hints != 'none': buildWorldGossipHints(self) diff --git a/worlds/sc2wol/Locations.py b/worlds/sc2wol/Locations.py index f69abd48e3..14dd25fd52 100644 --- a/worlds/sc2wol/Locations.py +++ b/worlds/sc2wol/Locations.py @@ -27,13 +27,10 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Liberation Day", "Liberation Day: Fourth Statue", SC2WOL_LOC_ID_OFFSET + 104), LocationData("Liberation Day", "Liberation Day: Fifth Statue", SC2WOL_LOC_ID_OFFSET + 105), LocationData("Liberation Day", "Liberation Day: Sixth Statue", SC2WOL_LOC_ID_OFFSET + 106), - LocationData("Liberation Day", "Beat Liberation Day", None), LocationData("The Outlaws", "The Outlaws: Victory", SC2WOL_LOC_ID_OFFSET + 200, lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("The Outlaws", "The Outlaws: Rebel Base", SC2WOL_LOC_ID_OFFSET + 201, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("The Outlaws", "Beat The Outlaws", None, - lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Zero Hour", "Zero Hour: Victory", SC2WOL_LOC_ID_OFFSET + 300, lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Zero Hour", "Zero Hour: First Group Rescued", SC2WOL_LOC_ID_OFFSET + 301), @@ -41,26 +38,20 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Zero Hour", "Zero Hour: Third Group Rescued", SC2WOL_LOC_ID_OFFSET + 303, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("Zero Hour", "Beat Zero Hour", None, - lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Evacuation", "Evacuation: Victory", SC2WOL_LOC_ID_OFFSET + 400, - lambda state: state._sc2wol_has_anti_air(world, player)), + lambda state: state._sc2wol_has_common_unit(world, player) and + state._sc2wol_has_competent_anti_air(world, player)), LocationData("Evacuation", "Evacuation: First Chysalis", SC2WOL_LOC_ID_OFFSET + 401), LocationData("Evacuation", "Evacuation: Second Chysalis", SC2WOL_LOC_ID_OFFSET + 402, lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Evacuation", "Evacuation: Third Chysalis", SC2WOL_LOC_ID_OFFSET + 403, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("Evacuation", "Beat Evacuation", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_competent_anti_air(world, player)), LocationData("Outbreak", "Outbreak: Victory", SC2WOL_LOC_ID_OFFSET + 500, lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), LocationData("Outbreak", "Outbreak: Left Infestor", SC2WOL_LOC_ID_OFFSET + 501, lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), LocationData("Outbreak", "Outbreak: Right Infestor", SC2WOL_LOC_ID_OFFSET + 502, lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), - LocationData("Outbreak", "Beat Outbreak", None, - lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), LocationData("Safe Haven", "Safe Haven: Victory", SC2WOL_LOC_ID_OFFSET + 600, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), @@ -73,9 +64,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Safe Haven", "Safe Haven: South Nexus", SC2WOL_LOC_ID_OFFSET + 603, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), - LocationData("Safe Haven", "Beat Safe Haven", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_competent_anti_air(world, player)), LocationData("Haven's Fall", "Haven's Fall: Victory", SC2WOL_LOC_ID_OFFSET + 700, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), @@ -88,9 +76,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Haven's Fall", "Haven's Fall: South Hive", SC2WOL_LOC_ID_OFFSET + 703, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), - LocationData("Haven's Fall", "Beat Haven's Fall", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_competent_anti_air(world, player)), LocationData("Smash and Grab", "Smash and Grab: Victory", SC2WOL_LOC_ID_OFFSET + 800, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), @@ -101,9 +86,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Smash and Grab", "Smash and Grab: Fourth Relic", SC2WOL_LOC_ID_OFFSET + 804, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_anti_air(world, player)), - LocationData("Smash and Grab", "Beat Smash and Grab", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_anti_air(world, player)), LocationData("The Dig", "The Dig: Victory", SC2WOL_LOC_ID_OFFSET + 900, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_anti_air(world, player) and @@ -114,10 +96,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("The Dig", "The Dig: Right Cliff Relic", SC2WOL_LOC_ID_OFFSET + 903, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("The Dig", "Beat The Dig", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_anti_air(world, player) and - state._sc2wol_has_heavy_defense(world, player)), LocationData("The Moebius Factor", "The Moebius Factor: Victory", SC2WOL_LOC_ID_OFFSET + 1000, lambda state: state._sc2wol_has_air(world, player) and state._sc2wol_has_anti_air(world, player)), LocationData("The Moebius Factor", "The Moebius Factor: South Rescue", SC2WOL_LOC_ID_OFFSET + 1003, @@ -132,8 +110,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_able_to_rescue(world, player)), LocationData("The Moebius Factor", "The Moebius Factor: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1008, lambda state: state._sc2wol_has_air(world, player)), - LocationData("The Moebius Factor", "Beat The Moebius Factor", None, - lambda state: state._sc2wol_has_air(world, player)), LocationData("Supernova", "Supernova: Victory", SC2WOL_LOC_ID_OFFSET + 1100, lambda state: state._sc2wol_beats_protoss_deathball(world, player)), LocationData("Supernova", "Supernova: West Relic", SC2WOL_LOC_ID_OFFSET + 1101), @@ -142,8 +118,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_beats_protoss_deathball(world, player)), LocationData("Supernova", "Supernova: East Relic", SC2WOL_LOC_ID_OFFSET + 1104, lambda state: state._sc2wol_beats_protoss_deathball(world, player)), - LocationData("Supernova", "Beat Supernova", None, - lambda state: state._sc2wol_beats_protoss_deathball(world, player)), LocationData("Maw of the Void", "Maw of the Void: Victory", SC2WOL_LOC_ID_OFFSET + 1200, lambda state: state.has('Battlecruiser', player) or state._sc2wol_has_air(world, player) and @@ -170,19 +144,12 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L state._sc2wol_has_air(world, player) and state._sc2wol_has_competent_anti_air(world, player) and state.has('Science Vessel', player)), - LocationData("Maw of the Void", "Beat Maw of the Void", None, - lambda state: state.has('Battlecruiser', player) or - state._sc2wol_has_air(world, player) and - state._sc2wol_has_competent_anti_air(world, player) and - state.has('Science Vessel', player)), LocationData("Devil's Playground", "Devil's Playground: Victory", SC2WOL_LOC_ID_OFFSET + 1300, lambda state: state._sc2wol_has_anti_air(world, player) and ( state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player))), LocationData("Devil's Playground", "Devil's Playground: Tosh's Miners", SC2WOL_LOC_ID_OFFSET + 1301), LocationData("Devil's Playground", "Devil's Playground: Brutalisk", SC2WOL_LOC_ID_OFFSET + 1302, lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), - LocationData("Devil's Playground", "Beat Devil's Playground", None, - lambda state: state._sc2wol_has_common_unit(world, player) or state.has("Reaper", player)), LocationData("Welcome to the Jungle", "Welcome to the Jungle: Victory", SC2WOL_LOC_ID_OFFSET + 1400, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), @@ -193,28 +160,21 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Welcome to the Jungle", "Welcome to the Jungle: North-East Relic", SC2WOL_LOC_ID_OFFSET + 1403, lambda state: state._sc2wol_has_common_unit(world, player) and state._sc2wol_has_competent_anti_air(world, player)), - LocationData("Welcome to the Jungle", "Beat Welcome to the Jungle", None, - lambda state: state._sc2wol_has_common_unit(world, player) and - state._sc2wol_has_competent_anti_air(world, player)), LocationData("Breakout", "Breakout: Victory", SC2WOL_LOC_ID_OFFSET + 1500), LocationData("Breakout", "Breakout: Diamondback Prison", SC2WOL_LOC_ID_OFFSET + 1501), LocationData("Breakout", "Breakout: Siegetank Prison", SC2WOL_LOC_ID_OFFSET + 1502), - LocationData("Breakout", "Beat Breakout", None), LocationData("Ghost of a Chance", "Ghost of a Chance: Victory", SC2WOL_LOC_ID_OFFSET + 1600), LocationData("Ghost of a Chance", "Ghost of a Chance: Terrazine Tank", SC2WOL_LOC_ID_OFFSET + 1601), LocationData("Ghost of a Chance", "Ghost of a Chance: Jorium Stockpile", SC2WOL_LOC_ID_OFFSET + 1602), LocationData("Ghost of a Chance", "Ghost of a Chance: First Island Spectres", SC2WOL_LOC_ID_OFFSET + 1603), LocationData("Ghost of a Chance", "Ghost of a Chance: Second Island Spectres", SC2WOL_LOC_ID_OFFSET + 1604), LocationData("Ghost of a Chance", "Ghost of a Chance: Third Island Spectres", SC2WOL_LOC_ID_OFFSET + 1605), - LocationData("Ghost of a Chance", "Beat Ghost of a Chance", None), LocationData("The Great Train Robbery", "The Great Train Robbery: Victory", SC2WOL_LOC_ID_OFFSET + 1700, lambda state: state._sc2wol_has_train_killers(world, player) and state._sc2wol_has_anti_air(world, player)), LocationData("The Great Train Robbery", "The Great Train Robbery: North Defiler", SC2WOL_LOC_ID_OFFSET + 1701), LocationData("The Great Train Robbery", "The Great Train Robbery: Mid Defiler", SC2WOL_LOC_ID_OFFSET + 1702), LocationData("The Great Train Robbery", "The Great Train Robbery: South Defiler", SC2WOL_LOC_ID_OFFSET + 1703), - LocationData("The Great Train Robbery", "Beat The Great Train Robbery", None, - lambda state: state._sc2wol_has_train_killers(world, player)), LocationData("Cutthroat", "Cutthroat: Victory", SC2WOL_LOC_ID_OFFSET + 1800, lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Cutthroat", "Cutthroat: Mira Han", SC2WOL_LOC_ID_OFFSET + 1801, @@ -224,10 +184,9 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Cutthroat", "Cutthroat: Mid Relic", SC2WOL_LOC_ID_OFFSET + 1803), LocationData("Cutthroat", "Cutthroat: Southwest Relic", SC2WOL_LOC_ID_OFFSET + 1804, lambda state: state._sc2wol_has_common_unit(world, player)), - LocationData("Cutthroat", "Beat Cutthroat", None, - lambda state: state._sc2wol_has_common_unit(world, player)), LocationData("Engine of Destruction", "Engine of Destruction: Victory", SC2WOL_LOC_ID_OFFSET + 1900, - lambda state: state._sc2wol_has_competent_anti_air(world, player)), + lambda state: state._sc2wol_has_competent_anti_air(world, player) and + state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)), LocationData("Engine of Destruction", "Engine of Destruction: Odin", SC2WOL_LOC_ID_OFFSET + 1901), LocationData("Engine of Destruction", "Engine of Destruction: Loki", SC2WOL_LOC_ID_OFFSET + 1902, lambda state: state._sc2wol_has_competent_anti_air(world, player) and @@ -239,9 +198,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Engine of Destruction", "Engine of Destruction: Southeast Devourer", SC2WOL_LOC_ID_OFFSET + 1905, lambda state: state._sc2wol_has_competent_anti_air(world, player) and state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)), - LocationData("Engine of Destruction", "Beat Engine of Destruction", None, - lambda state: state._sc2wol_has_competent_anti_air(world, player) and - state._sc2wol_has_common_unit(world, player) or state.has('Wraith', player)), LocationData("Media Blitz", "Media Blitz: Victory", SC2WOL_LOC_ID_OFFSET + 2000, lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Media Blitz", "Media Blitz: Tower 1", SC2WOL_LOC_ID_OFFSET + 2001, @@ -251,8 +207,6 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L LocationData("Media Blitz", "Media Blitz: Tower 3", SC2WOL_LOC_ID_OFFSET + 2003, lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Media Blitz", "Media Blitz: Science Facility", SC2WOL_LOC_ID_OFFSET + 2004), - LocationData("Media Blitz", "Beat Media Blitz", None, - lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Piercing the Shroud", "Piercing the Shroud: Victory", SC2WOL_LOC_ID_OFFSET + 2100, lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)), LocationData("Piercing the Shroud", "Piercing the Shroud: Holding Cell Relic", SC2WOL_LOC_ID_OFFSET + 2101), @@ -264,44 +218,34 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)), LocationData("Piercing the Shroud", "Piercing the Shroud: Brutalisk ", SC2WOL_LOC_ID_OFFSET + 2105, lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)), - LocationData("Piercing the Shroud", "Beat Piercing the Shroud", None, - lambda state: state.has_any({'Combat Shield (Marine)', 'Stabilizer Medpacks (Medic)'}, player)), LocationData("Whispers of Doom", "Whispers of Doom: Victory", SC2WOL_LOC_ID_OFFSET + 2200), LocationData("Whispers of Doom", "Whispers of Doom: First Hatchery", SC2WOL_LOC_ID_OFFSET + 2201), LocationData("Whispers of Doom", "Whispers of Doom: Second Hatchery", SC2WOL_LOC_ID_OFFSET + 2202), LocationData("Whispers of Doom", "Whispers of Doom: Third Hatchery", SC2WOL_LOC_ID_OFFSET + 2203), - LocationData("Whispers of Doom", "Beat Whispers of Doom", None), LocationData("A Sinister Turn", "A Sinister Turn: Victory", SC2WOL_LOC_ID_OFFSET + 2300, lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("A Sinister Turn", "A Sinister Turn: Robotics Facility", SC2WOL_LOC_ID_OFFSET + 2301), LocationData("A Sinister Turn", "A Sinister Turn: Dark Shrine", SC2WOL_LOC_ID_OFFSET + 2302), LocationData("A Sinister Turn", "A Sinister Turn: Templar Archives", SC2WOL_LOC_ID_OFFSET + 2303, lambda state: state._sc2wol_has_protoss_common_units(world, player)), - LocationData("A Sinister Turn", "Beat A Sinister Turn", None, - lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("Echoes of the Future", "Echoes of the Future: Victory", SC2WOL_LOC_ID_OFFSET + 2400, lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("Echoes of the Future", "Echoes of the Future: Close Obelisk", SC2WOL_LOC_ID_OFFSET + 2401), LocationData("Echoes of the Future", "Echoes of the Future: West Obelisk", SC2WOL_LOC_ID_OFFSET + 2402, lambda state: state._sc2wol_has_protoss_common_units(world, player)), - LocationData("Echoes of the Future", "Beat Echoes of the Future", None, - lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("In Utter Darkness", "In Utter Darkness: Defeat", SC2WOL_LOC_ID_OFFSET + 2500), LocationData("In Utter Darkness", "In Utter Darkness: Protoss Archive", SC2WOL_LOC_ID_OFFSET + 2501, lambda state: state._sc2wol_has_protoss_medium_units(world, player)), LocationData("In Utter Darkness", "In Utter Darkness: Kills", SC2WOL_LOC_ID_OFFSET + 2502, lambda state: state._sc2wol_has_protoss_common_units(world, player)), - LocationData("In Utter Darkness", "Beat In Utter Darkness", None), LocationData("Gates of Hell", "Gates of Hell: Victory", SC2WOL_LOC_ID_OFFSET + 2600, lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Gates of Hell", "Gates of Hell: Large Army", SC2WOL_LOC_ID_OFFSET + 2601, lambda state: state._sc2wol_has_competent_comp(world, player)), - LocationData("Gates of Hell", "Beat Gates of Hell", None), LocationData("Belly of the Beast", "Belly of the Beast: Victory", SC2WOL_LOC_ID_OFFSET + 2700), LocationData("Belly of the Beast", "Belly of the Beast: First Charge", SC2WOL_LOC_ID_OFFSET + 2701), LocationData("Belly of the Beast", "Belly of the Beast: Second Charge", SC2WOL_LOC_ID_OFFSET + 2702), LocationData("Belly of the Beast", "Belly of the Beast: Third Charge", SC2WOL_LOC_ID_OFFSET + 2703), - LocationData("Belly of the Beast", "Beat Belly of the Beast", None), LocationData("Shatter the Sky", "Shatter the Sky: Victory", SC2WOL_LOC_ID_OFFSET + 2800, lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Shatter the Sky", "Shatter the Sky: Close Coolant Tower", SC2WOL_LOC_ID_OFFSET + 2801, @@ -314,9 +258,15 @@ def get_locations(world: Optional[MultiWorld], player: Optional[int]) -> Tuple[L lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("Shatter the Sky", "Shatter the Sky: Leviathan", SC2WOL_LOC_ID_OFFSET + 2805, lambda state: state._sc2wol_has_competent_comp(world, player)), - LocationData("Shatter the Sky", "Beat Shatter the Sky", None, - lambda state: state._sc2wol_has_competent_comp(world, player)), LocationData("All-In", "All-In: Victory", None) ] - return tuple(location_table) + beat_events = [] + + for location_data in location_table: + if location_data.name.endswith((": Victory", ": Defeat")): + beat_events.append( + location_data._replace(name="Beat " + location_data.name.rsplit(": ", 1)[0], code=None) + ) + + return tuple(location_table + beat_events) diff --git a/worlds/sc2wol/LogicMixin.py b/worlds/sc2wol/LogicMixin.py index 7a08142672..52bb6b09a8 100644 --- a/worlds/sc2wol/LogicMixin.py +++ b/worlds/sc2wol/LogicMixin.py @@ -1,5 +1,5 @@ from BaseClasses import MultiWorld -from ..AutoWorld import LogicMixin +from worlds.AutoWorld import LogicMixin class SC2WoLLogic(LogicMixin): diff --git a/worlds/sc2wol/Regions.py b/worlds/sc2wol/Regions.py index 4e20752982..8219a982c9 100644 --- a/worlds/sc2wol/Regions.py +++ b/worlds/sc2wol/Regions.py @@ -1,8 +1,8 @@ -from typing import List, Set, Dict, Tuple, Optional, Callable, NamedTuple +from typing import List, Set, Dict, Tuple, Optional, Callable from BaseClasses import MultiWorld, Region, Entrance, Location, RegionType from .Locations import LocationData from .Options import get_option_value -from worlds.sc2wol.MissionTables import MissionInfo, vanilla_shuffle_order, vanilla_mission_req_table, \ +from .MissionTables import MissionInfo, vanilla_shuffle_order, vanilla_mission_req_table, \ no_build_regions_list, easy_regions_list, medium_regions_list, hard_regions_list import random diff --git a/worlds/sc2wol/__init__.py b/worlds/sc2wol/__init__.py index 4f9b33609f..6d056df808 100644 --- a/worlds/sc2wol/__init__.py +++ b/worlds/sc2wol/__init__.py @@ -1,15 +1,14 @@ import typing -from typing import List, Set, Tuple, NamedTuple +from typing import List, Set, Tuple from BaseClasses import Item, MultiWorld, Location, Tutorial, ItemClassification -from ..AutoWorld import WebWorld +from worlds.AutoWorld import WebWorld, World from .Items import StarcraftWoLItem, item_table, filler_items, item_name_groups, get_full_item_list, \ basic_unit from .Locations import get_locations from .Regions import create_regions from .Options import sc2wol_options, get_option_value from .LogicMixin import SC2WoLLogic -from ..AutoWorld import World class Starcraft2WoLWebWorld(WebWorld): diff --git a/worlds/sm/Rom.py b/worlds/sm/Rom.py index e2957fe00f..e5f5bc7a37 100644 --- a/worlds/sm/Rom.py +++ b/worlds/sm/Rom.py @@ -3,7 +3,8 @@ import os import json import Utils -from Patch import read_rom, APDeltaPatch +from Utils import read_snes_rom +from worlds.Files import APDeltaPatch SMJUHASH = '21f3e98df4780ee1c667b84e57d88675' ROM_PLAYER_LIMIT = 65535 # max archipelago player ID. note, SM ROM itself will only store 201 names+ids max @@ -22,7 +23,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: file_name = get_base_rom_path(file_name) - base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + base_rom_bytes = bytes(read_snes_rom(open(file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(base_rom_bytes) diff --git a/worlds/sm/__init__.py b/worlds/sm/__init__.py index 0bf12ca7eb..d901303215 100644 --- a/worlds/sm/__init__.py +++ b/worlds/sm/__init__.py @@ -505,10 +505,8 @@ class SMWorld(World): romPatcher.writeRandoSettings(self.variaRando.randoExec.randoSettings, itemLocs) def generate_output(self, output_directory: str): - outfilebase = 'AP_' + self.world.seed_name - outfilepname = f'_P{self.player}' - outfilepname += f"_{self.world.get_file_safe_player_name(self.player).replace(' ', '_')}" - outputFilename = os.path.join(output_directory, f'{outfilebase}{outfilepname}.sfc') + outfilebase = self.world.get_out_file_name_base(self.player) + outputFilename = os.path.join(output_directory, f"{outfilebase}.sfc") try: self.variaRando.PatchRom(outputFilename, self.APPrePatchRom, self.APPostPatchRom) diff --git a/worlds/sm/docs/multiworld_en.md b/worlds/sm/docs/multiworld_en.md index 27e1d4a4ad..826be60188 100644 --- a/worlds/sm/docs/multiworld_en.md +++ b/worlds/sm/docs/multiworld_en.md @@ -7,8 +7,7 @@ - Hardware or software capable of loading and playing SNES ROM files - An emulator capable of connecting to SNI such as: - - snes9x Multitroid - from: [snes9x Multitroid Download](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) - RetroArch 1.10.1 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other @@ -81,7 +80,7 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. -##### snes9x Multitroid +##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. 2. Click on the File menu and hover on **Lua Scripting** diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py index 88b2261a23..8cf2f74350 100644 --- a/worlds/sm64ex/__init__.py +++ b/worlds/sm64ex/__init__.py @@ -173,7 +173,7 @@ class SM64World(World): } } } - filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_file_safe_player_name(self.player)}.apsm64ex" + filename = f"{self.world.get_out_file_name_base(self.player)}.apsm64ex" with open(os.path.join(output_directory, filename), 'w') as f: json.dump(data, f) diff --git a/worlds/sm64ex/docs/setup_en.md b/worlds/sm64ex/docs/setup_en.md index 1b4ae6dfb4..d77e091359 100644 --- a/worlds/sm64ex/docs/setup_en.md +++ b/worlds/sm64ex/docs/setup_en.md @@ -52,7 +52,8 @@ Optionally, add `--sm64ap_passwd "YourPassword"` if the room you are using requi The Name in this case is the one specified in your generated .yaml file. In case you are using the Archipelago Website, the IP should be `archipelago.gg`. -If everything worked out, you will see a textbox informing you the connection has been established after the story intro. +Should the connection fail (for example when using the wrong name or IP/Port combination) the game will inform you of that. +Additionally, any time the game is not connected (for example when the connection is unstable) it will attempt to reconnect and display a status text. # Playing offline diff --git a/worlds/smw/Aesthetics.py b/worlds/smw/Aesthetics.py new file mode 100644 index 0000000000..624440c55f --- /dev/null +++ b/worlds/smw/Aesthetics.py @@ -0,0 +1,202 @@ + +mario_palettes = [ + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x1F, 0x39, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0xB6, 0x30, 0xDF, 0x35, 0xFF, 0x03], # Mario + [0x3F, 0x4F, 0x1D, 0x58, 0x40, 0x11, 0xE0, 0x3F, 0x07, 0x3C, 0xAE, 0x7C, 0xB3, 0x7D, 0x00, 0x2F, 0x5F, 0x16, 0xFF, 0x03], # Luigi + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x1F, 0x03, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x16, 0x02, 0xDF, 0x35, 0xFF, 0x03], # Wario + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x12, 0x7C, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x0D, 0x58, 0xDF, 0x35, 0xFF, 0x03], # Waluigi + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x00, 0x7C, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x00, 0x58, 0xDF, 0x35, 0xFF, 0x03], # Geno + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0x1F, 0x7C, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x16, 0x58, 0xDF, 0x35, 0xFF, 0x03], # Princess + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0xE0, 0x00, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x80, 0x00, 0xDF, 0x35, 0xFF, 0x03], # Dark + [0x5F, 0x63, 0x1D, 0x58, 0x0A, 0x00, 0xFF, 0x01, 0xC4, 0x44, 0x08, 0x4E, 0x70, 0x67, 0x5F, 0x01, 0xDF, 0x35, 0xFF, 0x03], # Sponge +] + +fire_mario_palettes = [ + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x17, 0x00, 0x1F, 0x00, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Mario + [0x1F, 0x3B, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x40, 0x11, 0xE0, 0x01, 0xE0, 0x02, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Luigi + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x16, 0x02, 0x1F, 0x03, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Wario + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x0D, 0x58, 0x12, 0x7C, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Waluigi + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x00, 0x58, 0x00, 0x7C, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Geno + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x16, 0x58, 0x1F, 0x7C, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Princess + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x80, 0x00, 0xE0, 0x00, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Dark + [0x5F, 0x63, 0x1D, 0x58, 0x29, 0x25, 0xFF, 0x7F, 0x08, 0x00, 0x5F, 0x01, 0xFF, 0x01, 0x7B, 0x57, 0xDF, 0x0D, 0xFF, 0x03], # Sponge +] + +ow_mario_palettes = [ + [0x16, 0x00, 0x1F, 0x00], # Mario + [0x80, 0x02, 0xE0, 0x03], # Luigi + [0x16, 0x02, 0x1F, 0x03], # Wario + [0x0D, 0x58, 0x12, 0x7C], # Waluigi + [0x00, 0x58, 0x00, 0x7C], # Geno + [0x16, 0x58, 0x1F, 0x7C], # Princess + [0x80, 0x00, 0xE0, 0x00], # Dark + [0x5F, 0x01, 0xFF, 0x01], # Sponge +] + +level_music_address_data = [ + 0x284DB, + 0x284DC, + 0x284DD, + 0x284DE, + 0x284DF, + 0x284E0, + 0x284E1, + 0x284E2, +] + +level_music_value_data = [ + 0x02, + 0x06, + 0x01, + 0x08, + 0x07, + 0x03, + 0x05, + 0x12, +] + +ow_music_address_data = [ + [0x25BC8, 0x20D8A], + [0x25BC9, 0x20D8B], + [0x25BCA, 0x20D8C], + [0x25BCB, 0x20D8D], + [0x25BCC, 0x20D8E], + [0x25BCD, 0x20D8F], + [0x25BCE, 0x20D90], + [0x16C7] +] + +ow_music_value_data = [ + 0x02, + 0x03, + 0x04, + 0x06, + 0x07, + 0x09, + 0x05, + 0x01, +] + +valid_foreground_palettes = { + 0x00: [0x00, 0x01, 0x03, 0x04, 0x05, 0x07], # Normal 1 + 0x01: [0x03, 0x04, 0x05, 0x07], # Castle 1 + 0x02: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Rope 1 + 0x03: [0x02, 0x03, 0x04, 0x05, 0x07], # Underground 1 + 0x04: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Switch Palace 1 + 0x05: [0x04, 0x05], # Ghost House 1 + 0x06: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Rope 2 + 0x07: [0x00, 0x01, 0x03, 0x04, 0x05, 0x07], # Normal 2 + 0x08: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Rope 3 + 0x09: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Underground 2 + 0x0A: [0x01, 0x02, 0x03, 0x04, 0x05, 0x07], # Switch Palace 2 + 0x0B: [0x03, 0x04, 0x05, 0x07], # Castle 2 + #0x0C: [], # Cloud/Forest + 0x0D: [0x04, 0x05], # Ghost House 2 + 0x0E: [0x02, 0x03, 0x04, 0x05, 0x07], # Underground 3 +} + +valid_background_palettes = { + 0x06861B: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Ghost House Exit + 0xFFD900: [0x01], # P. Hills + 0xFFDAB9: [0x04], # Water + 0xFFDC71: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Hills & Clouds + 0xFFDD44: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Clouds + 0xFFDE54: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Small Hills + 0xFFDF59: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Mountains & Clouds + 0xFFE103: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Castle Pillars + 0xFFE472: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Big Hills + 0xFFE674: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Bonus + 0xFFE684: [0x01, 0x03, 0x05, 0x06], # Stars + 0xFFE7C0: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07], # Mountains + 0xFFE8EE: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Empty/Layer 2 + 0xFFE8FE: [0x01, 0x06], # Cave + 0xFFEC82: [0x00, 0x02, 0x03, 0x05, 0x06, 0x07], # Bushes + 0xFFEF80: [0x01, 0x03, 0x05, 0x06], # Ghost House + 0xFFF175: [0x00, 0x01, 0x02, 0x03, 0x05, 0x06], # Ghost Ship + 0xFFF45A: [0x01, 0x03, 0x06], # Castle +} + +valid_background_colors = { + 0x06861B: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Ghost House Exit + 0xFFD900: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # P. Hills + 0xFFDAB9: [0x02, 0x03, 0x05], # Water + 0xFFDC71: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Hills & Clouds + 0xFFDD44: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Clouds + 0xFFDE54: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Small Hills + 0xFFDF59: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Mountains & Clouds + 0xFFE103: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Castle Pillars + 0xFFE472: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Big Hills + 0xFFE674: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Bonus + 0xFFE684: [0x02, 0x03, 0x04, 0x05], # Stars + 0xFFE7C0: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Mountains + 0xFFE8EE: [0x03, 0x05], # Empty/Layer 2 + 0xFFE8FE: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Cave + 0xFFEC82: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Bushes + 0xFFEF80: [0x03, 0x04], # Ghost House + 0xFFF175: [0x02, 0x03, 0x04, 0x05], # Ghost Ship + 0xFFF45A: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Castle +} + +def generate_shuffled_level_music(world, player): + shuffled_level_music = level_music_value_data.copy() + + if world.music_shuffle[player] == "consistent": + world.random.shuffle(shuffled_level_music) + elif world.music_shuffle[player] == "singularity": + single_song = world.random.choice(shuffled_level_music) + shuffled_level_music = [single_song for i in range(len(shuffled_level_music))] + + return shuffled_level_music + +def generate_shuffled_ow_music(world, player): + shuffled_ow_music = ow_music_value_data.copy() + + if world.music_shuffle[player] == "consistent" or world.music_shuffle[player] == "full": + world.random.shuffle(shuffled_ow_music) + elif world.music_shuffle[player] == "singularity": + single_song = world.random.choice(shuffled_ow_music) + shuffled_ow_music = [single_song for i in range(len(shuffled_ow_music))] + + return shuffled_ow_music + +def generate_shuffled_header_data(rom, world, player): + if world.music_shuffle[player] != "full" and not world.foreground_palette_shuffle[player] and not world.background_palette_shuffle[player]: + return + + for level_id in range(0, 0x200): + layer1_ptr_list = list(rom.read_bytes(0x2E000 + level_id * 3, 3)) + layer1_ptr = (layer1_ptr_list[2] << 16 | layer1_ptr_list[1] << 8 | layer1_ptr_list[0]) + + if layer1_ptr == 0x68000: + # Unused Levels + continue + + if layer1_ptr >= 0x70000: + layer1_ptr -= 0x8000 + + layer1_ptr -= 0x38000 + + level_header = list(rom.read_bytes(layer1_ptr, 5)) + + tileset = level_header[4] & 0x0F + + if world.music_shuffle[player] == "full": + level_header[2] &= 0x8F + level_header[2] |= (world.random.randint(0, 7) << 5) + + if (world.foreground_palette_shuffle[player] and tileset in valid_foreground_palettes): + level_header[3] &= 0xF8 + level_header[3] |= world.random.choice(valid_foreground_palettes[tileset]) + + if world.background_palette_shuffle[player]: + layer2_ptr_list = list(rom.read_bytes(0x2E600 + level_id * 3, 3)) + layer2_ptr = (layer2_ptr_list[2] << 16 | layer2_ptr_list[1] << 8 | layer2_ptr_list[0]) + + if layer2_ptr in valid_background_palettes: + level_header[0] &= 0x1F + level_header[0] |= (world.random.choice(valid_background_palettes[layer2_ptr]) << 5) + + if layer2_ptr in valid_background_colors: + level_header[1] &= 0x1F + level_header[1] |= (world.random.choice(valid_background_colors[layer2_ptr]) << 5) + + rom.write_bytes(layer1_ptr, bytes(level_header)) diff --git a/worlds/smw/Client.py b/worlds/smw/Client.py new file mode 100644 index 0000000000..6ddd4e1073 --- /dev/null +++ b/worlds/smw/Client.py @@ -0,0 +1,455 @@ +import logging +import asyncio +import time + +from NetUtils import ClientStatus, color +from worlds import AutoWorldRegister +from SNIClient import Context, snes_buffered_write, snes_flush_writes, snes_read +from .Names.TextBox import generate_received_text +from Patch import GAME_SMW + +snes_logger = logging.getLogger("SNES") + +ROM_START = 0x000000 +WRAM_START = 0xF50000 +WRAM_SIZE = 0x20000 +SRAM_START = 0xE00000 + +SAVEDATA_START = WRAM_START + 0xF000 +SAVEDATA_SIZE = 0x500 + +SMW_ROMHASH_START = 0x7FC0 +ROMHASH_SIZE = 0x15 + +SMW_PROGRESS_DATA = WRAM_START + 0x1F02 +SMW_DRAGON_COINS_DATA = WRAM_START + 0x1F2F +SMW_PATH_DATA = WRAM_START + 0x1EA2 +SMW_EVENT_ROM_DATA = ROM_START + 0x2D608 +SMW_ACTIVE_LEVEL_DATA = ROM_START + 0x37F70 + +SMW_GOAL_DATA = ROM_START + 0x01BFA0 +SMW_REQUIRED_BOSSES_DATA = ROM_START + 0x01BFA1 +SMW_REQUIRED_EGGS_DATA = ROM_START + 0x01BFA2 +SMW_SEND_MSG_DATA = ROM_START + 0x01BFA3 +SMW_RECEIVE_MSG_DATA = ROM_START + 0x01BFA4 +SMW_DEATH_LINK_ACTIVE_ADDR = ROM_START + 0x01BFA5 +SMW_DRAGON_COINS_ACTIVE_ADDR = ROM_START + 0x01BFA6 +SMW_SWAMP_DONUT_GH_ADDR = ROM_START + 0x01BFA7 + +SMW_GAME_STATE_ADDR = WRAM_START + 0x100 +SMW_MARIO_STATE_ADDR = WRAM_START + 0x71 +SMW_BOSS_STATE_ADDR = WRAM_START + 0xD9B +SMW_ACTIVE_BOSS_ADDR = WRAM_START + 0x13FC +SMW_CURRENT_LEVEL_ADDR = WRAM_START + 0x13BF +SMW_MESSAGE_BOX_ADDR = WRAM_START + 0x1426 +SMW_BONUS_STAR_ADDR = WRAM_START + 0xF48 +SMW_EGG_COUNT_ADDR = WRAM_START + 0x1F24 +SMW_BOSS_COUNT_ADDR = WRAM_START + 0x1F26 +SMW_NUM_EVENTS_ADDR = WRAM_START + 0x1F2E +SMW_SFX_ADDR = WRAM_START + 0x1DFC +SMW_PAUSE_ADDR = WRAM_START + 0x13D4 +SMW_MESSAGE_QUEUE_ADDR = WRAM_START + 0xC391 + +SMW_RECV_PROGRESS_ADDR = WRAM_START + 0x1F2B + +SMW_GOAL_LEVELS = [0x28, 0x31, 0x32] +SMW_INVALID_MARIO_STATES = [0x05, 0x06, 0x0A, 0x0C, 0x0D] +SMW_BAD_TEXT_BOX_LEVELS = [0x26, 0x02, 0x4B] +SMW_BOSS_STATES = [0x80, 0xC0, 0xC1] +SMW_UNCOLLECTABLE_LEVELS = [0x25, 0x07, 0x0B, 0x40, 0x0E, 0x1F, 0x20, 0x1B, 0x1A, 0x35, 0x34, 0x31, 0x32] + +async def deathlink_kill_player(ctx: Context): + if ctx.game == GAME_SMW: + game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + if game_state[0] != 0x14: + return + + mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1) + if mario_state[0] != 0x00: + return + + message_box = await snes_read(ctx, SMW_MESSAGE_BOX_ADDR, 0x1) + if message_box[0] != 0x00: + return + + pause_state = await snes_read(ctx, SMW_PAUSE_ADDR, 0x1) + if pause_state[0] != 0x00: + return + + snes_buffered_write(ctx, WRAM_START + 0x9D, bytes([0x30])) # Freeze Gameplay + snes_buffered_write(ctx, WRAM_START + 0x1DFB, bytes([0x09])) # Death Music + snes_buffered_write(ctx, WRAM_START + 0x0DDA, bytes([0xFF])) # Flush Music Buffer + snes_buffered_write(ctx, WRAM_START + 0x1407, bytes([0x00])) # Flush Cape Fly Phase + snes_buffered_write(ctx, WRAM_START + 0x140D, bytes([0x00])) # Flush Spin Jump Flag + snes_buffered_write(ctx, WRAM_START + 0x188A, bytes([0x00])) # Flush Empty Byte because the game does it + snes_buffered_write(ctx, WRAM_START + 0x7D, bytes([0x90])) # Mario Y Speed + snes_buffered_write(ctx, WRAM_START + 0x1496, bytes([0x30])) # Death Timer + snes_buffered_write(ctx, SMW_MARIO_STATE_ADDR, bytes([0x09])) # Mario State -> Dead + + await snes_flush_writes(ctx) + + from SNIClient import DeathState + ctx.death_state = DeathState.dead + ctx.last_death_link = time.time() + + return + + +async def smw_rom_init(ctx: Context): + if not ctx.rom: + ctx.finished_game = False + ctx.death_link_allow_survive = False + game_hash = await snes_read(ctx, SMW_ROMHASH_START, ROMHASH_SIZE) + if game_hash is None or game_hash == bytes([0] * ROMHASH_SIZE) or game_hash[:3] != b"SMW": + return False + else: + ctx.game = GAME_SMW + ctx.items_handling = 0b111 # remote items + + ctx.rom = game_hash + + receive_option = await snes_read(ctx, SMW_RECEIVE_MSG_DATA, 0x1) + send_option = await snes_read(ctx, SMW_SEND_MSG_DATA, 0x1) + + ctx.receive_option = receive_option[0] + ctx.send_option = send_option[0] + + ctx.message_queue = [] + + ctx.allow_collect = True + + death_link = await snes_read(ctx, SMW_DEATH_LINK_ACTIVE_ADDR, 1) + if death_link: + await ctx.update_death_link(bool(death_link[0] & 0b1)) + return True + + +def add_message_to_queue(ctx: Context, new_message): + + if not hasattr(ctx, "message_queue"): + ctx.message_queue = [] + + ctx.message_queue.append(new_message) + + return + + +async def handle_message_queue(ctx: Context): + + game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + if game_state[0] != 0x14: + return + + mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1) + if mario_state[0] != 0x00: + return + + message_box = await snes_read(ctx, SMW_MESSAGE_BOX_ADDR, 0x1) + if message_box[0] != 0x00: + return + + pause_state = await snes_read(ctx, SMW_PAUSE_ADDR, 0x1) + if pause_state[0] != 0x00: + return + + current_level = await snes_read(ctx, SMW_CURRENT_LEVEL_ADDR, 0x1) + if current_level[0] in SMW_BAD_TEXT_BOX_LEVELS: + return + + boss_state = await snes_read(ctx, SMW_BOSS_STATE_ADDR, 0x1) + if boss_state[0] in SMW_BOSS_STATES: + return + + active_boss = await snes_read(ctx, SMW_ACTIVE_BOSS_ADDR, 0x1) + if active_boss[0] != 0x00: + return + + if not hasattr(ctx, "message_queue") or len(ctx.message_queue) == 0: + return + + next_message = ctx.message_queue.pop(0) + + snes_buffered_write(ctx, SMW_MESSAGE_QUEUE_ADDR, bytes(next_message)) + snes_buffered_write(ctx, SMW_MESSAGE_BOX_ADDR, bytes([0x03])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x22])) + + await snes_flush_writes(ctx) + + return + + +async def smw_game_watcher(ctx: Context): + if ctx.game == GAME_SMW: + # SMW_TODO: Handle Deathlink + game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1) + if game_state is None: + # We're not properly connected + return + elif game_state[0] >= 0x18: + if not ctx.finished_game: + current_level = await snes_read(ctx, SMW_CURRENT_LEVEL_ADDR, 0x1) + + if current_level[0] in SMW_GOAL_LEVELS: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + return + elif game_state[0] < 0x0B: + # We haven't loaded a save file + ctx.message_queue = [] + return + elif mario_state[0] in SMW_INVALID_MARIO_STATES: + # Mario can't come to the phone right now + return + + if "DeathLink" in ctx.tags and game_state[0] == 0x14 and ctx.last_death_link + 1 < time.time(): + currently_dead = mario_state[0] == 0x09 + await ctx.handle_deathlink_state(currently_dead) + + # Check for Egg Hunt ending + goal = await snes_read(ctx, SMW_GOAL_DATA, 0x1) + if game_state[0] == 0x14 and goal[0] == 1: + current_level = await snes_read(ctx, SMW_CURRENT_LEVEL_ADDR, 0x1) + message_box = await snes_read(ctx, SMW_MESSAGE_BOX_ADDR, 0x1) + egg_count = await snes_read(ctx, SMW_EGG_COUNT_ADDR, 0x1) + required_egg_count = await snes_read(ctx, SMW_REQUIRED_EGGS_DATA, 0x1) + + if current_level[0] == 0x28 and message_box[0] == 0x01 and egg_count[0] >= required_egg_count[0]: + snes_buffered_write(ctx, WRAM_START + 0x13C6, bytes([0x08])) + snes_buffered_write(ctx, WRAM_START + 0x13CE, bytes([0x01])) + snes_buffered_write(ctx, WRAM_START + 0x1DE9, bytes([0x01])) + snes_buffered_write(ctx, SMW_GAME_STATE_ADDR, bytes([0x18])) + + await snes_flush_writes(ctx) + return + + egg_count = await snes_read(ctx, SMW_EGG_COUNT_ADDR, 0x1) + boss_count = await snes_read(ctx, SMW_BOSS_COUNT_ADDR, 0x1) + display_count = await snes_read(ctx, SMW_BONUS_STAR_ADDR, 0x1) + + if goal[0] == 0 and boss_count[0] > display_count[0]: + snes_buffered_write(ctx, SMW_BONUS_STAR_ADDR, bytes([boss_count[0]])) + await snes_flush_writes(ctx) + elif goal[0] == 1 and egg_count[0] > display_count[0]: + snes_buffered_write(ctx, SMW_BONUS_STAR_ADDR, bytes([egg_count[0]])) + await snes_flush_writes(ctx) + + await handle_message_queue(ctx) + + new_checks = [] + event_data = await snes_read(ctx, SMW_EVENT_ROM_DATA, 0x60) + progress_data = bytearray(await snes_read(ctx, SMW_PROGRESS_DATA, 0x0F)) + dragon_coins_data = bytearray(await snes_read(ctx, SMW_DRAGON_COINS_DATA, 0x0C)) + dragon_coins_active = await snes_read(ctx, SMW_DRAGON_COINS_ACTIVE_ADDR, 0x1) + from worlds.smw.Rom import item_rom_data, ability_rom_data + from worlds.smw.Levels import location_id_to_level_id, level_info_dict + for loc_name, level_data in location_id_to_level_id.items(): + loc_id = AutoWorldRegister.world_types[ctx.game].location_name_to_id[loc_name] + if loc_id not in ctx.locations_checked: + + event_id = event_data[level_data[0]] + + if level_data[1] == 2: + # Dragon Coins Check + if not dragon_coins_active or dragon_coins_active[0] == 0: + continue + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = dragon_coins_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + # SMW_TODO: Handle non-included checks + new_checks.append(loc_id) + else: + event_id_value = event_id + level_data[1] + + progress_byte = (event_id_value // 8) + progress_bit = 7 - (event_id_value % 8) + + data = progress_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + # SMW_TODO: Handle non-included checks + new_checks.append(loc_id) + + verify_game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + if verify_game_state is None or verify_game_state[0] < 0x0B or verify_game_state[0] > 0x29: + # We have somehow exited the save file (or worse) + print("Exit Save File") + return + + rom = await snes_read(ctx, SMW_ROMHASH_START, ROMHASH_SIZE) + if rom != ctx.rom: + ctx.rom = None + print("Exit ROM") + # We have somehow loaded a different ROM + return + + for new_check_id in new_checks: + ctx.locations_checked.add(new_check_id) + location = ctx.location_names[new_check_id] + snes_logger.info( + f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') + await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) + + if game_state[0] != 0x14: + # Don't receive items or collect locations outside of in-level mode + return + + recv_count = await snes_read(ctx, SMW_RECV_PROGRESS_ADDR, 1) + recv_index = recv_count[0] + + if recv_index < len(ctx.items_received): + item = ctx.items_received[recv_index] + recv_index += 1 + logging.info('Received %s from %s (%s) (%d/%d in list)' % ( + color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.player_names[item.player], 'yellow'), + ctx.location_names[item.location], recv_index, len(ctx.items_received))) + + if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((item.flags & 1) != 0)): + if item.item != 0xBC0012: + # Don't send messages for Boss Tokens + item_name = ctx.item_names[item.item] + player_name = ctx.player_names[item.player] + + receive_message = generate_received_text(item_name, player_name) + add_message_to_queue(ctx, receive_message) + + snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index])) + if item.item in item_rom_data: + item_count = await snes_read(ctx, WRAM_START + item_rom_data[item.item][0], 0x1) + increment = item_rom_data[item.item][1] + + new_item_count = item_count[0] + if increment > 1: + new_item_count = increment + else: + new_item_count += increment + + if verify_game_state[0] == 0x14 and len(item_rom_data[item.item]) > 2: + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([item_rom_data[item.item][2]])) + + snes_buffered_write(ctx, WRAM_START + item_rom_data[item.item][0], bytes([new_item_count])) + elif item.item in ability_rom_data: + # Handle Upgrades + for rom_data in ability_rom_data[item.item]: + data = await snes_read(ctx, WRAM_START + rom_data[0], 1) + masked_data = data[0] | (1 << rom_data[1]) + snes_buffered_write(ctx, WRAM_START + rom_data[0], bytes([masked_data])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x3E])) # SMW_TODO: Custom sounds for each + elif item.item == 0xBC000A: + # Handle Progressive Powerup + data = await snes_read(ctx, WRAM_START + 0x1F2D, 1) + mushroom_data = data[0] & (1 << 0) + fire_flower_data = data[0] & (1 << 1) + cape_data = data[0] & (1 << 2) + if mushroom_data == 0: + masked_data = data[0] | (1 << 0) + snes_buffered_write(ctx, WRAM_START + 0x1F2D, bytes([masked_data])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x3E])) + elif fire_flower_data == 0: + masked_data = data[0] | (1 << 1) + snes_buffered_write(ctx, WRAM_START + 0x1F2D, bytes([masked_data])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x3E])) + elif cape_data == 0: + masked_data = data[0] | (1 << 2) + snes_buffered_write(ctx, WRAM_START + 0x1F2D, bytes([masked_data])) + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([0x41])) + else: + # Extra Powerup? + pass + elif item.item == 0xBC0015: + # Handle Literature Trap + from .Names.LiteratureTrap import lit_trap_text_list + import random + rand_trap = random.choice(lit_trap_text_list) + + for message in rand_trap: + add_message_to_queue(ctx, message) + + await snes_flush_writes(ctx) + + # Handle Collected Locations + new_events = 0 + path_data = bytearray(await snes_read(ctx, SMW_PATH_DATA, 0x60)) + donut_gh_swapped = await snes_read(ctx, SMW_SWAMP_DONUT_GH_ADDR, 0x1) + new_dragon_coin = False + for loc_id in ctx.checked_locations: + if loc_id not in ctx.locations_checked: + ctx.locations_checked.add(loc_id) + loc_name = ctx.location_names[loc_id] + + if loc_name not in location_id_to_level_id: + continue + + level_data = location_id_to_level_id[loc_name] + + if level_data[1] == 2: + # Dragon Coins Check + + progress_byte = (level_data[0] // 8) + progress_bit = 7 - (level_data[0] % 8) + + data = dragon_coins_data[progress_byte] + new_data = data | (1 << progress_bit) + dragon_coins_data[progress_byte] = new_data + + new_dragon_coin = True + else: + if level_data[0] in SMW_UNCOLLECTABLE_LEVELS: + continue + + event_id = event_data[level_data[0]] + event_id_value = event_id + level_data[1] + + progress_byte = (event_id_value // 8) + progress_bit = 7 - (event_id_value % 8) + + data = progress_data[progress_byte] + masked_data = data & (1 << progress_bit) + bit_set = (masked_data != 0) + + if bit_set: + continue + + new_events += 1 + new_data = data | (1 << progress_bit) + progress_data[progress_byte] = new_data + + tile_id = await snes_read(ctx, SMW_ACTIVE_LEVEL_DATA + level_data[0], 0x1) + + level_info = level_info_dict[tile_id[0]] + + path = level_info.exit1Path if level_data[1] == 0 else level_info.exit2Path + + if donut_gh_swapped[0] != 0 and tile_id[0] == 0x04: + # Handle Swapped Donut GH Exits + path = level_info.exit2Path if level_data[1] == 0 else level_info.exit1Path + + if not path: + continue + + this_end_path = path_data[tile_id[0]] + new_data = this_end_path | path.thisEndDirection + path_data[tile_id[0]] = new_data + + other_end_path = path_data[path.otherLevelID] + new_data = other_end_path | path.otherEndDirection + path_data[path.otherLevelID] = new_data + + if new_dragon_coin: + snes_buffered_write(ctx, SMW_DRAGON_COINS_DATA, bytes(dragon_coins_data)) + if new_events > 0: + snes_buffered_write(ctx, SMW_PROGRESS_DATA, bytes(progress_data)) + snes_buffered_write(ctx, SMW_PATH_DATA, bytes(path_data)) + old_events = await snes_read(ctx, SMW_NUM_EVENTS_ADDR, 0x1) + snes_buffered_write(ctx, SMW_NUM_EVENTS_ADDR, bytes([old_events[0] + new_events])) + + await snes_flush_writes(ctx) diff --git a/worlds/smw/Items.py b/worlds/smw/Items.py new file mode 100644 index 0000000000..e650aef4a5 --- /dev/null +++ b/worlds/smw/Items.py @@ -0,0 +1,69 @@ +import typing + +from BaseClasses import Item, ItemClassification +from .Names import ItemName + + +class ItemData(typing.NamedTuple): + code: typing.Optional[int] + progression: bool + trap: bool = False + quantity: int = 1 + event: bool = False + + +class SMWItem(Item): + game: str = "Super Mario World" + + +# Separate tables for each type of item. +junk_table = { + ItemName.one_up_mushroom: ItemData(0xBC0001, False), +} + +collectable_table = { + ItemName.yoshi_egg: ItemData(0xBC0002, True), +} + +upgrade_table = { + ItemName.mario_run: ItemData(0xBC0003, True), + ItemName.mario_carry: ItemData(0xBC0004, True), + ItemName.mario_swim: ItemData(0xBC0005, True), + ItemName.mario_spin_jump: ItemData(0xBC0006, True), + ItemName.mario_climb: ItemData(0xBC0007, True), + ItemName.yoshi_activate: ItemData(0xBC0008, True), + ItemName.p_switch: ItemData(0xBC0009, True), + ItemName.progressive_powerup: ItemData(0xBC000A, True), + ItemName.p_balloon: ItemData(0xBC000B, True), + ItemName.super_star_active: ItemData(0xBC000D, True), +} + +switch_palace_table = { + ItemName.yellow_switch_palace: ItemData(0xBC000E, True), + ItemName.green_switch_palace: ItemData(0xBC000F, True), + ItemName.red_switch_palace: ItemData(0xBC0010, True), + ItemName.blue_switch_palace: ItemData(0xBC0011, True), +} + +trap_table = { + ItemName.ice_trap: ItemData(0xBC0013, False, True), + ItemName.stun_trap: ItemData(0xBC0014, False, True), + ItemName.literature_trap: ItemData(0xBC0015, False, True), +} + +event_table = { + ItemName.victory: ItemData(0xBC0000, True), + ItemName.koopaling: ItemData(0xBC0012, True), +} + +# Complete item table. +item_table = { + **junk_table, + **collectable_table, + **upgrade_table, + **switch_palace_table, + **trap_table, + **event_table, +} + +lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code} diff --git a/worlds/smw/Levels.py b/worlds/smw/Levels.py new file mode 100644 index 0000000000..0783ebfd39 --- /dev/null +++ b/worlds/smw/Levels.py @@ -0,0 +1,579 @@ + +from .Names import LocationName + +class SMWPath(): + thisEndDirection: int + otherLevelID: int + otherEndDirection: int + + def __init__(self, thisEndDirection: int, otherLevelID: int, otherEndDirection: int): + self.thisEndDirection = thisEndDirection + self.otherLevelID = otherLevelID + self.otherEndDirection = otherEndDirection + + +class SMWLevel(): + levelName: str + levelIDAddress: int + #eventIDAddress: int + eventIDValue: int + #progressByte: int + #progressBit: int + exit1Path: SMWPath + exit2Path: SMWPath + + def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1Path: SMWPath = None, exit2Path: SMWPath = None): + self.levelName = levelName + self.levelIDAddress = levelIDAddress + #self.eventIDAddress = eventIDAddress # Inferred from: LevelIDValue (Dict Key): $2D608 + LevelIDValue + self.eventIDValue = eventIDValue + #self.progressByte = progressByte # Inferred from EventIDValue: (ID / 8) + $1F02 + #self.progressBit = progressBit # Inferred from EventIDValue: 1 << (7 - (ID % 8)) + self.exit1Path = exit1Path + self.exit2Path = exit2Path + + +level_info_dict = { + 0x28: SMWLevel(LocationName.yoshis_house, 0x37A76, 0x00), + 0x29: SMWLevel(LocationName.yoshis_island_1_region, 0x37A83, 0x01, SMWPath(0x08, 0x14, 0x04)), + 0x14: SMWLevel(LocationName.yellow_switch_palace, 0x37812, 0x02), + 0x2A: SMWLevel(LocationName.yoshis_island_2_region, 0x37A89, 0x03, SMWPath(0x08, 0x27, 0x04)), + 0x27: SMWLevel(LocationName.yoshis_island_3_region, 0x37A69, 0x04, SMWPath(0x01, 0x26, 0x04)), + 0x26: SMWLevel(LocationName.yoshis_island_4_region, 0x37A4B, 0x05, SMWPath(0x08, 0x25, 0x01)), + 0x25: SMWLevel(LocationName.yoshis_island_castle_region, 0x37A29, 0x06, SMWPath(0x08, 0x15, 0x04)), + + 0x15: SMWLevel(LocationName.donut_plains_1_region, 0x37815, 0x07, SMWPath(0x02, 0x09, 0x04), SMWPath(0x08, 0x0A, 0x04)), + 0x09: SMWLevel(LocationName.donut_plains_2_region, 0x376D3, 0x09, SMWPath(0x08, 0x04, 0x02), SMWPath(0x02, 0x08, 0x01)), + 0x0A: SMWLevel(LocationName.donut_secret_1_region, 0x376E5, 0x10, SMWPath(0x08, 0x04, 0x04), SMWPath(0x01, 0x13, 0x08)), + 0x08: SMWLevel(LocationName.green_switch_palace, 0x376D1, 0x28), + 0x04: SMWLevel(LocationName.donut_ghost_house_region, 0x376A5, 0x0B, SMWPath(0x08, 0x03, 0x04), SMWPath(0x01, 0x05, 0x02)), + 0x13: SMWLevel(LocationName.donut_secret_house_region, 0x37807, 0x12, SMWPath(0x01, 0x2F, 0x04), SMWPath(0x04, 0x16, 0x08)), # SMW_TODO: Check this wrt pipe behavior + 0x05: SMWLevel(LocationName.donut_plains_3_region, 0x376A9, 0x0D, SMWPath(0x01, 0x06, 0x08)), + 0x06: SMWLevel(LocationName.donut_plains_4_region, 0x376CB, 0x0E, SMWPath(0x01, 0x07, 0x02)), + 0x2F: SMWLevel(LocationName.donut_secret_2_region, 0x37B10, 0x14, SMWPath(0x01, 0x05, 0x04)), + 0x07: SMWLevel(LocationName.donut_plains_castle_region, 0x376CD, 0x0F, SMWPath(0x08, 0x3E, 0x04)), + 0x03: SMWLevel(LocationName.donut_plains_top_secret, 0x37685, 0xFF), + 0x16: SMWLevel(LocationName.donut_plains_star_road, 0x37827, 0xFF), + + 0x3E: SMWLevel(LocationName.vanilla_dome_1_region, 0x37C25, 0x15, SMWPath(0x01, 0x3C, 0x04), SMWPath(0x02, 0x2D, 0x04)), + 0x3C: SMWLevel(LocationName.vanilla_dome_2_region, 0x37C08, 0x17, SMWPath(0x08, 0x2B, 0x04), SMWPath(0x01, 0x3F, 0x08)), + 0x2D: SMWLevel(LocationName.vanilla_secret_1_region, 0x37AE3, 0x1D, SMWPath(0x08, 0x01, 0x02), SMWPath(0x02, 0x2C, 0x01)), + 0x2B: SMWLevel(LocationName.vanilla_ghost_house_region, 0x37AC8, 0x19, SMWPath(0x01, 0x2E, 0x08)), + 0x2E: SMWLevel(LocationName.vanilla_dome_3_region, 0x37AEC, 0x1A, SMWPath(0x04, 0x3D, 0x08)), + 0x3D: SMWLevel(LocationName.vanilla_dome_4_region, 0x37C0C, 0x1B, SMWPath(0x04, 0x40, 0x08)), + 0x3F: SMWLevel(LocationName.red_switch_palace, 0x37C2A, 0x29), + 0x01: SMWLevel(LocationName.vanilla_secret_2_region, 0x3763C, 0x1F, SMWPath(0x01, 0x02, 0x02)), + 0x02: SMWLevel(LocationName.vanilla_secret_3_region, 0x3763E, 0x20, SMWPath(0x01, 0x0B, 0x02)), + 0x0B: SMWLevel(LocationName.vanilla_fortress_region, 0x37730, 0x21, SMWPath(0x01, 0x0C, 0x02)), + 0x40: SMWLevel(LocationName.vanilla_dome_castle_region, 0x37C2C, 0x1C, SMWPath(0x04, 0x0F, 0x02)), + 0x2C: SMWLevel(LocationName.vanilla_dome_star_road, 0x37AE0, 0xFF), + + 0x0C: SMWLevel(LocationName.butter_bridge_1_region, 0x37734, 0x22, SMWPath(0x01, 0x0D, 0x02)), + 0x0D: SMWLevel(LocationName.butter_bridge_2_region, 0x37736, 0x23, SMWPath(0x01, 0x0E, 0x02)), + 0x0F: SMWLevel(LocationName.cheese_bridge_region, 0x37754, 0x25, SMWPath(0x01, 0x10, 0x02), SMWPath(0x04, 0x11, 0x08)), + 0x11: SMWLevel(LocationName.soda_lake_region, 0x37784, 0x60, SMWPath(0x04, 0x12, 0x04)), + 0x10: SMWLevel(LocationName.cookie_mountain_region, 0x37757, 0x27, SMWPath(0x04, 0x0E, 0x04)), + 0x0E: SMWLevel(LocationName.twin_bridges_castle_region, 0x3773A, 0x24, SMWPath(0x01, 0x42, 0x08)), + 0x12: SMWLevel(LocationName.twin_bridges_star_road, 0x377F0, 0xFF), + + 0x42: SMWLevel(LocationName.forest_of_illusion_1_region, 0x37C78, 0x2A, SMWPath(0x01, 0x44, 0x08), SMWPath(0x02, 0x41, 0x01)), + 0x44: SMWLevel(LocationName.forest_of_illusion_2_region, 0x37CAA, 0x2C, SMWPath(0x04, 0x47, 0x08), SMWPath(0x01, 0x45, 0x02)), + 0x47: SMWLevel(LocationName.forest_of_illusion_3_region, 0x37CC8, 0x2E, SMWPath(0x02, 0x41, 0x04), SMWPath(0x04, 0x20, 0x01)), + 0x43: SMWLevel(LocationName.forest_of_illusion_4_region, 0x37CA4, 0x32, SMWPath(0x01, 0x44, 0x02), SMWPath(0x04, 0x46, 0x08)), + 0x41: SMWLevel(LocationName.forest_ghost_house_region, 0x37C76, 0x30, SMWPath(0x01, 0x42, 0x02), SMWPath(0x02, 0x43, 0x08)), + 0x46: SMWLevel(LocationName.forest_secret_region, 0x37CC4, 0x34, SMWPath(0x04, 0x1F, 0x01)), + 0x45: SMWLevel(LocationName.blue_switch_palace, 0x37CAC, 0x37), + 0x1F: SMWLevel(LocationName.forest_fortress_region, 0x37906, 0x35, SMWPath(0x02, 0x1E, 0x01)), + 0x20: SMWLevel(LocationName.forest_castle_region, 0x37928, 0x61, SMWPath(0x04, 0x22, 0x08)), + 0x1E: SMWLevel(LocationName.forest_star_road, 0x37904, 0x36), + + 0x22: SMWLevel(LocationName.chocolate_island_1_region, 0x37968, 0x62, SMWPath(0x02, 0x21, 0x01)), + 0x24: SMWLevel(LocationName.chocolate_island_2_region, 0x379B5, 0x46, SMWPath(0x02, 0x23, 0x01), SMWPath(0x04, 0x3B, 0x01)), + 0x23: SMWLevel(LocationName.chocolate_island_3_region, 0x379B3, 0x48, SMWPath(0x04, 0x23, 0x08), SMWPath(0x02, 0x1B, 0x01)), + 0x1D: SMWLevel(LocationName.chocolate_island_4_region, 0x378DF, 0x4B, SMWPath(0x02, 0x1C, 0x01)), + 0x1C: SMWLevel(LocationName.chocolate_island_5_region, 0x378DC, 0x4C, SMWPath(0x08, 0x1A, 0x04)), + 0x21: SMWLevel(LocationName.chocolate_ghost_house_region, 0x37965, 0x63, SMWPath(0x04, 0x24, 0x08)), + 0x1B: SMWLevel(LocationName.chocolate_fortress_region, 0x378BF, 0x4A, SMWPath(0x04, 0x1D, 0x08)), + 0x3B: SMWLevel(LocationName.chocolate_secret_region, 0x37B97, 0x4F, SMWPath(0x02, 0x1A, 0x02)), + 0x1A: SMWLevel(LocationName.chocolate_castle_region, 0x378BC, 0x4D, SMWPath(0x08, 0x18, 0x02)), + + 0x18: SMWLevel(LocationName.sunken_ghost_ship_region, 0x3787E, 0x4E, SMWPath(0x08, 0x3A, 0x01)), + 0x3A: SMWLevel(LocationName.valley_of_bowser_1_region, 0x37B7B, 0x38, SMWPath(0x02, 0x39, 0x01)), + 0x39: SMWLevel(LocationName.valley_of_bowser_2_region, 0x37B79, 0x39, SMWPath(0x02, 0x38, 0x01), SMWPath(0x08, 0x35, 0x04)), + 0x37: SMWLevel(LocationName.valley_of_bowser_3_region, 0x37B74, 0x3D, SMWPath(0x08, 0x33, 0x04)), + 0x33: SMWLevel(LocationName.valley_of_bowser_4_region, 0x37B54, 0x3E, SMWPath(0x01, 0x34, 0x02), SMWPath(0x08, 0x30, 0x04)), + 0x38: SMWLevel(LocationName.valley_ghost_house_region, 0x37B77, 0x3B, SMWPath(0x02, 0x37, 0x01), SMWPath(0x08, 0x34, 0x04)), + 0x35: SMWLevel(LocationName.valley_fortress_region, 0x37B59, 0x41, SMWPath(0x08, 0x32, 0x04)), + 0x34: SMWLevel(LocationName.valley_castle_region, 0x37B57, 0x40, SMWPath(0x08, 0x31, 0x04)), + 0x31: SMWLevel(LocationName.front_door, 0x37B37, 0x45), + 0x81: SMWLevel(LocationName.front_door, 0x37B37, 0x45), # Fake Extra Front Door + 0x32: SMWLevel(LocationName.back_door, 0x37B39, 0x42), + 0x82: SMWLevel(LocationName.back_door, 0x37B39, 0x42), # Fake Extra Back Door + 0x30: SMWLevel(LocationName.valley_star_road, 0x37B34, 0x44), + + 0x5B: SMWLevel(LocationName.star_road_donut, 0x37DD3, 0x50), + 0x58: SMWLevel(LocationName.star_road_1_region, 0x37DA4, 0x51, None, SMWPath(0x02, 0x53, 0x04)), + 0x53: SMWLevel(LocationName.star_road_vanilla, 0x37D82, 0x53), + 0x54: SMWLevel(LocationName.star_road_2_region, 0x37D85, 0x54, None, SMWPath(0x08, 0x52, 0x02)), + 0x52: SMWLevel(LocationName.star_road_twin_bridges, 0x37D67, 0x56), + 0x56: SMWLevel(LocationName.star_road_3_region, 0x37D89, 0x57, None, SMWPath(0x01, 0x57, 0x02)), + 0x57: SMWLevel(LocationName.star_road_forest, 0x37D8C, 0x59), + 0x59: SMWLevel(LocationName.star_road_4_region, 0x37DAA, 0x5A, None, SMWPath(0x04, 0x5C, 0x08)), + 0x5C: SMWLevel(LocationName.star_road_valley, 0x37DDC, 0x5C), + 0x5A: SMWLevel(LocationName.star_road_5_region, 0x37DB7, 0x5D, SMWPath(0x02, 0x5B, 0x01), SMWPath(0x08, 0x55, 0x04)), + 0x55: SMWLevel(LocationName.star_road_special, 0x37D87, 0x5F), + + 0x4D: SMWLevel(LocationName.special_star_road, 0x37D31, 0x64), + 0x4E: SMWLevel(LocationName.special_zone_1_region, 0x37D33, 0x65, SMWPath(0x01, 0x4F, 0x02)), + 0x4F: SMWLevel(LocationName.special_zone_2_region, 0x37D36, 0x66, SMWPath(0x01, 0x50, 0x02)), + 0x50: SMWLevel(LocationName.special_zone_3_region, 0x37D39, 0x67, SMWPath(0x01, 0x51, 0x02)), + 0x51: SMWLevel(LocationName.special_zone_4_region, 0x37D3C, 0x68, SMWPath(0x01, 0x4C, 0x01)), + 0x4C: SMWLevel(LocationName.special_zone_5_region, 0x37D1C, 0x69, SMWPath(0x02, 0x4B, 0x01)), + 0x4B: SMWLevel(LocationName.special_zone_6_region, 0x37D19, 0x6A, SMWPath(0x02, 0x4A, 0x01)), + 0x4A: SMWLevel(LocationName.special_zone_7_region, 0x37D16, 0x6B, SMWPath(0x02, 0x49, 0x01)), + 0x49: SMWLevel(LocationName.special_zone_8_region, 0x37D13, 0x6C, SMWPath(0x02, 0x48, 0x01)), + 0x48: SMWLevel(LocationName.special_complete, 0x37D11, 0x6D), +} + +full_level_list = [ + 0x28, 0x29, 0x14, 0x2A, 0x27, 0x26, 0x25, + 0x15, 0x09, 0x0A, 0x08, 0x04, 0x13, 0x05, 0x06, 0x2F, 0x07, 0x03, 0x16, + 0x3E, 0x3C, 0x2D, 0x2B, 0x2E, 0x3D, 0x3F, 0x01, 0x02, 0x0B, 0x40, 0x2C, + 0x0C, 0x0D, 0x0F, 0x11, 0x10, 0x0E, 0x12, + 0x42, 0x44, 0x47, 0x43, 0x41, 0x46, 0x45, 0x1F, 0x20, 0x1E, + 0x22, 0x24, 0x23, 0x1D, 0x1C, 0x21, 0x1B, 0x3B, 0x1A, + 0x18, 0x3A, 0x39, 0x37, 0x33, 0x38, 0x35, 0x34, 0x31, 0x32, 0x30, + 0x5B, 0x58, 0x53, 0x54, 0x52, 0x56, 0x57, 0x59, 0x5C, 0x5A, 0x55, + 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x4C, 0x4B, 0x4A, 0x49, 0x48, +] + +submap_level_list = [ + 0x28, 0x29, 0x2A, 0x27, 0x26, 0x25, + 0x2F, + 0x3E, 0x3C, 0x2D, 0x2B, 0x2E, 0x3D, 0x3F, 0x40, 0x2C, + 0x42, 0x44, 0x47, 0x43, 0x41, 0x46, 0x45, + 0x3B, + 0x3A, 0x39, 0x37, 0x33, 0x38, 0x35, 0x34, 0x31, 0x32, 0x30, + 0x5B, 0x58, 0x53, 0x54, 0x52, 0x56, 0x57, 0x59, 0x5C, 0x5A, 0x55, + 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x4C, 0x4B, 0x4A, 0x49, 0x48, +] + +easy_castle_fortress_levels = [ + 0x07, + 0x40, + 0x1F, + 0x20, + 0x1B, + 0x34, +] + +hard_castle_fortress_levels = [ + 0x25, + 0x0B, + 0x0E, + 0x1A, + 0x35, +] + +easy_single_levels = [ + 0x29, + 0x2A, + 0x27, + 0x26, + 0x05, + 0x06, + 0x2F, + 0x2E, + 0x3D, + 0x01, + 0x0C, + 0x0D, + 0x46, + 0x1D, +] + +hard_single_levels = [ + 0x2B, + 0x02, + 0x11, + 0x10, + 0x22, + 0x1C, + 0x21, + 0x3B, + 0x3A, + 0x37, + 0x4E, + 0x4F, + 0x50, + 0x51, + 0x4C, + 0x4B, + 0x4A, + 0x49, +] + +easy_double_levels = [ + 0x15, + 0x09, + 0x0F, + 0x42, + 0x43, + 0x24, + 0x38, + 0x58, + 0x54, + 0x56, +] + +hard_double_levels = [ + 0x0A, + 0x04, + 0x13, + 0x3E, + 0x3C, + 0x2D, + 0x44, + 0x47, + 0x41, + 0x23, + 0x33, + 0x39, + 0x59, + 0x5A, +] + +switch_palace_levels = [ + 0x14, + 0x08, + 0x3F, + 0x45, +] + +location_id_to_level_id = { + LocationName.yoshis_island_1_exit_1: [0x29, 0], + LocationName.yoshis_island_1_dragon: [0x29, 2], + LocationName.yoshis_island_2_exit_1: [0x2A, 0], + LocationName.yoshis_island_2_dragon: [0x2A, 2], + LocationName.yoshis_island_3_exit_1: [0x27, 0], + LocationName.yoshis_island_3_dragon: [0x27, 2], + LocationName.yoshis_island_4_exit_1: [0x26, 0], + LocationName.yoshis_island_4_dragon: [0x26, 2], + LocationName.yoshis_island_castle: [0x25, 0], + LocationName.yoshis_island_koopaling: [0x25, 0], + LocationName.yellow_switch_palace: [0x14, 0], + + LocationName.donut_plains_1_exit_1: [0x15, 0], + LocationName.donut_plains_1_exit_2: [0x15, 1], + LocationName.donut_plains_1_dragon: [0x15, 2], + LocationName.donut_plains_2_exit_1: [0x09, 0], + LocationName.donut_plains_2_exit_2: [0x09, 1], + LocationName.donut_plains_2_dragon: [0x09, 2], + LocationName.donut_plains_3_exit_1: [0x05, 0], + LocationName.donut_plains_3_dragon: [0x05, 2], + LocationName.donut_plains_4_exit_1: [0x06, 0], + LocationName.donut_plains_4_dragon: [0x06, 2], + LocationName.donut_secret_1_exit_1: [0x0A, 0], + LocationName.donut_secret_1_exit_2: [0x0A, 1], + LocationName.donut_secret_1_dragon: [0x0A, 2], + LocationName.donut_secret_2_exit_1: [0x2F, 0], + LocationName.donut_secret_2_dragon: [0x2F, 2], + LocationName.donut_ghost_house_exit_1: [0x04, 0], + LocationName.donut_ghost_house_exit_2: [0x04, 1], + LocationName.donut_secret_house_exit_1: [0x13, 0], + LocationName.donut_secret_house_exit_2: [0x13, 1], + LocationName.donut_plains_castle: [0x07, 0], + LocationName.donut_plains_koopaling: [0x07, 0], + LocationName.green_switch_palace: [0x08, 0], + + LocationName.vanilla_dome_1_exit_1: [0x3E, 0], + LocationName.vanilla_dome_1_exit_2: [0x3E, 1], + LocationName.vanilla_dome_1_dragon: [0x3E, 2], + LocationName.vanilla_dome_2_exit_1: [0x3C, 0], + LocationName.vanilla_dome_2_exit_2: [0x3C, 1], + LocationName.vanilla_dome_2_dragon: [0x3C, 2], + LocationName.vanilla_dome_3_exit_1: [0x2E, 0], + LocationName.vanilla_dome_3_dragon: [0x2E, 2], + LocationName.vanilla_dome_4_exit_1: [0x3D, 0], + LocationName.vanilla_dome_4_dragon: [0x3D, 2], + LocationName.vanilla_secret_1_exit_1: [0x2D, 0], + LocationName.vanilla_secret_1_exit_2: [0x2D, 1], + LocationName.vanilla_secret_1_dragon: [0x2D, 2], + LocationName.vanilla_secret_2_exit_1: [0x01, 0], + LocationName.vanilla_secret_2_dragon: [0x01, 2], + LocationName.vanilla_secret_3_exit_1: [0x02, 0], + LocationName.vanilla_secret_3_dragon: [0x02, 2], + LocationName.vanilla_ghost_house_exit_1: [0x2B, 0], + LocationName.vanilla_ghost_house_dragon: [0x2B, 2], + LocationName.vanilla_fortress: [0x0B, 0], + LocationName.vanilla_reznor: [0x0B, 0], + LocationName.vanilla_dome_castle: [0x40, 0], + LocationName.vanilla_dome_koopaling: [0x40, 0], + LocationName.red_switch_palace: [0x3F, 0], + + LocationName.butter_bridge_1_exit_1: [0x0C, 0], + LocationName.butter_bridge_1_dragon: [0x0C, 2], + LocationName.butter_bridge_2_exit_1: [0x0D, 0], + LocationName.butter_bridge_2_dragon: [0x0D, 2], + LocationName.cheese_bridge_exit_1: [0x0F, 0], + LocationName.cheese_bridge_exit_2: [0x0F, 1], + LocationName.cheese_bridge_dragon: [0x0F, 2], + LocationName.cookie_mountain_exit_1: [0x10, 0], + LocationName.cookie_mountain_dragon: [0x10, 2], + LocationName.soda_lake_exit_1: [0x11, 0], + LocationName.soda_lake_dragon: [0x11, 2], + LocationName.twin_bridges_castle: [0x0E, 0], + LocationName.twin_bridges_koopaling: [0x0E, 0], + + LocationName.forest_of_illusion_1_exit_1: [0x42, 0], + LocationName.forest_of_illusion_1_exit_2: [0x42, 1], + LocationName.forest_of_illusion_2_exit_1: [0x44, 0], + LocationName.forest_of_illusion_2_exit_2: [0x44, 1], + LocationName.forest_of_illusion_2_dragon: [0x44, 2], + LocationName.forest_of_illusion_3_exit_1: [0x47, 0], + LocationName.forest_of_illusion_3_exit_2: [0x47, 1], + LocationName.forest_of_illusion_3_dragon: [0x47, 2], + LocationName.forest_of_illusion_4_exit_1: [0x43, 0], + LocationName.forest_of_illusion_4_exit_2: [0x43, 1], + LocationName.forest_of_illusion_4_dragon: [0x43, 2], + LocationName.forest_ghost_house_exit_1: [0x41, 0], + LocationName.forest_ghost_house_exit_2: [0x41, 1], + LocationName.forest_ghost_house_dragon: [0x41, 2], + LocationName.forest_secret_exit_1: [0x46, 0], + LocationName.forest_secret_dragon: [0x46, 2], + LocationName.forest_fortress: [0x1F, 0], + LocationName.forest_reznor: [0x1F, 0], + LocationName.forest_castle: [0x20, 0], + LocationName.forest_koopaling: [0x20, 0], + LocationName.forest_castle_dragon: [0x20, 2], + LocationName.blue_switch_palace: [0x45, 0], + + LocationName.chocolate_island_1_exit_1: [0x22, 0], + LocationName.chocolate_island_1_dragon: [0x22, 2], + LocationName.chocolate_island_2_exit_1: [0x24, 0], + LocationName.chocolate_island_2_exit_2: [0x24, 1], + LocationName.chocolate_island_2_dragon: [0x24, 2], + LocationName.chocolate_island_3_exit_1: [0x23, 0], + LocationName.chocolate_island_3_exit_2: [0x23, 1], + LocationName.chocolate_island_3_dragon: [0x23, 2], + LocationName.chocolate_island_4_exit_1: [0x1D, 0], + LocationName.chocolate_island_4_dragon: [0x1D, 2], + LocationName.chocolate_island_5_exit_1: [0x1C, 0], + LocationName.chocolate_island_5_dragon: [0x1C, 2], + LocationName.chocolate_ghost_house_exit_1: [0x21, 0], + LocationName.chocolate_secret_exit_1: [0x3B, 0], + LocationName.chocolate_fortress: [0x1B, 0], + LocationName.chocolate_reznor: [0x1B, 0], + LocationName.chocolate_castle: [0x1A, 0], + LocationName.chocolate_koopaling: [0x1A, 0], + + LocationName.sunken_ghost_ship: [0x18, 0], + LocationName.sunken_ghost_ship_dragon: [0x18, 2], + + LocationName.valley_of_bowser_1_exit_1: [0x3A, 0], + LocationName.valley_of_bowser_1_dragon: [0x3A, 2], + LocationName.valley_of_bowser_2_exit_1: [0x39, 0], + LocationName.valley_of_bowser_2_exit_2: [0x39, 1], + LocationName.valley_of_bowser_2_dragon: [0x39, 2], + LocationName.valley_of_bowser_3_exit_1: [0x37, 0], + LocationName.valley_of_bowser_3_dragon: [0x37, 2], + LocationName.valley_of_bowser_4_exit_1: [0x33, 0], + LocationName.valley_of_bowser_4_exit_2: [0x33, 1], + LocationName.valley_ghost_house_exit_1: [0x38, 0], + LocationName.valley_ghost_house_exit_2: [0x38, 1], + LocationName.valley_ghost_house_dragon: [0x38, 2], + LocationName.valley_fortress: [0x35, 0], + LocationName.valley_reznor: [0x35, 0], + LocationName.valley_castle: [0x34, 0], + LocationName.valley_koopaling: [0x34, 0], + LocationName.valley_castle_dragon: [0x34, 2], + + LocationName.star_road_1_exit_1: [0x58, 0], + LocationName.star_road_1_exit_2: [0x58, 1], + LocationName.star_road_1_dragon: [0x58, 2], + LocationName.star_road_2_exit_1: [0x54, 0], + LocationName.star_road_2_exit_2: [0x54, 1], + LocationName.star_road_3_exit_1: [0x56, 0], + LocationName.star_road_3_exit_2: [0x56, 1], + LocationName.star_road_4_exit_1: [0x59, 0], + LocationName.star_road_4_exit_2: [0x59, 1], + LocationName.star_road_5_exit_1: [0x5A, 0], + LocationName.star_road_5_exit_2: [0x5A, 1], + + LocationName.special_zone_1_exit_1: [0x4E, 0], + LocationName.special_zone_1_dragon: [0x4E, 2], + LocationName.special_zone_2_exit_1: [0x4F, 0], + LocationName.special_zone_2_dragon: [0x4F, 2], + LocationName.special_zone_3_exit_1: [0x50, 0], + LocationName.special_zone_3_dragon: [0x50, 2], + LocationName.special_zone_4_exit_1: [0x51, 0], + LocationName.special_zone_4_dragon: [0x51, 2], + LocationName.special_zone_5_exit_1: [0x4C, 0], + LocationName.special_zone_5_dragon: [0x4C, 2], + LocationName.special_zone_6_exit_1: [0x4B, 0], + LocationName.special_zone_6_dragon: [0x4B, 2], + LocationName.special_zone_7_exit_1: [0x4A, 0], + LocationName.special_zone_7_dragon: [0x4A, 2], + LocationName.special_zone_8_exit_1: [0x49, 0], + LocationName.special_zone_8_dragon: [0x49, 2], +} + +def generate_level_list(world, player): + + if not world.level_shuffle[player]: + out_level_list = full_level_list.copy() + out_level_list[0x00] = 0x03 + out_level_list[0x11] = 0x28 + + if world.bowser_castle_doors[player] == "fast": + out_level_list[0x41] = 0x82 + out_level_list[0x42] = 0x32 + elif world.bowser_castle_doors[player] == "slow": + out_level_list[0x41] = 0x31 + out_level_list[0x42] = 0x81 + + return out_level_list + + shuffled_level_list = [] + easy_castle_fortress_levels_copy = easy_castle_fortress_levels.copy() + world.random.shuffle(easy_castle_fortress_levels_copy) + hard_castle_fortress_levels_copy = hard_castle_fortress_levels.copy() + world.random.shuffle(hard_castle_fortress_levels_copy) + easy_single_levels_copy = easy_single_levels.copy() + world.random.shuffle(easy_single_levels_copy) + hard_single_levels_copy = hard_single_levels.copy() + world.random.shuffle(hard_single_levels_copy) + easy_double_levels_copy = easy_double_levels.copy() + world.random.shuffle(easy_double_levels_copy) + hard_double_levels_copy = hard_double_levels.copy() + world.random.shuffle(hard_double_levels_copy) + switch_palace_levels_copy = switch_palace_levels.copy() + world.random.shuffle(switch_palace_levels_copy) + + # Yoshi's Island + shuffled_level_list.append(0x03) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(0x14) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_castle_fortress_levels_copy.pop(0)) + + # Donut Plains + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(0x08) + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(easy_double_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_single_levels_copy.pop(0)) + shuffled_level_list.append(easy_castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(0x28) + shuffled_level_list.append(0x16) + + single_levels_copy = (easy_single_levels_copy.copy() + hard_single_levels_copy.copy()) + world.random.shuffle(single_levels_copy) + + castle_fortress_levels_copy = (easy_castle_fortress_levels_copy.copy() + hard_castle_fortress_levels_copy.copy()) + world.random.shuffle(castle_fortress_levels_copy) + + double_levels_copy = (easy_double_levels_copy.copy() + hard_double_levels_copy.copy()) + world.random.shuffle(double_levels_copy) + + # Vanilla Dome + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(0x3F) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(0x2C) + + # Twin Bridges + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(0x12) + + # Forest of Illusion + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(0x45) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(0x1E) + + # Chocolate Island + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + + # Valley of Bowser + shuffled_level_list.append(0x18) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + + # Front/Back Door + if world.bowser_castle_doors[player] == "fast": + shuffled_level_list.append(0x82) + shuffled_level_list.append(0x32) + elif world.bowser_castle_doors[player] == "slow": + shuffled_level_list.append(0x31) + shuffled_level_list.append(0x81) + else: + shuffled_level_list.append(0x31) + shuffled_level_list.append(0x32) + + shuffled_level_list.append(0x30) + + # Star Road + shuffled_level_list.append(0x5B) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x53) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x52) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x57) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x5C) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x55) + + # Special Zone + shuffled_level_list.append(0x4D) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(0x48) + + return shuffled_level_list diff --git a/worlds/smw/Locations.py b/worlds/smw/Locations.py new file mode 100644 index 0000000000..a997f92f65 --- /dev/null +++ b/worlds/smw/Locations.py @@ -0,0 +1,233 @@ +import typing + +from BaseClasses import Location +from .Names import LocationName + + +class SMWLocation(Location): + game: str = "Super Mario World" + + def __init__(self, player: int, name: str = '', address: int = None, parent=None, prog_byte: int = None, prog_bit: int = None): + super().__init__(player, name, address, parent) + self.progress_byte = prog_byte + self.progress_bit = prog_bit + + +level_location_table = { + LocationName.yoshis_island_1_exit_1: 0xBC0000, + LocationName.yoshis_island_2_exit_1: 0xBC0001, + LocationName.yoshis_island_3_exit_1: 0xBC0002, + LocationName.yoshis_island_4_exit_1: 0xBC0003, + LocationName.yoshis_island_castle: 0xBC0004, + LocationName.yoshis_island_koopaling: 0xBC00A0, + + LocationName.yellow_switch_palace: 0xBC0005, + + LocationName.donut_plains_1_exit_1: 0xBC0006, + LocationName.donut_plains_1_exit_2: 0xBC0007, + LocationName.donut_plains_2_exit_1: 0xBC0008, + LocationName.donut_plains_2_exit_2: 0xBC0009, + LocationName.donut_plains_3_exit_1: 0xBC000A, + LocationName.donut_plains_4_exit_1: 0xBC000B, + LocationName.donut_secret_1_exit_1: 0xBC000C, + LocationName.donut_secret_1_exit_2: 0xBC000D, + LocationName.donut_secret_2_exit_1: 0xBC0063, + LocationName.donut_ghost_house_exit_1: 0xBC000E, + LocationName.donut_ghost_house_exit_2: 0xBC000F, + LocationName.donut_secret_house_exit_1: 0xBC0010, + LocationName.donut_secret_house_exit_2: 0xBC0011, + LocationName.donut_plains_castle: 0xBC0012, + LocationName.donut_plains_koopaling: 0xBC00A1, + + LocationName.green_switch_palace: 0xBC0013, + + LocationName.vanilla_dome_1_exit_1: 0xBC0014, + LocationName.vanilla_dome_1_exit_2: 0xBC0015, + LocationName.vanilla_dome_2_exit_1: 0xBC0016, + LocationName.vanilla_dome_2_exit_2: 0xBC0017, + LocationName.vanilla_dome_3_exit_1: 0xBC0018, + LocationName.vanilla_dome_4_exit_1: 0xBC0019, + LocationName.vanilla_secret_1_exit_1: 0xBC001A, + LocationName.vanilla_secret_1_exit_2: 0xBC001B, + LocationName.vanilla_secret_2_exit_1: 0xBC001C, + LocationName.vanilla_secret_3_exit_1: 0xBC001D, + LocationName.vanilla_ghost_house_exit_1: 0xBC001E, + LocationName.vanilla_fortress: 0xBC0020, + LocationName.vanilla_reznor: 0xBC00B0, + LocationName.vanilla_dome_castle: 0xBC0021, + LocationName.vanilla_dome_koopaling: 0xBC00A2, + + LocationName.red_switch_palace: 0xBC0022, + + LocationName.butter_bridge_1_exit_1: 0xBC0023, + LocationName.butter_bridge_2_exit_1: 0xBC0024, + LocationName.cheese_bridge_exit_1: 0xBC0025, + LocationName.cheese_bridge_exit_2: 0xBC0026, + LocationName.cookie_mountain_exit_1: 0xBC0027, + LocationName.soda_lake_exit_1: 0xBC0028, + LocationName.twin_bridges_castle: 0xBC0029, + LocationName.twin_bridges_koopaling: 0xBC00A3, + + LocationName.forest_of_illusion_1_exit_1: 0xBC002A, + LocationName.forest_of_illusion_1_exit_2: 0xBC002B, + LocationName.forest_of_illusion_2_exit_1: 0xBC002C, + LocationName.forest_of_illusion_2_exit_2: 0xBC002D, + LocationName.forest_of_illusion_3_exit_1: 0xBC002E, + LocationName.forest_of_illusion_3_exit_2: 0xBC002F, + LocationName.forest_of_illusion_4_exit_1: 0xBC0030, + LocationName.forest_of_illusion_4_exit_2: 0xBC0031, + LocationName.forest_ghost_house_exit_1: 0xBC0032, + LocationName.forest_ghost_house_exit_2: 0xBC0033, + LocationName.forest_secret_exit_1: 0xBC0034, + LocationName.forest_fortress: 0xBC0035, + LocationName.forest_reznor: 0xBC00B1, + LocationName.forest_castle: 0xBC0036, + LocationName.forest_koopaling: 0xBC00A4, + + LocationName.blue_switch_palace: 0xBC0037, + + LocationName.chocolate_island_1_exit_1: 0xBC0038, + LocationName.chocolate_island_2_exit_1: 0xBC0039, + LocationName.chocolate_island_2_exit_2: 0xBC003A, + LocationName.chocolate_island_3_exit_1: 0xBC003B, + LocationName.chocolate_island_3_exit_2: 0xBC003C, + LocationName.chocolate_island_4_exit_1: 0xBC003D, + LocationName.chocolate_island_5_exit_1: 0xBC003E, + LocationName.chocolate_ghost_house_exit_1: 0xBC003F, + LocationName.chocolate_secret_exit_1: 0xBC0041, + LocationName.chocolate_fortress: 0xBC0042, + LocationName.chocolate_reznor: 0xBC00B2, + LocationName.chocolate_castle: 0xBC0043, + LocationName.chocolate_koopaling: 0xBC00A5, + + LocationName.sunken_ghost_ship: 0xBC0044, + + LocationName.valley_of_bowser_1_exit_1: 0xBC0045, + LocationName.valley_of_bowser_2_exit_1: 0xBC0046, + LocationName.valley_of_bowser_2_exit_2: 0xBC0047, + LocationName.valley_of_bowser_3_exit_1: 0xBC0048, + LocationName.valley_of_bowser_4_exit_1: 0xBC0049, + LocationName.valley_of_bowser_4_exit_2: 0xBC004A, + LocationName.valley_ghost_house_exit_1: 0xBC004B, + LocationName.valley_ghost_house_exit_2: 0xBC004C, + LocationName.valley_fortress: 0xBC004E, + LocationName.valley_reznor: 0xBC00B3, + LocationName.valley_castle: 0xBC004F, + LocationName.valley_koopaling: 0xBC00A6, + + LocationName.star_road_1_exit_1: 0xBC0051, + LocationName.star_road_1_exit_2: 0xBC0052, + LocationName.star_road_2_exit_1: 0xBC0053, + LocationName.star_road_2_exit_2: 0xBC0054, + LocationName.star_road_3_exit_1: 0xBC0055, + LocationName.star_road_3_exit_2: 0xBC0056, + LocationName.star_road_4_exit_1: 0xBC0057, + LocationName.star_road_4_exit_2: 0xBC0058, + LocationName.star_road_5_exit_1: 0xBC0059, + LocationName.star_road_5_exit_2: 0xBC005A, + + LocationName.special_zone_1_exit_1: 0xBC005B, + LocationName.special_zone_2_exit_1: 0xBC005C, + LocationName.special_zone_3_exit_1: 0xBC005D, + LocationName.special_zone_4_exit_1: 0xBC005E, + LocationName.special_zone_5_exit_1: 0xBC005F, + LocationName.special_zone_6_exit_1: 0xBC0060, + LocationName.special_zone_7_exit_1: 0xBC0061, + LocationName.special_zone_8_exit_1: 0xBC0062, +} + +dragon_coin_location_table = { + LocationName.yoshis_island_1_dragon: 0xBC0100, + LocationName.yoshis_island_2_dragon: 0xBC0101, + LocationName.yoshis_island_3_dragon: 0xBC0102, + LocationName.yoshis_island_4_dragon: 0xBC0103, + + LocationName.donut_plains_1_dragon: 0xBC0106, + LocationName.donut_plains_2_dragon: 0xBC0108, + LocationName.donut_plains_3_dragon: 0xBC010A, + LocationName.donut_plains_4_dragon: 0xBC010B, + LocationName.donut_secret_1_dragon: 0xBC010C, + LocationName.donut_secret_2_dragon: 0xBC010D, + + LocationName.vanilla_dome_1_dragon: 0xBC0114, + LocationName.vanilla_dome_2_dragon: 0xBC0116, + LocationName.vanilla_dome_3_dragon: 0xBC0118, + LocationName.vanilla_dome_4_dragon: 0xBC0119, + LocationName.vanilla_secret_1_dragon: 0xBC011A, + LocationName.vanilla_secret_2_dragon: 0xBC011C, + LocationName.vanilla_secret_3_dragon: 0xBC011D, + LocationName.vanilla_ghost_house_dragon: 0xBC011E, + + LocationName.butter_bridge_1_dragon: 0xBC0123, + LocationName.butter_bridge_2_dragon: 0xBC0124, + LocationName.cheese_bridge_dragon: 0xBC0125, + LocationName.cookie_mountain_dragon: 0xBC0127, + LocationName.soda_lake_dragon: 0xBC0128, + + LocationName.forest_of_illusion_2_dragon: 0xBC012C, + LocationName.forest_of_illusion_3_dragon: 0xBC012E, + LocationName.forest_of_illusion_4_dragon: 0xBC0130, + LocationName.forest_ghost_house_dragon: 0xBC0132, + LocationName.forest_secret_dragon: 0xBC0134, + LocationName.forest_castle_dragon: 0xBC0136, + + LocationName.chocolate_island_1_dragon: 0xBC0138, + LocationName.chocolate_island_2_dragon: 0xBC0139, + LocationName.chocolate_island_3_dragon: 0xBC013B, + LocationName.chocolate_island_4_dragon: 0xBC013D, + LocationName.chocolate_island_5_dragon: 0xBC013E, + + LocationName.sunken_ghost_ship_dragon: 0xBC0144, + + LocationName.valley_of_bowser_1_dragon: 0xBC0145, + LocationName.valley_of_bowser_2_dragon: 0xBC0146, + LocationName.valley_of_bowser_3_dragon: 0xBC0148, + LocationName.valley_ghost_house_dragon: 0xBC014B, + LocationName.valley_castle_dragon: 0xBC014F, + + LocationName.star_road_1_dragon: 0xBC0151, + + LocationName.special_zone_1_dragon: 0xBC015B, + LocationName.special_zone_2_dragon: 0xBC015C, + LocationName.special_zone_3_dragon: 0xBC015D, + LocationName.special_zone_4_dragon: 0xBC015E, + LocationName.special_zone_5_dragon: 0xBC015F, + LocationName.special_zone_6_dragon: 0xBC0160, + LocationName.special_zone_7_dragon: 0xBC0161, + LocationName.special_zone_8_dragon: 0xBC0162, +} + +bowser_location_table = { + LocationName.bowser: 0xBC0200, +} + +yoshi_house_location_table = { + LocationName.yoshis_house: 0xBC0201, +} + +all_locations = { + **level_location_table, + **dragon_coin_location_table, + **bowser_location_table, + **yoshi_house_location_table, +} + +location_table = {} + + +def setup_locations(world, player: int): + location_table = {**level_location_table} + + # Dragon Coins here + if world.dragon_coin_checks[player].value: + location_table.update({**dragon_coin_location_table}) + + if world.goal[player] == "yoshi_egg_hunt": + location_table.update({**yoshi_house_location_table}) + else: + location_table.update({**bowser_location_table}) + + return location_table + + +lookup_id_to_name: typing.Dict[int, str] = {id: name for name, _ in all_locations.items()} diff --git a/worlds/smw/Names/ItemName.py b/worlds/smw/Names/ItemName.py new file mode 100644 index 0000000000..72c984b016 --- /dev/null +++ b/worlds/smw/Names/ItemName.py @@ -0,0 +1,32 @@ +# Junk Definitions +one_up_mushroom = "1-Up Mushroom" + +# Collectable Definitions +yoshi_egg = "Yoshi Egg" + +# Upgrade Definitions +mario_run = "Run" +mario_carry = "Carry" +mario_swim = "Swim" +mario_spin_jump = "Spin Jump" +mario_climb = "Climb" +yoshi_activate = "Yoshi" +p_switch = "P-Switch" +p_balloon = "P-Balloon" +progressive_powerup = "Progressive Powerup" +super_star_active = "Super Star Activate" + +# Switch Palace Definitions +yellow_switch_palace = "Yellow Switch Palace" +green_switch_palace = "Green Switch Palace" +red_switch_palace = "Red Switch Palace" +blue_switch_palace = "Blue Switch Palace" + +# Trap Definitions +ice_trap = "Ice Trap" +stun_trap = "Stun Trap" +literature_trap = "Literature Trap" + +# Other Definitions +victory = "The Princess" +koopaling = "Boss Token" diff --git a/worlds/smw/Names/LiteratureTrap.py b/worlds/smw/Names/LiteratureTrap.py new file mode 100644 index 0000000000..94c038228d --- /dev/null +++ b/worlds/smw/Names/LiteratureTrap.py @@ -0,0 +1,52 @@ +lit_trap_text_list = [ +[[0x8, 0x1f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x45, 0x44, 0x40, 0x51, 0x1b, 0x9f, 0x5, 0x44, 0x40, 0x51, 0x1f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x48, 0x4d, 0x43, 0x1c, 0x4a, 0x48, 0x4b, 0x4b, 0x44, 0x51, 0x1b, 0x1f, 0x5, 0x44, 0x40, 0x51, 0x9f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4b, 0x48, 0x53, 0x53, 0x4b, 0x44, 0x1c, 0x43, 0x44, 0x40, 0x53, 0x47, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x41, 0x51, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x40, 0x41, 0x4e, 0x54, 0x53, 0x1f, 0x53, 0x4e, 0x53, 0x40, 0xcb, 0x4e, 0x41, 0x4b, 0x48, 0x53, 0x44, 0x51, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x1f, 0x8, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x45, 0x40, 0x42, 0x44, 0x1f, 0x4c, 0x58, 0x1f, 0x45, 0x44, 0x40, 0x51, 0x9b, ], [0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4f, 0x44, 0x51, 0x4c, 0x48, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x53, 0x4e, 0x1f, 0x4f, 0x40, 0x52, 0x52, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x4c, 0x44, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x1f, 0x4c, 0x44, 0x1b, 0x9f, 0x0, 0x4d, 0x43, 0x1f, 0x56, 0x47, 0x44, 0x4d, 0x1f, 0x48, 0x53, 0x1f, 0x47, 0x40, 0x52, 0x9f, 0x46, 0x4e, 0x4d, 0x44, 0x1f, 0x4f, 0x40, 0x52, 0x53, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x53, 0x54, 0x51, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x48, 0x4d, 0x4d, 0x44, 0x51, 0x1f, 0x44, 0x58, 0xc4, 0x53, 0x4e, 0x1f, 0x52, 0x44, 0x44, 0x1f, 0x48, 0x53, 0x52, 0x1f, 0x4f, 0x40, 0x53, 0x47, 0x1b, 0x9f, 0x16, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x45, 0x44, 0x40, 0x51, 0x1f, 0x47, 0x40, 0xd2, ], [0x46, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x41, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0xe, 0x4d, 0x4b, 0x58, 0x1f, 0x88, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x51, 0x44, 0x4c, 0x40, 0x48, 0x4d, 0x1b, 0x9f, 0x1c, 0x7, 0x44, 0x51, 0x41, 0x44, 0x51, 0x53, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x0, 0x41, 0x4e, 0x54, 0x53, 0x1f, 0x53, 0x47, 0x51, 0x44, 0x44, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0xd2, 0x8, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x41, 0x52, 0x4e, 0x4b, 0x54, 0x53, 0x44, 0x4b, 0x58, 0x9f, 0x4f, 0x4e, 0x52, 0x48, 0x53, 0x48, 0x55, 0x44, 0x1b, 0x1f, 0x5, 0x48, 0x51, 0x52, 0x53, 0x1d, 0x9f, 0x4, 0x43, 0x56, 0x40, 0x51, 0x43, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x9f, 0x55, 0x40, 0x4c, 0x4f, 0x48, 0x51, 0x44, 0x1b, 0x1f, 0x12, 0x44, 0x42, 0x4e, 0x4d, 0x43, 0x1d, 0x9f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x1f, 0x4f, 0x40, 0x51, 0x53, 0x9f, 0x4e, 0x45, 0x1f, 0x47, 0x48, 0x4c, 0x1f, 0x1c, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x1f, 0x4a, 0x4d, 0x4e, 0x56, 0x1f, 0x47, 0x4e, 0x56, 0x9f, ], [0x4f, 0x4e, 0x53, 0x44, 0x4d, 0x53, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4f, 0x40, 0x51, 0x53, 0x9f, 0x4c, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x41, 0x44, 0x1f, 0x1c, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x53, 0x47, 0x48, 0x51, 0x52, 0x53, 0x44, 0x43, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x4c, 0x58, 0x9f, 0x41, 0x4b, 0x4e, 0x4e, 0x43, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x48, 0x51, 0x43, 0x1d, 0x9f, 0x8, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x54, 0x4d, 0x42, 0x4e, 0x4d, 0x43, 0x48, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x4b, 0x4b, 0x58, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x48, 0x51, 0x51, 0x44, 0x55, 0x4e, 0x42, 0x40, 0x41, 0x4b, 0x58, 0x1f, 0x48, 0xcd, 0x4b, 0x4e, 0x55, 0x44, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x47, 0x48, 0x4c, 0x1b, 0x9f, ], [0x1c, 0xc, 0x44, 0x58, 0x44, 0x51, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x12, 0x40, 0x58, 0x1a, 0x1f, 0x8, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x46, 0x51, 0x44, 0x44, 0x4d, 0x9f, 0x44, 0x46, 0x46, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x40, 0x4c, 0x1a, 0x1f, 0x8, 0x9f, 0x43, 0x4e, 0x1a, 0x1f, 0x8, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1d, 0x9f, 0x12, 0x40, 0x4c, 0x1c, 0x8, 0x1c, 0x40, 0x4c, 0x1a, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0x4d, 0x9f, 0x40, 0x1f, 0x41, 0x4e, 0x40, 0x53, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x40, 0x1f, 0x46, 0x4e, 0x40, 0x53, 0x1b, 0x1b, 0x1b, 0x1f, 0x0, 0x4d, 0xc3, ], [0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0xcd, 0x53, 0x47, 0x44, 0x1f, 0x51, 0x40, 0x48, 0x4d, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x40, 0x51, 0x4a, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x4e, 0x4d, 0x1f, 0xc0, 0x53, 0x51, 0x40, 0x48, 0x4d, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x9f, 0x42, 0x40, 0x51, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x9f, 0x53, 0x51, 0x44, 0x44, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x52, 0x4e, 0x9f, 0x46, 0x4e, 0x4e, 0x43, 0x1d, 0x1f, 0x52, 0x4e, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1d, 0x1f, 0x58, 0x4e, 0xd4, 0x52, 0x44, 0x44, 0x1a, 0x1f, 0x12, 0x4e, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x44, 0x40, 0xd3, ], [0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x1f, 0x41, 0x4e, 0x57, 0x1b, 0x1f, 0x0, 0x4d, 0xc3, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x40, 0x1f, 0x45, 0x4e, 0x57, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0x4d, 0x1f, 0xc0, 0x47, 0x4e, 0x54, 0x52, 0x44, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x40, 0x9f, 0x4c, 0x4e, 0x54, 0x52, 0x44, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x9f, ], [0x53, 0x47, 0x44, 0x51, 0x44, 0x1b, 0x1f, 0x12, 0x40, 0x58, 0x1a, 0x1f, 0x8, 0x1f, 0x56, 0x48, 0x4b, 0xcb, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x40, 0x4d, 0x58, 0x56, 0x47, 0x44, 0x51, 0x44, 0x9a, 0x8, 0x1f, 0x43, 0x4e, 0x1f, 0x52, 0x4e, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x46, 0x51, 0x44, 0x44, 0xcd, 0x44, 0x46, 0x46, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x40, 0x4c, 0x1a, 0x9f, 0x13, 0x47, 0x40, 0x4d, 0x4a, 0x1f, 0x58, 0x4e, 0x54, 0x1a, 0x1f, 0x13, 0x47, 0x40, 0x4d, 0x4a, 0x9f, 0x58, 0x4e, 0x54, 0x1d, 0x1f, 0x12, 0x40, 0x4c, 0x1c, 0x8, 0x1c, 0x40, 0x4c, 0x1a, 0x9f, 0x1c, 0x12, 0x44, 0x54, 0x52, 0x52, 0x1f, 0x9f, 0x9f, ]], +[[0x1, 0x54, 0x53, 0x1d, 0x1f, 0x52, 0x4e, 0x45, 0x53, 0x1a, 0x1f, 0x56, 0x47, 0x40, 0x53, 0x9f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x58, 0x4e, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x56, 0x48, 0x4d, 0x43, 0x4e, 0x56, 0x9f, 0x41, 0x51, 0x44, 0x40, 0x4a, 0x52, 0x1e, 0x1f, 0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x44, 0x40, 0x52, 0x53, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x9, 0x54, 0x4b, 0x48, 0x44, 0x53, 0x9f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x54, 0x4d, 0x1b, 0x1f, 0x0, 0x51, 0x48, 0x52, 0x44, 0x9d, 0x45, 0x40, 0x48, 0x51, 0x1f, 0x52, 0x54, 0x4d, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4a, 0x48, 0x4b, 0xcb, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4d, 0x55, 0x48, 0x4e, 0x54, 0x52, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1d, 0x9f, ], [0x16, 0x47, 0x4e, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x4b, 0x51, 0x44, 0x40, 0x43, 0x58, 0x9f, 0x52, 0x48, 0x42, 0x4a, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x40, 0x4b, 0x44, 0x1f, 0x56, 0x48, 0x53, 0xc7, 0x46, 0x51, 0x48, 0x44, 0x45, 0x1d, 0x1f, 0x13, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x4e, 0x54, 0x9f, 0x47, 0x44, 0x51, 0x1f, 0x4c, 0x40, 0x48, 0x43, 0x1f, 0x40, 0x51, 0x53, 0x1f, 0x45, 0x40, 0x51, 0x9f, 0x4c, 0x4e, 0x51, 0x44, 0x1f, 0x45, 0x40, 0x48, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x9f, 0x52, 0x47, 0x44, 0x1b, 0x1f, 0x1, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x47, 0x44, 0x51, 0x9f, 0x4c, 0x40, 0x48, 0x43, 0x1d, 0x1f, 0x52, 0x48, 0x4d, 0x42, 0x44, 0x1f, 0x52, 0x47, 0x44, 0x1f, 0x48, 0xd2, 0x44, 0x4d, 0x55, 0x48, 0x4e, 0x54, 0x52, 0x1b, 0x1f, 0x7, 0x44, 0x51, 0x9f, ], [0x55, 0x44, 0x52, 0x53, 0x40, 0x4b, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x48, 0x52, 0x9f, 0x41, 0x54, 0x53, 0x1f, 0x52, 0x48, 0x42, 0x4a, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x46, 0x51, 0x44, 0x44, 0xcd, 0x0, 0x4d, 0x43, 0x1f, 0x4d, 0x4e, 0x4d, 0x44, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x45, 0x4e, 0x4e, 0x4b, 0xd2, 0x43, 0x4e, 0x1f, 0x56, 0x44, 0x40, 0x51, 0x1f, 0x48, 0x53, 0x1b, 0x1f, 0x42, 0x40, 0x52, 0x53, 0x9f, 0x48, 0x53, 0x1f, 0x4e, 0x45, 0x45, 0x1b, 0x1f, 0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x4c, 0x58, 0x9f, 0x4b, 0x40, 0x43, 0x58, 0x1d, 0x1f, 0xe, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x4c, 0x58, 0x9f, 0x4b, 0x4e, 0x55, 0x44, 0x1a, 0x1f, 0xe, 0x1d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x52, 0x47, 0x44, 0x9f, 0x4a, 0x4d, 0x44, 0x56, 0x1f, 0x52, 0x47, 0x44, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1a, 0x9f, ], [0x1c, 0x12, 0x47, 0x40, 0x4a, 0x44, 0x52, 0x4f, 0x44, 0x40, 0x51, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x41, 0x44, 0x52, 0x53, 0x1f, 0x4e, 0xc5, 0x53, 0x48, 0x4c, 0x44, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x4e, 0x51, 0x52, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x48, 0x4c, 0x44, 0x52, 0x1d, 0x1f, 0x48, 0xd3, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x40, 0x46, 0x44, 0x1f, 0x4e, 0x45, 0x9f, 0x56, 0x48, 0x52, 0x43, 0x4e, 0x4c, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0xc4, 0x40, 0x46, 0x44, 0x1f, 0x4e, 0x45, 0x9f, 0x45, 0x4e, 0x4e, 0x4b, 0x48, 0x52, 0x47, 0x4d, 0x44, 0x52, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4f, 0x4e, 0x42, 0x47, 0x1f, 0x4e, 0x45, 0x9f, ], [0x41, 0x44, 0x4b, 0x48, 0x44, 0x45, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0xc4, 0x44, 0x4f, 0x4e, 0x42, 0x47, 0x1f, 0x4e, 0x45, 0x9f, 0x48, 0x4d, 0x42, 0x51, 0x44, 0x43, 0x54, 0x4b, 0x48, 0x53, 0x58, 0x1d, 0x1f, 0x48, 0x53, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x44, 0x40, 0x52, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x9f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x44, 0x40, 0x52, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x9f, 0x43, 0x40, 0x51, 0x4a, 0x4d, 0x44, 0x52, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x4f, 0x51, 0x48, 0x4d, 0x46, 0x1f, 0x4e, 0x45, 0x9f, ], [0x47, 0x4e, 0x4f, 0x44, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x48, 0x4d, 0x53, 0x44, 0x51, 0x1f, 0x4e, 0x45, 0x1f, 0x43, 0x44, 0x52, 0x4f, 0x40, 0x48, 0x51, 0x9b, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x44, 0x4d, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xc, 0x40, 0x51, 0x4b, 0x44, 0x58, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x43, 0x44, 0x40, 0x43, 0x1d, 0x9f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x46, 0x48, 0x4d, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1b, 0x9f, 0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x4d, 0x4e, 0x1f, 0x43, 0x4e, 0x54, 0x41, 0x53, 0x9f, 0x56, 0x47, 0x40, 0x53, 0x44, 0x55, 0x44, 0x51, 0x1d, 0x1f, 0x40, 0x41, 0x4e, 0x54, 0x53, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x1f, 0x51, 0x44, 0x46, 0x48, 0x52, 0x53, 0x44, 0xd1, 0x4e, 0x45, 0x1f, 0x47, 0x48, 0x52, 0x1f, 0x41, 0x54, 0x51, 0x48, 0x40, 0x4b, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x52, 0x48, 0x46, 0x4d, 0x44, 0x43, 0x1f, 0x41, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x42, 0x4b, 0x44, 0x51, 0x46, 0x58, 0x4c, 0x40, 0x4d, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, ], [0x42, 0x4b, 0x44, 0x51, 0x4a, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x53, 0x40, 0x4a, 0x44, 0x51, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x42, 0x47, 0x48, 0x44, 0x45, 0x1f, 0x4c, 0x4e, 0x54, 0x51, 0x4d, 0x44, 0x51, 0x9b, 0x12, 0x42, 0x51, 0x4e, 0x4e, 0x46, 0x44, 0x1f, 0x52, 0x48, 0x46, 0x4d, 0x44, 0x43, 0x1f, 0x48, 0x53, 0x9b, 0x40, 0x4d, 0x43, 0x1f, 0x12, 0x42, 0x51, 0x4e, 0x4e, 0x46, 0x44, 0x5d, 0x52, 0x1f, 0x4d, 0x40, 0x4c, 0xc4, 0x56, 0x40, 0x52, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x54, 0x4f, 0x4e, 0x4d, 0x9f, 0x5d, 0x42, 0x47, 0x40, 0x4d, 0x46, 0x44, 0x1d, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x40, 0x4d, 0x58, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x47, 0x44, 0x1f, 0x42, 0x47, 0x4e, 0x52, 0x44, 0x9f, ], [0x53, 0x4e, 0x1f, 0x4f, 0x54, 0x53, 0x1f, 0x47, 0x48, 0x52, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x4e, 0x1b, 0x1f, 0xe, 0x4b, 0x43, 0x1f, 0xc, 0x40, 0x51, 0x4b, 0x44, 0x58, 0x1f, 0x56, 0x40, 0xd2, 0x40, 0x52, 0x1f, 0x43, 0x44, 0x40, 0x43, 0x1f, 0x40, 0x52, 0x1f, 0x40, 0x9f, 0x43, 0x4e, 0x4e, 0x51, 0x1c, 0x4d, 0x40, 0x48, 0x4b, 0x1b, 0x9f, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x44, 0x4d, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xc, 0x40, 0x4d, 0x58, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x9f, 0x43, 0x44, 0x52, 0x44, 0x51, 0x55, 0x44, 0x1f, 0x43, 0x44, 0x40, 0x53, 0x47, 0x1b, 0x1f, 0x0, 0x4d, 0xc3, 0x52, 0x4e, 0x4c, 0x44, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x43, 0x48, 0x44, 0x9f, 0x43, 0x44, 0x52, 0x44, 0x51, 0x55, 0x44, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1b, 0x1f, 0x2, 0x40, 0x4d, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x1f, 0x48, 0x53, 0x1f, 0x53, 0x4e, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x1e, 0x1f, 0x13, 0x47, 0x44, 0x4d, 0x1f, 0x43, 0x4e, 0x1f, 0x4d, 0x4e, 0x53, 0x9f, 0x41, 0x44, 0x1f, 0x53, 0x4e, 0x4e, 0x1f, 0x44, 0x40, 0x46, 0x44, 0x51, 0x1f, 0x53, 0x4e, 0x9f, 0x43, 0x44, 0x40, 0x4b, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x43, 0x44, 0x40, 0x53, 0x47, 0x1f, 0x48, 0x4d, 0x9f, ], [0x49, 0x54, 0x43, 0x46, 0x44, 0x4c, 0x44, 0x4d, 0x53, 0x1b, 0x1f, 0x5, 0x4e, 0x51, 0x9f, 0x44, 0x55, 0x44, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x56, 0x48, 0x52, 0xc4, 0x42, 0x40, 0x4d, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x44, 0x44, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x44, 0x4d, 0x43, 0x52, 0x1b, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x1f, 0x53, 0x51, 0x54, 0x53, 0x47, 0x9f, 0x54, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x40, 0x4b, 0x4b, 0x58, 0x9f, 0x40, 0x42, 0x4a, 0x4d, 0x4e, 0x56, 0x4b, 0x44, 0x43, 0x46, 0x44, 0x43, 0x1d, 0x1f, 0x53, 0x47, 0x40, 0xd3, 0x40, 0x1f, 0x52, 0x48, 0x4d, 0x46, 0x4b, 0x44, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x48, 0x4d, 0x9f, 0x4f, 0x4e, 0x52, 0x52, 0x44, 0x52, 0x52, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x45, 0x4e, 0x51, 0x53, 0x54, 0x4d, 0x44, 0x1d, 0x1f, 0x4c, 0x54, 0x52, 0xd3, 0x41, 0x44, 0x1f, 0x48, 0x4d, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x56, 0x48, 0x45, 0x44, 0x1b, 0x1f, 0x1c, 0x0, 0x54, 0x52, 0x53, 0x44, 0x4d, 0x1f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x53, 0x51, 0x54, 0x53, 0x47, 0x1f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x9f, 0x42, 0x40, 0x51, 0x51, 0x48, 0x44, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x40, 0x4c, 0x41, 0x48, 0x46, 0x54, 0x48, 0x53, 0x58, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x4e, 0x51, 0x43, 0x52, 0x1f, 0x54, 0x52, 0x44, 0x43, 0x1f, 0x53, 0x4e, 0x9f, 0x44, 0x57, 0x4f, 0x51, 0x44, 0x52, 0x52, 0x1f, 0x48, 0x53, 0x1b, 0x9f, 0x1c, 0x7, 0x44, 0x51, 0x41, 0x44, 0x51, 0x53, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x5d, 0x8, 0x1f, 0x43, 0x40, 0x51, 0x44, 0x52, 0x40, 0x58, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x4d, 0x5d, 0x53, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x4c, 0x54, 0x42, 0x47, 0x9f, 0x4f, 0x51, 0x40, 0x42, 0x53, 0x48, 0x42, 0x44, 0x1d, 0x5d, 0x1f, 0x52, 0x40, 0x48, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x10, 0x54, 0x44, 0x44, 0x4d, 0x1b, 0x1f, 0x5d, 0x16, 0x47, 0x44, 0x4d, 0x1f, 0x88, 0x56, 0x40, 0x52, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x40, 0x46, 0x44, 0x1d, 0x1f, 0x8, 0x9f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x1f, 0x43, 0x48, 0x43, 0x1f, 0x48, 0x53, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x47, 0x40, 0x4b, 0x45, 0x1c, 0x40, 0x4d, 0x1c, 0x47, 0x4e, 0x54, 0x51, 0x1f, 0x40, 0x9f, 0x43, 0x40, 0x58, 0x1b, 0x1f, 0x16, 0x47, 0x58, 0x1d, 0x9f, ], [0x52, 0x4e, 0x4c, 0x44, 0x53, 0x48, 0x4c, 0x44, 0x52, 0x1f, 0x8, 0x5d, 0x55, 0x44, 0x9f, 0x41, 0x44, 0x4b, 0x48, 0x44, 0x55, 0x44, 0x43, 0x1f, 0x40, 0x52, 0x1f, 0x4c, 0x40, 0x4d, 0x58, 0x9f, 0x40, 0x52, 0x1f, 0x52, 0x48, 0x57, 0x1f, 0x48, 0x4c, 0x4f, 0x4e, 0x52, 0x52, 0x48, 0x41, 0x4b, 0x44, 0x9f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x41, 0x44, 0x45, 0x4e, 0x51, 0x44, 0x9f, 0x41, 0x51, 0x44, 0x40, 0x4a, 0x45, 0x40, 0x52, 0x53, 0x1b, 0x5d, 0x9f, 0x1c, 0x2, 0x40, 0x51, 0x51, 0x4e, 0x4b, 0x4b, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0xb, 0x48, 0x45, 0x44, 0x1d, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x51, 0x54, 0x4b, 0x44, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x4e, 0x41, 0x4b, 0x48, 0x46, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x48, 0x53, 0x52, 0x1f, 0x45, 0x51, 0x44, 0x44, 0x43, 0x4e, 0x4c, 0x52, 0x1d, 0x1f, 0x48, 0x52, 0x9f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x40, 0x1f, 0x52, 0x4e, 0x4d, 0x4d, 0x44, 0x53, 0x1b, 0x9f, 0x18, 0x4e, 0x54, 0x5d, 0x51, 0x44, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x45, 0x4e, 0x51, 0x4c, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x47, 0x40, 0x55, 0xc4, 0x53, 0x4e, 0x1f, 0x56, 0x51, 0x48, 0x53, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, ], [0x52, 0x4e, 0x4d, 0x4d, 0x44, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x52, 0x44, 0x4b, 0x45, 0x1b, 0x9f, 0x1c, 0xb, 0x5d, 0x4, 0x4d, 0x46, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xb, 0x48, 0x4a, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1f, 0x4e, 0x55, 0x44, 0xd1, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x40, 0x58, 0x1f, 0x4c, 0x58, 0x1f, 0x46, 0x44, 0x4d, 0x48, 0x54, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x41, 0x51, 0x40, 0x56, 0x4d, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x4b, 0x4e, 0x52, 0xd3, 0x4e, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x52, 0x44, 0x1f, 0x45, 0x4e, 0x4e, 0x4b, 0x52, 0x9f, 0x1c, 0x7, 0x40, 0x48, 0x4a, 0x54, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x4d, 0x1f, 0x40, 0x1f, 0x47, 0x4e, 0x4b, 0x44, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x46, 0x51, 0x4e, 0x54, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0xc3, 0x40, 0x1f, 0x47, 0x4e, 0x41, 0x41, 0x48, 0x53, 0x1b, 0x1f, 0xd, 0x4e, 0x53, 0x1f, 0x40, 0x9f, 0x4d, 0x40, 0x52, 0x53, 0x58, 0x1d, 0x1f, 0x43, 0x48, 0x51, 0x53, 0x58, 0x1d, 0x1f, 0x56, 0x44, 0x53, 0x9f, 0x47, 0x4e, 0x4b, 0x44, 0x1d, 0x1f, 0x45, 0x48, 0x4b, 0x4b, 0x44, 0x43, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4d, 0x43, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x56, 0x4e, 0x51, 0x4c, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x4d, 0x1f, 0x4e, 0x4e, 0x59, 0x58, 0x1f, 0x52, 0x4c, 0x44, 0x4b, 0x4b, 0x9d, 0x4d, 0x4e, 0x51, 0x1f, 0x58, 0x44, 0x53, 0x1f, 0x40, 0x1f, 0x43, 0x51, 0x58, 0x1d, 0x9f, ], [0x41, 0x40, 0x51, 0x44, 0x1d, 0x1f, 0x52, 0x40, 0x4d, 0x43, 0x58, 0x1f, 0x47, 0x4e, 0x4b, 0x44, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4d, 0x4e, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x48, 0x4d, 0x1f, 0x48, 0xd3, 0x53, 0x4e, 0x1f, 0x52, 0x48, 0x53, 0x1f, 0x43, 0x4e, 0x56, 0x4d, 0x1f, 0x4e, 0x4d, 0x1f, 0x4e, 0x51, 0x9f, 0x53, 0x4e, 0x1f, 0x44, 0x40, 0x53, 0x1b, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x9f, 0x47, 0x4e, 0x41, 0x41, 0x48, 0x53, 0x1c, 0x47, 0x4e, 0x4b, 0x44, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4c, 0x44, 0x40, 0x4d, 0x52, 0x9f, 0x42, 0x4e, 0x4c, 0x45, 0x4e, 0x51, 0x53, 0x1b, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, ]], +[[0x5d, 0x6, 0x4e, 0x4e, 0x43, 0x1f, 0xc, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1a, 0x5d, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1f, 0x1, 0x48, 0x4b, 0x41, 0x4e, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0xc4, 0x4c, 0x44, 0x40, 0x4d, 0x53, 0x1f, 0x48, 0x53, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x1f, 0x52, 0x54, 0x4d, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x52, 0x47, 0x48, 0x4d, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x46, 0x51, 0x40, 0x52, 0x52, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x55, 0x44, 0x51, 0xd8, 0x46, 0x51, 0x44, 0x44, 0x4d, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x6, 0x40, 0x4d, 0x43, 0x40, 0x4b, 0xc5, 0x4b, 0x4e, 0x4e, 0x4a, 0x44, 0x43, 0x1f, 0x40, 0x53, 0x1f, 0x47, 0x48, 0x4c, 0x1f, 0x45, 0x51, 0x4e, 0xcc, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x4b, 0x4e, 0x4d, 0x46, 0x1f, 0x41, 0x54, 0x52, 0x47, 0x58, 0x9f, ], [0x44, 0x58, 0x44, 0x41, 0x51, 0x4e, 0x56, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x52, 0x53, 0x54, 0x42, 0x4a, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x45, 0x54, 0x51, 0x53, 0x47, 0x44, 0x51, 0x9f, 0x53, 0x47, 0x40, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x41, 0x51, 0x48, 0x4c, 0x1f, 0x4e, 0x45, 0x9f, 0x47, 0x48, 0x52, 0x1f, 0x52, 0x47, 0x40, 0x43, 0x58, 0x1f, 0x47, 0x40, 0x53, 0x1b, 0x9f, 0x5d, 0x16, 0x47, 0x40, 0x53, 0x1f, 0x43, 0x4e, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x4c, 0x44, 0x40, 0x4d, 0x1e, 0x5d, 0x1f, 0x47, 0x44, 0x1f, 0x52, 0x40, 0x48, 0x43, 0x1b, 0x9f, 0x5d, 0x3, 0x4e, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x56, 0x48, 0x52, 0x47, 0x1f, 0x4c, 0x44, 0x1f, 0x40, 0x9f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x4e, 0x51, 0x9f, ], [0x4c, 0x44, 0x40, 0x4d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x9f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x9f, 0x56, 0x47, 0x44, 0x53, 0x47, 0x44, 0x51, 0x1f, 0x8, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x4e, 0x51, 0x1f, 0x4d, 0x4e, 0x53, 0x1d, 0x1f, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x45, 0x44, 0x44, 0x4b, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x53, 0x47, 0x48, 0xd2, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x48, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x1f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0xce, 0x41, 0x44, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x4e, 0x4d, 0x1e, 0x5d, 0x1f, 0x5d, 0x0, 0x4b, 0x4b, 0x9f, ], [0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x40, 0x53, 0x1f, 0x4e, 0x4d, 0x42, 0x44, 0x1d, 0x5d, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1f, 0x1, 0x48, 0x4b, 0x41, 0x4e, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5d, 0x6, 0x4e, 0x4e, 0x43, 0x1f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1a, 0x5d, 0x1f, 0x47, 0xc4, 0x52, 0x40, 0x48, 0x43, 0x1f, 0x40, 0x53, 0x1f, 0x4b, 0x40, 0x52, 0x53, 0x1b, 0x1f, 0x5d, 0x16, 0x44, 0x9f, 0x43, 0x4e, 0x4d, 0x5d, 0x53, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x40, 0x4d, 0x58, 0x9f, 0x40, 0x43, 0x55, 0x44, 0x4d, 0x53, 0x54, 0x51, 0x44, 0x52, 0x1f, 0x47, 0x44, 0x51, 0x44, 0x1d, 0x9f, 0x53, 0x47, 0x40, 0x4d, 0x4a, 0x1f, 0x58, 0x4e, 0x54, 0x1a, 0x1f, 0x18, 0x4e, 0x54, 0x9f, 0x4c, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x53, 0x51, 0x58, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x13, 0x47, 0xc4, 0x7, 0x48, 0x4b, 0x4b, 0x1f, 0x4e, 0x51, 0x1f, 0x40, 0x42, 0x51, 0x4e, 0x52, 0x52, 0x1f, 0x13, 0x47, 0xc4, 0x16, 0x40, 0x53, 0x44, 0x51, 0x1b, 0x5d, 0x1f, 0x1, 0x58, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x47, 0xc4, ], [0x4c, 0x44, 0x40, 0x4d, 0x53, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x42, 0x4e, 0x4d, 0x55, 0x44, 0x51, 0x52, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x40, 0x53, 0x1f, 0x40, 0x4d, 0x1f, 0x44, 0x4d, 0x43, 0x1b, 0x1f, 0x5d, 0x16, 0x47, 0x40, 0x53, 0x1f, 0xc0, 0x4b, 0x4e, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x43, 0x4e, 0x1f, 0x54, 0x52, 0x44, 0x1f, 0x6, 0x4e, 0x4e, 0x43, 0x9f, 0x4c, 0x4e, 0x51, 0x4d, 0x48, 0x4d, 0x46, 0x1f, 0x45, 0x4e, 0x51, 0x1a, 0x5d, 0x1f, 0x52, 0x40, 0x48, 0xc3, 0x6, 0x40, 0x4d, 0x43, 0x40, 0x4b, 0x45, 0x1b, 0x1f, 0x5d, 0xd, 0x4e, 0x56, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x4c, 0x44, 0x40, 0x4d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x56, 0x40, 0x4d, 0xd3, ], [0x53, 0x4e, 0x1f, 0x46, 0x44, 0x53, 0x1f, 0x51, 0x48, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x44, 0x1d, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x4e, 0x4d, 0x5d, 0x53, 0x9f, 0x41, 0x44, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x53, 0x48, 0x4b, 0x4b, 0x1f, 0x8, 0x9f, 0x4c, 0x4e, 0x55, 0x44, 0x1f, 0x4e, 0x45, 0x45, 0x1b, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5d, 0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x9f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x48, 0x45, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x45, 0x48, 0x4d, 0x43, 0x9f, 0x52, 0x4e, 0x4c, 0x44, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x9f, 0x42, 0x44, 0x51, 0x53, 0x40, 0x48, 0x4d, 0x4b, 0x58, 0x1f, 0x54, 0x52, 0x54, 0x40, 0x4b, 0x4b, 0x58, 0x9f, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x52, 0x4e, 0x4c, 0x44, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x48, 0xc5, 0x58, 0x4e, 0x54, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x48, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x9f, ], [0x50, 0x54, 0x48, 0x53, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x4e, 0x4c, 0x44, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x56, 0x44, 0x51, 0xc4, 0x40, 0x45, 0x53, 0x44, 0x51, 0x1b, 0x5d, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x3, 0x51, 0x54, 0x4c, 0x4c, 0x44, 0x51, 0x1d, 0x1f, 0x41, 0x44, 0x40, 0x53, 0x1d, 0x1f, 0x40, 0x4d, 0xc3, 0x4f, 0x48, 0x4f, 0x44, 0x51, 0x1d, 0x1f, 0x41, 0x4b, 0x4e, 0x56, 0x1b, 0x9f, 0x7, 0x40, 0x51, 0x4f, 0x44, 0x51, 0x1d, 0x1f, 0x52, 0x53, 0x51, 0x48, 0x4a, 0x44, 0x1d, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x52, 0x4e, 0x4b, 0x43, 0x48, 0x44, 0x51, 0x1d, 0x1f, 0x46, 0x4e, 0x1b, 0x9f, 0x5, 0x51, 0x44, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x45, 0x4b, 0x40, 0x4c, 0x44, 0x1f, 0x40, 0x4d, 0xc3, 0x52, 0x44, 0x40, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x46, 0x51, 0x40, 0x52, 0x52, 0x44, 0x52, 0x1d, 0x9f, 0x13, 0x48, 0x4b, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x40, 0x56, 0x4d, 0x48, 0x4d, 0x46, 0x9f, 0x11, 0x44, 0x43, 0x1f, 0x12, 0x53, 0x40, 0x51, 0x1f, 0x4f, 0x40, 0x52, 0x52, 0x44, 0x52, 0x1b, 0x9f, ], [0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x53, 0x44, 0x40, 0x51, 0x52, 0x1f, 0x8, 0x1f, 0x45, 0x44, 0x44, 0x4b, 0x9f, 0x53, 0x4e, 0x43, 0x40, 0x58, 0x1f, 0x8, 0x5d, 0x4b, 0x4b, 0x1f, 0x56, 0x40, 0x48, 0x53, 0x1f, 0x53, 0xce, 0x52, 0x47, 0x44, 0x43, 0x1f, 0x53, 0x4e, 0x4c, 0x4e, 0x51, 0x51, 0x4e, 0x56, 0x1b, 0x9f, 0x13, 0x47, 0x4e, 0x54, 0x46, 0x47, 0x1f, 0x8, 0x5d, 0x4b, 0x4b, 0x1f, 0x4d, 0x4e, 0x53, 0x9f, 0x52, 0x4b, 0x44, 0x44, 0x4f, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x4d, 0x48, 0x46, 0x47, 0x53, 0x9f, 0xd, 0x4e, 0x51, 0x1f, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x52, 0x54, 0x51, 0x42, 0x44, 0x40, 0x52, 0x44, 0x9f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x52, 0x4e, 0x51, 0x51, 0x4e, 0x56, 0x1b, 0x1f, 0xc, 0x58, 0x9f, 0x44, 0x58, 0x44, 0x52, 0x1f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x4a, 0x44, 0x44, 0x4f, 0x9f, ], [0x53, 0x47, 0x44, 0x48, 0x51, 0x1f, 0x52, 0x48, 0x46, 0x47, 0x53, 0x1b, 0x1f, 0x8, 0x9f, 0x43, 0x40, 0x51, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x41, 0x44, 0x9f, 0x53, 0x44, 0x40, 0x51, 0x1c, 0x41, 0x4b, 0x48, 0x4d, 0x43, 0x44, 0x43, 0x1b, 0x1f, 0x8, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x41, 0x44, 0x1f, 0x45, 0x51, 0x44, 0x44, 0x1f, 0x53, 0x4e, 0x9f, 0x53, 0x40, 0x4b, 0x4a, 0x1f, 0xd, 0x4e, 0x53, 0x1f, 0x42, 0x47, 0x4e, 0x4a, 0x44, 0x43, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x46, 0x51, 0x48, 0x44, 0x45, 0x1d, 0x9f, 0x42, 0x4b, 0x44, 0x40, 0x51, 0x1c, 0x4c, 0x48, 0x4d, 0x43, 0x44, 0x43, 0x1b, 0x1f, 0xc, 0x58, 0x9f, 0x4c, 0x4e, 0x54, 0x53, 0x47, 0x1f, 0x42, 0x40, 0x4d, 0x4d, 0x4e, 0x53, 0x9f, ], [0x41, 0x44, 0x53, 0x51, 0x40, 0x58, 0x1f, 0x13, 0x47, 0x44, 0x1f, 0x40, 0x4d, 0x46, 0x54, 0x48, 0x52, 0xc7, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x8, 0x1f, 0x4a, 0x4d, 0x4e, 0x56, 0x1b, 0x1f, 0x18, 0x44, 0x52, 0x1d, 0x9f, 0x8, 0x5d, 0x4b, 0x4b, 0x1f, 0x4a, 0x44, 0x44, 0x4f, 0x1f, 0x4c, 0x58, 0x1f, 0x53, 0x44, 0x40, 0x51, 0xd2, 0x53, 0x48, 0x4b, 0x1f, 0x4b, 0x40, 0x53, 0x44, 0x51, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x4c, 0x58, 0x9f, 0x46, 0x51, 0x48, 0x44, 0x45, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x46, 0x4e, 0x1b, 0x1f, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x16, 0x47, 0x4e, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x52, 0x1d, 0x1f, 0x2, 0x40, 0x4d, 0x1b, 0x9f, 0x16, 0x47, 0x4e, 0x1f, 0x53, 0x51, 0x48, 0x44, 0x52, 0x1d, 0x1f, 0x3, 0x4e, 0x44, 0x52, 0x1b, 0x9f, 0x16, 0x47, 0x4e, 0x1f, 0x4b, 0x4e, 0x55, 0x44, 0x52, 0x1d, 0x1f, 0xb, 0x48, 0x55, 0x44, 0x52, 0x1b, 0x9f, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x53, 0x53, 0x4b, 0x44, 0x1f, 0x50, 0x54, 0x44, 0x44, 0x4d, 0x9f, 0x40, 0x4b, 0x4b, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x44, 0x4d, 0x1f, 0x5, 0x4b, 0x44, 0x56, 0x9f, 0x47, 0x48, 0x52, 0x52, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x44, 0x40, 0x1b, 0x1f, 0x13, 0x4e, 0x1f, 0x52, 0x53, 0x4e, 0x4f, 0x1f, 0x44, 0x40, 0x42, 0x47, 0x9f, 0x56, 0x40, 0x55, 0x44, 0x1f, 0x7, 0x44, 0x51, 0x1f, 0x42, 0x4b, 0x54, 0x53, 0x42, 0x47, 0x1f, 0x53, 0xce, 0x52, 0x40, 0x55, 0x44, 0x1f, 0x12, 0x47, 0x44, 0x1f, 0x55, 0x44, 0x4d, 0x53, 0x54, 0x51, 0x44, 0x43, 0x9f, 0x41, 0x51, 0x40, 0x55, 0x44, 0x4b, 0x58, 0x1b, 0x1f, 0x0, 0x52, 0x1f, 0x52, 0x47, 0x44, 0x9f, 0x40, 0x53, 0x53, 0x40, 0x42, 0x4a, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x44, 0x40, 0x9f, ], [0x48, 0x4d, 0x1f, 0x51, 0x40, 0x46, 0x44, 0x1f, 0x0, 0x9f, 0x47, 0x4e, 0x4b, 0x43, 0x44, 0x51, 0x4c, 0x40, 0x4d, 0x1f, 0x42, 0x40, 0x4c, 0x44, 0x9f, 0x4d, 0x48, 0x46, 0x47, 0x1f, 0x0, 0x4b, 0x4e, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x40, 0x4d, 0x43, 0x1f, 0x5, 0x48, 0x52, 0x47, 0x4d, 0x44, 0x53, 0x1f, 0x48, 0x4d, 0x9f, 0x47, 0x40, 0x4d, 0x43, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x52, 0x40, 0x56, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x50, 0x54, 0x44, 0x44, 0x4d, 0x1f, 0x4c, 0x48, 0x43, 0x52, 0x4a, 0x58, 0x1b, 0x1f, 0x7, 0x44, 0x9f, 0x52, 0x53, 0x40, 0x51, 0x44, 0x43, 0x1f, 0x40, 0x53, 0x1f, 0x47, 0x44, 0x51, 0x1f, 0x48, 0x4d, 0x9f, 0x56, 0x4e, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x5, 0x4e, 0x51, 0x1f, 0x4e, 0x45, 0x53, 0x44, 0x4d, 0x9f, ], [0x47, 0x44, 0x5d, 0x43, 0x1f, 0x41, 0x44, 0x44, 0x4d, 0x1f, 0x53, 0x4e, 0x4b, 0x43, 0x9f, 0x13, 0x47, 0x40, 0x53, 0x1f, 0x52, 0x54, 0x42, 0x47, 0x1f, 0x40, 0x52, 0x1f, 0x52, 0x47, 0x44, 0x9f, 0x2, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x41, 0x44, 0x1f, 0x16, 0x47, 0xce, 0x47, 0x4e, 0x55, 0x44, 0x51, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1d, 0x9f, 0x41, 0x51, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x1b, 0x1f, 0x7, 0x44, 0x9f, 0x52, 0x40, 0x56, 0x1f, 0x47, 0x44, 0x51, 0x1f, 0x4f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x40, 0x4d, 0xc3, 0x50, 0x54, 0x48, 0x42, 0x4a, 0x4b, 0x58, 0x1f, 0x7, 0x44, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x44, 0x43, 0x9f, 0x54, 0x4f, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x42, 0x4b, 0x48, 0x45, 0x45, 0x1f, 0x47, 0x44, 0x9f, ], [0x45, 0x40, 0x42, 0x44, 0x43, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x52, 0x40, 0x56, 0x1f, 0x40, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x1f, 0x0, 0x41, 0x4e, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x40, 0x55, 0x44, 0x1f, 0x8, 0x4d, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x47, 0x44, 0x51, 0x9f, 0x44, 0x46, 0x46, 0x52, 0x1f, 0x47, 0x44, 0x1f, 0x4f, 0x4b, 0x40, 0x42, 0x44, 0x43, 0x1b, 0x9f, 0x13, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x53, 0x53, 0x4b, 0x44, 0x1f, 0x50, 0x54, 0x44, 0x44, 0x4d, 0x9f, 0x40, 0x4b, 0x4b, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x44, 0x4d, 0x1f, 0x14, 0x4f, 0x4e, 0x4d, 0x9f, 0x47, 0x48, 0x52, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0x43, 0x44, 0x51, 0x1f, 0x52, 0x53, 0x4e, 0x4e, 0xc3, 0x7, 0x44, 0x51, 0x1f, 0x44, 0x58, 0x44, 0x52, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x4b, 0x54, 0x44, 0x9f, ], [0x6, 0x4b, 0x4e, 0x56, 0x44, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x47, 0x44, 0x51, 0x1f, 0x53, 0x51, 0x54, 0xc4, 0x14, 0x4d, 0x43, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x46, 0x51, 0x40, 0x53, 0x48, 0x53, 0x54, 0x43, 0x44, 0x9b, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x7, 0x40, 0x51, 0x4f, 0x44, 0x51, 0x1d, 0x1f, 0x53, 0x51, 0x44, 0x40, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0xd1, 0x56, 0x4e, 0x51, 0x43, 0x52, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x42, 0x40, 0x51, 0x44, 0x9f, 0x5, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x4c, 0x40, 0x58, 0x1f, 0x42, 0x40, 0x54, 0x52, 0xc4, 0x49, 0x4e, 0x58, 0x1f, 0x4e, 0x51, 0x1f, 0x43, 0x44, 0x52, 0x4f, 0x40, 0x48, 0x51, 0x9f, 0x12, 0x48, 0x4d, 0x46, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x52, 0x4e, 0x4d, 0x46, 0x52, 0x1f, 0x4e, 0xc5, 0x47, 0x44, 0x40, 0x4b, 0x53, 0x47, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4b, 0x4e, 0x55, 0x44, 0x1f, 0xe, 0xc5, 0x43, 0x51, 0x40, 0x46, 0x4e, 0x4d, 0x52, 0x1f, 0x45, 0x4b, 0x40, 0x4c, 0x48, 0x4d, 0x46, 0x9f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x40, 0x41, 0x4e, 0x55, 0x44, 0x1b, 0x9f, ], [0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x4e, 0x4d, 0x4b, 0x58, 0x1f, 0x4e, 0x4d, 0xc4, 0x42, 0x40, 0x53, 0x42, 0x47, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x40, 0xd2, 0x2, 0x40, 0x53, 0x42, 0x47, 0x1c, 0x24, 0x24, 0x1d, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x9f, 0x52, 0x4f, 0x44, 0x42, 0x48, 0x45, 0x48, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x40, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x44, 0x51, 0x4d, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x4e, 0x4d, 0x44, 0x5d, 0x52, 0x9f, 0x52, 0x40, 0x45, 0x44, 0x53, 0x58, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x45, 0x40, 0x42, 0xc4, 0x4e, 0x45, 0x1f, 0x43, 0x40, 0x4d, 0x46, 0x44, 0x51, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x51, 0x44, 0x40, 0x4b, 0x1f, 0x40, 0x4d, 0x43, 0x9f, ], [0x48, 0x4c, 0x4c, 0x44, 0x43, 0x48, 0x40, 0x53, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x51, 0x4e, 0x42, 0x44, 0x52, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x51, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x4b, 0x1f, 0x4c, 0x48, 0x4d, 0x43, 0x1b, 0x1f, 0xe, 0x51, 0xd1, 0x56, 0x40, 0x52, 0x1f, 0x42, 0x51, 0x40, 0x59, 0x58, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x41, 0x44, 0x1f, 0x46, 0x51, 0x4e, 0x54, 0x4d, 0x43, 0x44, 0x43, 0x9b, 0x0, 0x4b, 0x4b, 0x1f, 0x47, 0x44, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x53, 0x4e, 0x1f, 0x43, 0x4e, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x40, 0x52, 0x4a, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x52, 0x9f, 0x52, 0x4e, 0x4e, 0x4d, 0x1f, 0x40, 0x52, 0x1f, 0x47, 0x44, 0x1f, 0x43, 0x48, 0x43, 0x1d, 0x1f, 0x47, 0xc4, ], [0x56, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x4d, 0x4e, 0x1f, 0x4b, 0x4e, 0x4d, 0x46, 0x44, 0x51, 0x1f, 0x41, 0xc4, 0x42, 0x51, 0x40, 0x59, 0x58, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x53, 0x4e, 0x1f, 0x45, 0x4b, 0x58, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x4c, 0x48, 0x52, 0x52, 0x48, 0x4e, 0x4d, 0x52, 0x1b, 0x1f, 0xe, 0x51, 0x51, 0x9f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x41, 0x44, 0x1f, 0x42, 0x51, 0x40, 0x59, 0x58, 0x1f, 0x53, 0x4e, 0x9f, 0x45, 0x4b, 0x58, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x1f, 0x4c, 0x48, 0x52, 0x52, 0x48, 0x4e, 0x4d, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x52, 0x40, 0x4d, 0x44, 0x1f, 0x48, 0x45, 0x1f, 0x47, 0x44, 0x9f, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x48, 0x45, 0x1f, 0x47, 0x44, 0x9f, ], [0x56, 0x40, 0x52, 0x1f, 0x52, 0x40, 0x4d, 0x44, 0x1f, 0x47, 0x44, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x53, 0xce, 0x45, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1b, 0x1f, 0x8, 0x45, 0x1f, 0x47, 0x44, 0x9f, 0x45, 0x4b, 0x44, 0x56, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x47, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x42, 0x51, 0x40, 0x59, 0x58, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x53, 0x4e, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x48, 0x45, 0x1f, 0x47, 0xc4, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x1f, 0x56, 0x40, 0x4d, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x47, 0x44, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x52, 0x40, 0x4d, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x40, 0x43, 0x9f, 0x53, 0x4e, 0x1b, 0x1f, 0x1c, 0x7, 0x44, 0x4b, 0x4b, 0x44, 0x51, 0x1f, 0x9f, ]], +[[0x5d, 0x13, 0x47, 0x44, 0x58, 0x5d, 0x51, 0x44, 0x1f, 0x53, 0x51, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0xce, 0x4a, 0x48, 0x4b, 0x4b, 0x1f, 0x4c, 0x44, 0x1d, 0x5d, 0x9f, 0x18, 0x4e, 0x52, 0x52, 0x40, 0x51, 0x48, 0x40, 0x4d, 0x1f, 0x53, 0x4e, 0x4b, 0x43, 0x1f, 0x47, 0x48, 0xcc, 0x42, 0x40, 0x4b, 0x4c, 0x4b, 0x58, 0x1b, 0x1f, 0x5d, 0xd, 0x4e, 0x1f, 0x4e, 0x4d, 0x44, 0x5d, 0x52, 0x9f, 0x53, 0x51, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x4a, 0x48, 0x4b, 0x4b, 0x9f, 0x58, 0x4e, 0x54, 0x1d, 0x5d, 0x1f, 0x2, 0x4b, 0x44, 0x55, 0x48, 0x4d, 0x46, 0x44, 0x51, 0x9f, 0x42, 0x51, 0x48, 0x44, 0x43, 0x1b, 0x1f, 0x5d, 0x13, 0x47, 0x44, 0x4d, 0x1f, 0x56, 0x47, 0x58, 0x9f, 0x40, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x52, 0x47, 0x4e, 0x4e, 0x53, 0x48, 0x4d, 0x46, 0x9f, ], [0x40, 0x53, 0x1f, 0x4c, 0x44, 0x1e, 0x5d, 0x1f, 0x18, 0x4e, 0x52, 0x52, 0x40, 0x51, 0x48, 0x40, 0x4d, 0x9f, 0x40, 0x52, 0x4a, 0x44, 0x43, 0x1b, 0x1f, 0x5d, 0x13, 0x47, 0x44, 0x58, 0x5d, 0x51, 0x44, 0x9f, 0x52, 0x47, 0x4e, 0x4e, 0x53, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0x53, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x4e, 0x4d, 0x44, 0x1d, 0x5d, 0x9f, 0x2, 0x4b, 0x44, 0x55, 0x48, 0x4d, 0x46, 0x44, 0x51, 0x9f, 0x40, 0x4d, 0x52, 0x56, 0x44, 0x51, 0x44, 0x43, 0x1b, 0x1f, 0x5d, 0x13, 0x47, 0x44, 0x58, 0x5d, 0x51, 0xc4, 0x53, 0x51, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x4a, 0x48, 0x4b, 0x4b, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x4e, 0x4d, 0x44, 0x1b, 0x5d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, ], [0x56, 0x47, 0x40, 0x53, 0x1f, 0x43, 0x48, 0x45, 0x45, 0x44, 0x51, 0x44, 0x4d, 0x42, 0x44, 0x9f, 0x43, 0x4e, 0x44, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4c, 0x40, 0x4a, 0x44, 0x1e, 0x9f, 0x1c, 0x7, 0x44, 0x4b, 0x4b, 0x44, 0x51, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x18, 0x4e, 0x54, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x41, 0x51, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x48, 0xcd, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x47, 0x44, 0x40, 0x43, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x45, 0x44, 0x44, 0x53, 0x1f, 0x48, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x9f, 0x52, 0x47, 0x4e, 0x44, 0x52, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x1f, 0x42, 0x40, 0x4d, 0x9f, 0x52, 0x53, 0x44, 0x44, 0x51, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x52, 0x44, 0x4b, 0x45, 0x1f, 0x40, 0x4d, 0xd8, 0x43, 0x48, 0x51, 0x44, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x42, 0x47, 0x4e, 0x4e, 0x52, 0x44, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x5d, 0x51, 0x44, 0x1f, 0x4e, 0x4d, 0x9f, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x4e, 0x56, 0x4d, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x58, 0x4e, 0x54, 0x9f, ], [0x4a, 0x4d, 0x4e, 0x56, 0x1f, 0x56, 0x47, 0x40, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x4a, 0x4d, 0x4e, 0x56, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x18, 0xe, 0x14, 0x1f, 0x40, 0x51, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4e, 0x4d, 0x44, 0x1f, 0x56, 0x47, 0x4e, 0x5d, 0x4b, 0x4b, 0x9f, 0x43, 0x44, 0x42, 0x48, 0x43, 0x44, 0x1f, 0x56, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x4e, 0x9f, 0x46, 0x4e, 0x1b, 0x1b, 0x1b, 0x1f, 0x1c, 0x12, 0x54, 0x44, 0x52, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x16, 0x47, 0x44, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x44, 0x4b, 0x48, 0x4c, 0x48, 0x4d, 0x40, 0x53, 0x44, 0x43, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x48, 0x52, 0x9f, 0x48, 0x4c, 0x4f, 0x4e, 0x52, 0x52, 0x48, 0x41, 0x4b, 0x44, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x4d, 0x9f, 0x56, 0x47, 0x40, 0x53, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x51, 0x44, 0x4c, 0x40, 0x48, 0x4d, 0x52, 0x1d, 0x9f, 0x47, 0x4e, 0x56, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x48, 0x4c, 0x4f, 0x51, 0x4e, 0x41, 0x40, 0x41, 0x4b, 0x44, 0x1d, 0x1f, 0x4c, 0x54, 0x52, 0x53, 0x9f, 0x41, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x53, 0x51, 0x54, 0x53, 0x47, 0x1b, 0x9f, ], [0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5d, 0xc, 0x58, 0x1f, 0x4c, 0x48, 0x4d, 0x43, 0x1d, 0x5d, 0x1f, 0x47, 0x44, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1d, 0x1f, 0x5d, 0x51, 0x44, 0x41, 0x44, 0x4b, 0x52, 0x1f, 0x40, 0x53, 0x9f, 0x52, 0x53, 0x40, 0x46, 0x4d, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x1f, 0x6, 0x48, 0x55, 0x44, 0x9f, 0x4c, 0x44, 0x1f, 0x4f, 0x51, 0x4e, 0x41, 0x4b, 0x44, 0x4c, 0x52, 0x1d, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x9f, 0x4c, 0x44, 0x1f, 0x56, 0x4e, 0x51, 0x4a, 0x1d, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x1f, 0x4c, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x40, 0x41, 0x52, 0x53, 0x51, 0x54, 0x52, 0x44, 0x9f, 0x42, 0x51, 0x58, 0x4f, 0x53, 0x4e, 0x46, 0x51, 0x40, 0x4c, 0x1f, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x48, 0x4d, 0x53, 0x51, 0x48, 0x42, 0x40, 0x53, 0x44, 0x9f, ], [0x40, 0x4d, 0x40, 0x4b, 0x58, 0x52, 0x48, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x8, 0x1f, 0x40, 0xcc, 0x48, 0x4d, 0x1f, 0x4c, 0x58, 0x1f, 0x4e, 0x56, 0x4d, 0x1f, 0x4f, 0x51, 0x4e, 0x4f, 0x44, 0x51, 0x9f, 0x40, 0x53, 0x4c, 0x4e, 0x52, 0x4f, 0x47, 0x44, 0x51, 0x44, 0x1b, 0x1f, 0x8, 0x1f, 0x42, 0x40, 0x4d, 0x9f, 0x43, 0x48, 0x52, 0x4f, 0x44, 0x4d, 0x52, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x4d, 0x1f, 0x56, 0x48, 0x53, 0xc7, 0x40, 0x51, 0x53, 0x48, 0x45, 0x48, 0x42, 0x48, 0x40, 0x4b, 0x9f, 0x52, 0x53, 0x48, 0x4c, 0x54, 0x4b, 0x40, 0x4d, 0x53, 0x52, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x8, 0x9f, 0x40, 0x41, 0x47, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x54, 0x4b, 0x4b, 0x9f, 0x51, 0x4e, 0x54, 0x53, 0x48, 0x4d, 0x44, 0x1f, 0x4e, 0x45, 0x9f, ], [0x44, 0x57, 0x48, 0x52, 0x53, 0x44, 0x4d, 0x42, 0x44, 0x1b, 0x1f, 0x8, 0x1f, 0x42, 0x51, 0x40, 0x55, 0xc4, 0x45, 0x4e, 0x51, 0x1f, 0x4c, 0x44, 0x4d, 0x53, 0x40, 0x4b, 0x9f, 0x44, 0x57, 0x40, 0x4b, 0x53, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x1f, 0x13, 0x47, 0x40, 0x53, 0x9f, 0x48, 0x52, 0x1f, 0x56, 0x47, 0x58, 0x1f, 0x8, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x42, 0x47, 0x4e, 0x52, 0x44, 0x4d, 0x1f, 0x4c, 0x58, 0x1f, 0x4e, 0x56, 0x4d, 0x9f, 0x4f, 0x40, 0x51, 0x53, 0x48, 0x42, 0x54, 0x4b, 0x40, 0x51, 0x9f, 0x4f, 0x51, 0x4e, 0x45, 0x44, 0x52, 0x52, 0x48, 0x4e, 0x4d, 0x1d, 0x1f, 0x4e, 0x51, 0x9f, 0x51, 0x40, 0x53, 0x47, 0x44, 0x51, 0x1f, 0x42, 0x51, 0x44, 0x40, 0x53, 0x44, 0x43, 0x1f, 0x48, 0x53, 0x9d, ], [0x45, 0x4e, 0x51, 0x1f, 0x8, 0x1f, 0x40, 0x4c, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4e, 0x4d, 0x4b, 0x58, 0x9f, 0x4e, 0x4d, 0x44, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1b, 0xdd, 0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xb, 0x48, 0x45, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x4b, 0xd8, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x9f, 0x40, 0x4d, 0x58, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x53, 0x47, 0xc4, 0x4c, 0x48, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x48, 0x4d, 0x55, 0x44, 0x4d, 0x53, 0x1b, 0x1f, 0x16, 0x44, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x43, 0x40, 0x51, 0x44, 0x1f, 0x53, 0x4e, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x44, 0x48, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x40, 0x51, 0x44, 0x9f, ], [0x51, 0x44, 0x40, 0x4b, 0x4b, 0x58, 0x1f, 0x4c, 0x44, 0x51, 0x44, 0x9f, 0x42, 0x4e, 0x4c, 0x4c, 0x4e, 0x4d, 0x4f, 0x4b, 0x40, 0x42, 0x44, 0x52, 0x1f, 0x4e, 0x45, 0x9f, 0x44, 0x57, 0x48, 0x52, 0x53, 0x44, 0x4d, 0x42, 0x44, 0x1b, 0x1f, 0x8, 0x45, 0x1f, 0x56, 0x44, 0x9f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x45, 0x4b, 0x58, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x4e, 0x45, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x48, 0x4d, 0x43, 0x4e, 0x56, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x9f, 0x48, 0x4d, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x1d, 0x1f, 0x47, 0x4e, 0x55, 0x44, 0x51, 0x9f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x46, 0x51, 0x44, 0x40, 0x53, 0x9f, 0x42, 0x48, 0x53, 0x58, 0x1d, 0x1f, 0x46, 0x44, 0x4d, 0x53, 0x4b, 0x58, 0x9f, ], [0x51, 0x44, 0x4c, 0x4e, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x51, 0x4e, 0x4e, 0x45, 0x52, 0x1d, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x44, 0x44, 0x4f, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0xd3, 0x53, 0x47, 0x44, 0x1f, 0x50, 0x54, 0x44, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x46, 0x4e, 0x48, 0x4d, 0x46, 0x9f, 0x4e, 0x4d, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x9f, 0x42, 0x4e, 0x48, 0x4d, 0x42, 0x48, 0x43, 0x44, 0x4d, 0x42, 0x44, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x4b, 0x40, 0x4d, 0x4d, 0x48, 0x4d, 0x46, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x42, 0x51, 0x4e, 0x52, 0x52, 0x1c, 0x4f, 0x54, 0x51, 0x4f, 0x4e, 0x52, 0x44, 0x52, 0x1d, 0x9f, ], [0x53, 0x47, 0x44, 0x1f, 0x56, 0x4e, 0x4d, 0x43, 0x44, 0x51, 0x45, 0x54, 0x4b, 0x9f, 0x42, 0x47, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x44, 0x55, 0x44, 0x4d, 0x53, 0x52, 0x1d, 0x9f, 0x56, 0x4e, 0x51, 0x4a, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x46, 0x44, 0x4d, 0x44, 0x51, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4b, 0x44, 0x40, 0x43, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x4e, 0x54, 0x53, 0x51, 0x44, 0x9f, 0x51, 0x44, 0x52, 0x54, 0x4b, 0x53, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4c, 0x40, 0x4a, 0x44, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x45, 0x48, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x9f, ], [0x56, 0x48, 0x53, 0x47, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x42, 0x4e, 0x4d, 0x55, 0x44, 0x4d, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x4b, 0x48, 0x53, 0x48, 0x44, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x45, 0x4e, 0x51, 0x44, 0x52, 0x44, 0x44, 0x4d, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x4b, 0x54, 0x52, 0x48, 0x4e, 0x4d, 0x52, 0x1f, 0x4c, 0x4e, 0x52, 0x53, 0x9f, 0x52, 0x53, 0x40, 0x4b, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x54, 0x4d, 0x4f, 0x51, 0x4e, 0x45, 0x48, 0x53, 0x40, 0x41, 0x4b, 0x44, 0x1b, 0x9f, 0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x52, 0x53, 0x4e, 0x51, 0x58, 0x1f, 0x52, 0x4e, 0x1f, 0x45, 0x40, 0x51, 0x1b, 0x9f, 0x8, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x41, 0x44, 0x46, 0x48, 0x4d, 0x4d, 0x48, 0x4d, 0x46, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x14, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x42, 0x51, 0x44, 0x40, 0x53, 0x44, 0x43, 0x1b, 0x1f, 0x13, 0x47, 0x48, 0x52, 0x1f, 0x47, 0x40, 0x52, 0x9f, 0x4c, 0x40, 0x43, 0x44, 0x1f, 0x40, 0x1f, 0x4b, 0x4e, 0x53, 0x1f, 0x4e, 0x45, 0x9f, 0x4f, 0x44, 0x4e, 0x4f, 0x4b, 0x44, 0x1f, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x40, 0x4d, 0x46, 0x51, 0x58, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x41, 0x44, 0x44, 0x4d, 0x1f, 0x56, 0x48, 0x43, 0x44, 0x4b, 0x58, 0x9f, 0x51, 0x44, 0x46, 0x40, 0x51, 0x43, 0x44, 0x43, 0x1f, 0x40, 0x52, 0x1f, 0x40, 0x1f, 0x41, 0x40, 0x43, 0x9f, ], [0x4c, 0x4e, 0x55, 0x44, 0x1b, 0x1f, 0x1c, 0x0, 0x43, 0x40, 0x4c, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5, 0x4e, 0x51, 0x1f, 0x48, 0x4d, 0x52, 0x53, 0x40, 0x4d, 0x42, 0x44, 0x1d, 0x1f, 0x4e, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4f, 0x4b, 0x40, 0x4d, 0x44, 0x53, 0x1f, 0x4, 0x40, 0x51, 0x53, 0x47, 0x1d, 0x9f, 0x4c, 0x40, 0x4d, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x9f, 0x40, 0x52, 0x52, 0x54, 0x4c, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x47, 0x44, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x48, 0x4d, 0x53, 0x44, 0x4b, 0x4b, 0x48, 0x46, 0x44, 0x4d, 0x53, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x9f, 0x43, 0x4e, 0x4b, 0x4f, 0x47, 0x48, 0x4d, 0x52, 0x1f, 0x41, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x9f, 0x47, 0x44, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x40, 0x42, 0x47, 0x48, 0x44, 0x55, 0x44, 0x43, 0x1f, 0x52, 0xce, ], [0x4c, 0x54, 0x42, 0x47, 0x1c, 0x53, 0x47, 0x44, 0x1f, 0x56, 0x47, 0x44, 0x44, 0x4b, 0x1d, 0x9f, 0xd, 0x44, 0x56, 0x1f, 0x18, 0x4e, 0x51, 0x4a, 0x1d, 0x1f, 0x56, 0x40, 0x51, 0x52, 0x1f, 0x40, 0x4d, 0xc3, 0x52, 0x4e, 0x1f, 0x4e, 0x4d, 0x1c, 0x56, 0x47, 0x48, 0x4b, 0x52, 0x53, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x4e, 0x4b, 0x4f, 0x47, 0x48, 0x4d, 0x52, 0x1f, 0x47, 0x40, 0x43, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x43, 0x4e, 0x4d, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x4c, 0x54, 0x42, 0xca, 0x40, 0x41, 0x4e, 0x54, 0x53, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x56, 0x40, 0x53, 0x44, 0xd1, 0x47, 0x40, 0x55, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x9f, 0x53, 0x48, 0x4c, 0x44, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x9f, ], [0x42, 0x4e, 0x4d, 0x55, 0x44, 0x51, 0x52, 0x44, 0x4b, 0x58, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x43, 0x4e, 0x4b, 0x4f, 0x47, 0x48, 0x4d, 0x52, 0x1f, 0x47, 0x40, 0x43, 0x9f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x1f, 0x41, 0x44, 0x4b, 0x48, 0x44, 0x55, 0x44, 0x43, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x45, 0x40, 0xd1, 0x4c, 0x4e, 0x51, 0x44, 0x1f, 0x48, 0x4d, 0x53, 0x44, 0x4b, 0x4b, 0x48, 0x46, 0x44, 0x4d, 0x53, 0x9f, 0x53, 0x47, 0x40, 0x4d, 0x1f, 0x4c, 0x40, 0x4d, 0x1b, 0x45, 0x4e, 0x51, 0x9f, 0x4f, 0x51, 0x44, 0x42, 0x48, 0x52, 0x44, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x40, 0x4c, 0xc4, 0x51, 0x44, 0x40, 0x52, 0x4e, 0x4d, 0x52, 0x1b, 0x1f, 0x1c, 0x0, 0x43, 0x40, 0x4c, 0x52, 0x1f, 0x9f, ]], +[[0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x4a, 0x4d, 0x4e, 0x56, 0x4d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x40, 0x4d, 0x9f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x1f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0x51, 0x1f, 0x4e, 0xc5, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x52, 0x1d, 0x1f, 0x52, 0x48, 0x4c, 0x4f, 0x4b, 0x58, 0x9f, 0x41, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x48, 0x52, 0x9f, 0x40, 0x4d, 0x1f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x1f, 0x40, 0x4c, 0x4e, 0x54, 0x4d, 0xd3, 0x4e, 0x45, 0x1f, 0x52, 0x4f, 0x40, 0x42, 0x44, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x9f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x1f, 0x48, 0x4d, 0x1b, 0x1f, 0x7, 0x4e, 0x56, 0x44, 0x55, 0x44, 0x51, 0x9d, ], [0x4d, 0x4e, 0x53, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x4e, 0x4d, 0x44, 0x1f, 0x4e, 0x45, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0x52, 0x1f, 0x48, 0x4d, 0x47, 0x40, 0x41, 0x48, 0x53, 0x44, 0x43, 0x9b, 0x13, 0x47, 0x44, 0x51, 0x44, 0x45, 0x4e, 0x51, 0x44, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x41, 0x44, 0x1f, 0x40, 0x1f, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x9f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0x51, 0x1f, 0x4e, 0x45, 0x9f, 0x48, 0x4d, 0x47, 0x40, 0x41, 0x48, 0x53, 0x44, 0x43, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x52, 0x1b, 0x9f, 0x0, 0x4d, 0x58, 0x1f, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x1f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0x51, 0x9f, 0x43, 0x48, 0x55, 0x48, 0x43, 0x44, 0x43, 0x1f, 0x41, 0x58, 0x9f, ], [0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x58, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x52, 0x9f, 0x4d, 0x44, 0x40, 0x51, 0x1f, 0x53, 0x4e, 0x1f, 0x4d, 0x4e, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0xd2, 0x4c, 0x40, 0x4a, 0x44, 0x52, 0x1f, 0x4d, 0x4e, 0x1f, 0x4e, 0x43, 0x43, 0x52, 0x1d, 0x1f, 0x52, 0x4e, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x40, 0x55, 0x44, 0x51, 0x40, 0x46, 0x44, 0x9f, 0x4f, 0x4e, 0x4f, 0x54, 0x4b, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4f, 0x4b, 0x40, 0x4d, 0x44, 0x53, 0x52, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0xc4, 0x14, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x42, 0x40, 0x4d, 0x1f, 0x41, 0x44, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x1f, 0x59, 0x44, 0x51, 0x4e, 0x1b, 0x9f, ], [0x5, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x48, 0x53, 0x9f, 0x45, 0x4e, 0x4b, 0x4b, 0x4e, 0x56, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x4e, 0x4f, 0x54, 0x4b, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x47, 0x4e, 0x4b, 0x44, 0x1f, 0x14, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x48, 0x52, 0x9f, 0x40, 0x4b, 0x52, 0x4e, 0x1f, 0x59, 0x44, 0x51, 0x4e, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x40, 0x4d, 0x58, 0x1f, 0x4f, 0x44, 0x4e, 0x4f, 0x4b, 0x44, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x4c, 0x40, 0x58, 0x1f, 0x4c, 0x44, 0x44, 0x53, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x9f, 0x53, 0x48, 0x4c, 0x44, 0x1f, 0x53, 0x4e, 0x1f, 0x53, 0x48, 0x4c, 0x44, 0x1f, 0x40, 0x51, 0x44, 0x9f, ], [0x4c, 0x44, 0x51, 0x44, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x51, 0x4e, 0x43, 0x54, 0x42, 0x53, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x43, 0x44, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x43, 0x9f, 0x48, 0x4c, 0x40, 0x46, 0x48, 0x4d, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x9f, 0x1c, 0x0, 0x43, 0x40, 0x4c, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5, 0x40, 0x51, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x48, 0x52, 0x53, 0xd8, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x42, 0x4e, 0x4b, 0x43, 0x1f, 0x13, 0x4e, 0x9f, 0x43, 0x54, 0x4d, 0x46, 0x44, 0x4e, 0x4d, 0x52, 0x1f, 0x43, 0x44, 0x44, 0x4f, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x51, 0x4d, 0x52, 0x1f, 0x4e, 0x4b, 0x43, 0x1f, 0x16, 0x44, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x1d, 0x1f, 0x44, 0x51, 0x44, 0x9f, 0x41, 0x51, 0x44, 0x40, 0x4a, 0x1f, 0x4e, 0x45, 0x1f, 0x43, 0x40, 0x58, 0x1d, 0x1f, 0x13, 0x4e, 0x9f, 0x52, 0x44, 0x44, 0x4a, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4f, 0x40, 0x4b, 0x44, 0x9f, 0x44, 0x4d, 0x42, 0x47, 0x40, 0x4d, 0x53, 0x44, 0x43, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x1b, 0x9f, ], [0x13, 0x47, 0x44, 0x1f, 0x43, 0x56, 0x40, 0x51, 0x55, 0x44, 0x52, 0x1f, 0x4e, 0x45, 0x9f, 0x58, 0x4e, 0x51, 0x44, 0x1f, 0x4c, 0x40, 0x43, 0x44, 0x1f, 0x4c, 0x48, 0x46, 0x47, 0x53, 0x58, 0x9f, 0x52, 0x4f, 0x44, 0x4b, 0x4b, 0x52, 0x1d, 0x1f, 0x16, 0x47, 0x48, 0x4b, 0x44, 0x9f, 0x47, 0x40, 0x4c, 0x4c, 0x44, 0x51, 0x52, 0x1f, 0x45, 0x44, 0x4b, 0x4b, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x9f, 0x51, 0x48, 0x4d, 0x46, 0x48, 0x4d, 0x46, 0x1f, 0x41, 0x44, 0x4b, 0x4b, 0x52, 0x1f, 0x8, 0x4d, 0x9f, 0x4f, 0x4b, 0x40, 0x42, 0x44, 0x52, 0x1f, 0x43, 0x44, 0x44, 0x4f, 0x1d, 0x1f, 0x56, 0x47, 0x44, 0x51, 0xc4, 0x43, 0x40, 0x51, 0x4a, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x52, 0x4b, 0x44, 0x44, 0x4f, 0x9d, 0x8, 0x4d, 0x1f, 0x47, 0x4e, 0x4b, 0x4b, 0x4e, 0x56, 0x1f, 0x47, 0x40, 0x4b, 0x4b, 0x52, 0x9f, ], [0x41, 0x44, 0x4d, 0x44, 0x40, 0x53, 0x47, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x45, 0x44, 0x4b, 0x4b, 0x52, 0x9b, 0x5, 0x4e, 0x51, 0x1f, 0x40, 0x4d, 0x42, 0x48, 0x44, 0x4d, 0x53, 0x1f, 0x4a, 0x48, 0x4d, 0x46, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x44, 0x4b, 0x55, 0x48, 0x52, 0x47, 0x1f, 0x4b, 0x4e, 0x51, 0x43, 0x9f, 0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4c, 0x40, 0x4d, 0x58, 0x1f, 0x40, 0x9f, 0x46, 0x4b, 0x44, 0x40, 0x4c, 0x48, 0x4d, 0x46, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x44, 0x4d, 0x9f, 0x47, 0x4e, 0x40, 0x51, 0x43, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x1f, 0x52, 0x47, 0x40, 0x4f, 0x44, 0x43, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x56, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x53, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x42, 0x40, 0x54, 0x46, 0x47, 0x53, 0x9f, ], [0x13, 0x4e, 0x1f, 0x47, 0x48, 0x43, 0x44, 0x1f, 0x48, 0x4d, 0x1f, 0x46, 0x44, 0x4c, 0x52, 0x1f, 0x4e, 0xcd, 0x47, 0x48, 0x4b, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x52, 0x56, 0x4e, 0x51, 0x43, 0x1b, 0x1f, 0xe, 0x4d, 0x9f, 0x52, 0x48, 0x4b, 0x55, 0x44, 0x51, 0x1f, 0x4d, 0x44, 0x42, 0x4a, 0x4b, 0x40, 0x42, 0x44, 0x52, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x52, 0x53, 0x51, 0x54, 0x4d, 0x46, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x45, 0x4b, 0x4e, 0x56, 0x44, 0x51, 0x48, 0x4d, 0x46, 0x1f, 0x52, 0x53, 0x40, 0x51, 0x52, 0x1d, 0x9f, 0x4e, 0x4d, 0x1f, 0x42, 0x51, 0x4e, 0x56, 0x4d, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x9f, 0x47, 0x54, 0x4d, 0x46, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x43, 0x51, 0x40, 0x46, 0x4e, 0x4d, 0x1c, 0x45, 0x48, 0x51, 0x44, 0x1d, 0x1f, 0x48, 0x4d, 0x9f, ], [0x53, 0x56, 0x48, 0x52, 0x53, 0x44, 0x43, 0x1f, 0x56, 0x48, 0x51, 0x44, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x9f, 0x4c, 0x44, 0x52, 0x47, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x9f, 0x4e, 0x45, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x52, 0x54, 0x4d, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x5, 0x40, 0x51, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x48, 0x52, 0x53, 0xd8, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x42, 0x4e, 0x4b, 0x43, 0x1f, 0x13, 0x4e, 0x9f, 0x43, 0x54, 0x4d, 0x46, 0x44, 0x4e, 0x4d, 0x52, 0x1f, 0x43, 0x44, 0x44, 0x4f, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x51, 0x4d, 0x52, 0x1f, 0x4e, 0x4b, 0x43, 0x1f, 0x16, 0x44, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x1d, 0x1f, 0x44, 0x51, 0x44, 0x9f, 0x41, 0x51, 0x44, 0x40, 0x4a, 0x1f, 0x4e, 0x45, 0x1f, 0x43, 0x40, 0x58, 0x1d, 0x1f, 0x13, 0x4e, 0x9f, 0x42, 0x4b, 0x40, 0x48, 0x4c, 0x1f, 0x4e, 0x54, 0x51, 0x9f, 0x4b, 0x4e, 0x4d, 0x46, 0x1c, 0x45, 0x4e, 0x51, 0x46, 0x4e, 0x53, 0x53, 0x44, 0x4d, 0x9f, ], [0x46, 0x4e, 0x4b, 0x43, 0x1b, 0x1f, 0x6, 0x4e, 0x41, 0x4b, 0x44, 0x53, 0x52, 0x1f, 0x53, 0x47, 0x44, 0xd8, 0x42, 0x40, 0x51, 0x55, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x52, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x47, 0x40, 0x51, 0x4f, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x1d, 0x9f, 0x56, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4d, 0x4e, 0x1f, 0x4c, 0x40, 0x4d, 0x9f, 0x43, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x13, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4b, 0x40, 0x58, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x4b, 0x4e, 0x4d, 0x46, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4c, 0x40, 0x4d, 0x58, 0x1f, 0x40, 0x1f, 0x52, 0x4e, 0x4d, 0x46, 0x1f, 0x16, 0x40, 0x52, 0x9f, ], [0x52, 0x54, 0x4d, 0x46, 0x1f, 0x54, 0x4d, 0x47, 0x44, 0x40, 0x51, 0x43, 0x1f, 0x41, 0x58, 0x9f, 0x4c, 0x44, 0x4d, 0x1f, 0x4e, 0x51, 0x1f, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x4f, 0x48, 0x4d, 0x44, 0x52, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x51, 0x4e, 0x40, 0x51, 0x48, 0x4d, 0xc6, 0x4e, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x47, 0x44, 0x48, 0x46, 0x47, 0x53, 0x1d, 0x1f, 0x13, 0x47, 0xc4, 0x56, 0x48, 0x4d, 0x43, 0x52, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x4c, 0x4e, 0x40, 0x4d, 0x48, 0x4d, 0xc6, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4d, 0x48, 0x46, 0x47, 0x53, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x45, 0x48, 0x51, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x51, 0x44, 0x43, 0x1d, 0x1f, 0x48, 0x53, 0x9f, 0x45, 0x4b, 0x40, 0x4c, 0x48, 0x4d, 0x46, 0x1f, 0x52, 0x4f, 0x51, 0x44, 0x40, 0x43, 0x1b, 0x9f, ], [0x13, 0x47, 0x44, 0x1f, 0x53, 0x51, 0x44, 0x44, 0x52, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x9f, 0x53, 0x4e, 0x51, 0x42, 0x47, 0x44, 0x52, 0x1f, 0x41, 0x4b, 0x40, 0x59, 0x44, 0x43, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x44, 0x1f, 0x41, 0x44, 0x4b, 0x4b, 0x52, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x9f, 0x51, 0x48, 0x4d, 0x46, 0x48, 0x4d, 0x46, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x43, 0x40, 0x4b, 0x44, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x4c, 0x44, 0x4d, 0x9f, 0x4b, 0x4e, 0x4e, 0x4a, 0x44, 0x43, 0x1f, 0x54, 0x4f, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x45, 0x40, 0x42, 0x44, 0x52, 0x1f, 0x4f, 0x40, 0x4b, 0x44, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x4d, 0x9f, 0x43, 0x51, 0x40, 0x46, 0x4e, 0x4d, 0x5d, 0x52, 0x1f, 0x48, 0x51, 0x44, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x45, 0x48, 0x44, 0x51, 0x42, 0x44, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x1f, 0x45, 0x48, 0x51, 0x44, 0x9f, 0xb, 0x40, 0x48, 0x43, 0x1f, 0x4b, 0x4e, 0x56, 0x1f, 0x53, 0x47, 0x44, 0x48, 0x51, 0x9f, ], [0x53, 0x4e, 0x56, 0x44, 0x51, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x4e, 0x54, 0x52, 0x44, 0x52, 0x9f, 0x45, 0x51, 0x40, 0x48, 0x4b, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x1f, 0x52, 0x4c, 0x4e, 0x4a, 0x44, 0x43, 0x9f, 0x41, 0x44, 0x4d, 0x44, 0x40, 0x53, 0x47, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1b, 0x9f, 0x13, 0x47, 0x44, 0x1f, 0x43, 0x56, 0x40, 0x51, 0x55, 0x44, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x9f, 0x47, 0x44, 0x40, 0x51, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x53, 0x51, 0x40, 0x4c, 0x4f, 0x1f, 0x4e, 0xc5, 0x43, 0x4e, 0x4e, 0x4c, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x1f, 0x45, 0x4b, 0x44, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x48, 0x51, 0x1f, 0x47, 0x40, 0x4b, 0x4b, 0x1d, 0x1f, 0x53, 0x4e, 0x9f, ], [0x43, 0x58, 0x48, 0x4d, 0x46, 0x1f, 0x45, 0x40, 0x4b, 0x4b, 0x1f, 0x1, 0x44, 0x4d, 0x44, 0x40, 0x53, 0xc7, 0x47, 0x48, 0x52, 0x1f, 0x45, 0x44, 0x44, 0x53, 0x1d, 0x1f, 0x41, 0x44, 0x4d, 0x44, 0x40, 0x53, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1b, 0x1f, 0x5, 0x40, 0x51, 0x1f, 0x4e, 0x55, 0x44, 0xd1, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x48, 0x52, 0x53, 0x58, 0x9f, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x46, 0x51, 0x48, 0x4c, 0x1f, 0x13, 0x4e, 0x9f, 0x43, 0x54, 0x4d, 0x46, 0x44, 0x4e, 0x4d, 0x52, 0x1f, 0x43, 0x44, 0x44, 0x4f, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x51, 0x4d, 0x52, 0x1f, 0x43, 0x48, 0x4c, 0x1f, 0x16, 0x44, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x1d, 0x1f, 0x44, 0x51, 0x44, 0x9f, ], [0x41, 0x51, 0x44, 0x40, 0x4a, 0x1f, 0x4e, 0x45, 0x1f, 0x43, 0x40, 0x58, 0x1d, 0x1f, 0x13, 0x4e, 0x9f, 0x56, 0x48, 0x4d, 0x1f, 0x4e, 0x54, 0x51, 0x1f, 0x47, 0x40, 0x51, 0x4f, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x46, 0x4e, 0x4b, 0x43, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x47, 0x48, 0x4c, 0x1a, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x11, 0x4e, 0x40, 0x43, 0x52, 0x1f, 0x46, 0x4e, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x44, 0x55, 0x44, 0xd1, 0x4e, 0x4d, 0x1d, 0x1f, 0xe, 0x55, 0x44, 0x51, 0x1f, 0x51, 0x4e, 0x42, 0x4a, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x53, 0x51, 0x44, 0x44, 0x1d, 0x1f, 0x1, 0x58, 0x9f, 0x42, 0x40, 0x55, 0x44, 0x52, 0x1f, 0x56, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x52, 0x54, 0x4d, 0x1f, 0x47, 0x40, 0x52, 0x1f, 0x52, 0x47, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x1, 0x58, 0x9f, 0x52, 0x53, 0x51, 0x44, 0x40, 0x4c, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0xd1, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x44, 0x40, 0x1b, 0x1f, 0xe, 0x55, 0x44, 0xd1, 0x52, 0x4d, 0x4e, 0x56, 0x1f, 0x41, 0x58, 0x1f, 0x56, 0x48, 0x4d, 0x53, 0x44, 0x51, 0x9f, ], [0x52, 0x4e, 0x56, 0x4d, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x44, 0x51, 0x51, 0x58, 0x1f, 0x45, 0x4b, 0x4e, 0x56, 0x44, 0x51, 0x52, 0x9f, 0x4e, 0x45, 0x1f, 0x9, 0x54, 0x4d, 0x44, 0x1d, 0x1f, 0xe, 0x55, 0x44, 0x51, 0x9f, 0x46, 0x51, 0x40, 0x52, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4e, 0x55, 0x44, 0x51, 0x9f, 0x52, 0x53, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x9f, 0x4c, 0x4e, 0x54, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x4e, 0x4d, 0x1b, 0x1f, 0x11, 0x4e, 0x40, 0x43, 0x52, 0x1f, 0x46, 0x4e, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x4e, 0x4d, 0x1f, 0x14, 0x4d, 0x43, 0x44, 0xd1, ], [0x42, 0x4b, 0x4e, 0x54, 0x43, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x9f, 0x52, 0x53, 0x40, 0x51, 0x1d, 0x1f, 0x18, 0x44, 0x53, 0x1f, 0x45, 0x44, 0x44, 0x53, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x40, 0x4d, 0x43, 0x44, 0x51, 0x48, 0x4d, 0x46, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x46, 0x4e, 0x4d, 0x44, 0x1f, 0x13, 0x54, 0x51, 0x4d, 0x1f, 0x40, 0x53, 0x9f, 0x4b, 0x40, 0x52, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x47, 0x4e, 0x4c, 0x44, 0x1f, 0x40, 0x45, 0x40, 0x51, 0x9b, 0x4, 0x58, 0x44, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x45, 0x48, 0x51, 0x44, 0x1f, 0x40, 0x4d, 0xc3, 0x52, 0x56, 0x4e, 0x51, 0x43, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x52, 0x44, 0x44, 0x4d, 0x9f, 0x0, 0x4d, 0x43, 0x1f, 0x47, 0x4e, 0x51, 0x51, 0x4e, 0x51, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, ], [0x47, 0x40, 0x4b, 0x4b, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x52, 0x53, 0x4e, 0x4d, 0x44, 0x9f, 0xb, 0x4e, 0x4e, 0x4a, 0x1f, 0x40, 0x53, 0x1f, 0x4b, 0x40, 0x52, 0x53, 0x1f, 0x4e, 0x4d, 0x9f, 0x4c, 0x44, 0x40, 0x43, 0x4e, 0x56, 0x52, 0x1f, 0x46, 0x51, 0x44, 0x44, 0x4d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x53, 0x51, 0x44, 0x44, 0x52, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x48, 0x4b, 0x4b, 0x52, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x4b, 0x4e, 0x4d, 0x46, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x4a, 0x4d, 0x4e, 0x56, 0x4d, 0x1b, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x51, 0x44, 0x44, 0x1f, 0x11, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4, 0x4b, 0x55, 0x44, 0x4d, 0x1c, 0x4a, 0x48, 0x4d, 0x46, 0x52, 0x9f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x4a, 0x58, 0x1d, 0x9f, 0x12, 0x44, 0x55, 0x44, 0x4d, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x3, 0x56, 0x40, 0x51, 0x45, 0x1c, 0x4b, 0x4e, 0x51, 0x43, 0x52, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x48, 0x51, 0x1f, 0x47, 0x40, 0x4b, 0x4b, 0x52, 0x1f, 0x4e, 0x45, 0x9f, 0x52, 0x53, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0xd, 0x48, 0x4d, 0x44, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0xc, 0x4e, 0x51, 0x53, 0x40, 0x4b, 0x1f, 0xc, 0x44, 0x4d, 0x1f, 0x43, 0x4e, 0x4e, 0x4c, 0x44, 0x43, 0x9f, ], [0x53, 0x4e, 0x1f, 0x43, 0x48, 0x44, 0x1d, 0x1f, 0xe, 0x4d, 0x44, 0x1f, 0x45, 0x4e, 0x51, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x3, 0x40, 0x51, 0x4a, 0x1f, 0xb, 0x4e, 0x51, 0x43, 0x1f, 0x4e, 0x4d, 0x9f, 0x47, 0x48, 0x52, 0x1f, 0x43, 0x40, 0x51, 0x4a, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x4d, 0x44, 0x1f, 0x8, 0xcd, 0x53, 0x47, 0x44, 0x1f, 0xb, 0x40, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0xc, 0x4e, 0x51, 0x43, 0x4e, 0xd1, 0x56, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x12, 0x47, 0x40, 0x43, 0x4e, 0x56, 0x52, 0x9f, 0x4b, 0x48, 0x44, 0x1b, 0x1f, 0xe, 0x4d, 0x44, 0x1f, 0x11, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x9f, 0x51, 0x54, 0x4b, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x40, 0x4b, 0x4b, 0x1d, 0x1f, 0xe, 0x4d, 0xc4, 0x11, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x9d, ], [0xe, 0x4d, 0x44, 0x1f, 0x11, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x51, 0x48, 0x4d, 0x46, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x40, 0x4b, 0x4b, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x40, 0x51, 0x4a, 0x4d, 0x44, 0x52, 0x52, 0x1f, 0x41, 0x48, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x1d, 0x1f, 0x8, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0xb, 0x40, 0x4d, 0x43, 0x9f, 0x4e, 0x45, 0x1f, 0xc, 0x4e, 0x51, 0x43, 0x4e, 0x51, 0x1f, 0x56, 0x47, 0x44, 0x51, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x12, 0x47, 0x40, 0x43, 0x4e, 0x56, 0x52, 0x1f, 0x4b, 0x48, 0x44, 0x1b, 0x9f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, ]], +[[0x0, 0x4b, 0x4b, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x46, 0x4e, 0x4b, 0x43, 0x9f, 0x43, 0x4e, 0x44, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x46, 0x4b, 0x48, 0x53, 0x53, 0x44, 0x51, 0x1d, 0x9f, 0xd, 0x4e, 0x53, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x53, 0x47, 0x4e, 0x52, 0x44, 0x1f, 0x56, 0x47, 0x4e, 0x9f, 0x56, 0x40, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x4b, 0x4e, 0x52, 0x53, 0x1b, 0x9f, 0x13, 0x47, 0x44, 0x1f, 0x4e, 0x4b, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x52, 0x9f, 0x52, 0x53, 0x51, 0x4e, 0x4d, 0x46, 0x1f, 0x43, 0x4e, 0x44, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x44, 0x51, 0x1d, 0x1f, 0x3, 0x44, 0x44, 0x4f, 0x1f, 0x51, 0x4e, 0x4e, 0x53, 0xd2, 0x40, 0x51, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x51, 0x44, 0x40, 0x42, 0x47, 0x44, 0x43, 0x1f, 0x41, 0xd8, ], [0x53, 0x47, 0x44, 0x1f, 0x45, 0x51, 0x4e, 0x52, 0x53, 0x1b, 0x1f, 0x5, 0x51, 0x4e, 0x4c, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x40, 0x52, 0x47, 0x44, 0x52, 0x1f, 0x40, 0x1f, 0x45, 0x48, 0x51, 0x44, 0x9f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x1f, 0x56, 0x4e, 0x4a, 0x44, 0x4d, 0x1d, 0x1f, 0x0, 0x9f, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x52, 0x47, 0x40, 0x43, 0x4e, 0x56, 0x52, 0x1f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x9f, 0x52, 0x4f, 0x51, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x11, 0x44, 0x4d, 0x44, 0x56, 0x44, 0x43, 0x9f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x1f, 0x41, 0x4b, 0x40, 0x43, 0x44, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x41, 0x51, 0x4e, 0x4a, 0x44, 0x4d, 0x1d, 0x9f, ], [0x13, 0x47, 0x44, 0x1f, 0x42, 0x51, 0x4e, 0x56, 0x4d, 0x4b, 0x44, 0x52, 0x52, 0x9f, 0x40, 0x46, 0x40, 0x48, 0x4d, 0x1f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x9f, 0x4a, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x1c, 0x13, 0x4e, 0x4b, 0x4a, 0x48, 0x44, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x16, 0x47, 0x4e, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x52, 0x1d, 0x1f, 0x2, 0x40, 0x4d, 0x1b, 0x9f, 0x16, 0x47, 0x4e, 0x1f, 0x53, 0x51, 0x48, 0x44, 0x52, 0x1d, 0x1f, 0x3, 0x4e, 0x44, 0x52, 0x1b, 0x9f, 0x16, 0x47, 0x4e, 0x1f, 0x4b, 0x4e, 0x55, 0x44, 0x52, 0x1d, 0x1f, 0xb, 0x48, 0x55, 0x44, 0x52, 0x1b, 0x9f, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x6, 0x4e, 0x4d, 0x44, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x1d, 0x1f, 0x46, 0x4e, 0x4d, 0x44, 0x9f, 0x40, 0x47, 0x44, 0x40, 0x43, 0x1d, 0x1f, 0x4, 0x42, 0x47, 0x4e, 0x44, 0x52, 0x1f, 0x51, 0x4e, 0x4b, 0xcb, 0x54, 0x4d, 0x40, 0x4d, 0x52, 0x56, 0x44, 0x51, 0x44, 0x43, 0x1b, 0x1f, 0x4, 0x4c, 0x4f, 0x53, 0x58, 0x9d, 0x4e, 0x4f, 0x44, 0x4d, 0x1d, 0x1f, 0x43, 0x54, 0x52, 0x53, 0x58, 0x1d, 0x1f, 0x43, 0x44, 0x40, 0x43, 0x9d, 0x16, 0x47, 0x58, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x16, 0x44, 0x58, 0x51, 0x45, 0x4e, 0x4b, 0x4a, 0x1f, 0x45, 0x4b, 0x44, 0x43, 0x1e, 0x9f, 0x16, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x43, 0x51, 0x40, 0x46, 0x4e, 0x4d, 0xd2, 0x46, 0x4e, 0x4d, 0x44, 0x1f, 0x53, 0x4e, 0x46, 0x44, 0x53, 0x47, 0x44, 0x51, 0x1e, 0x9f, ], [0xb, 0x44, 0x40, 0x55, 0x48, 0x4d, 0x46, 0x1f, 0x16, 0x44, 0x58, 0x51, 0x52, 0x1f, 0x53, 0x4e, 0x9f, 0x56, 0x48, 0x4d, 0x43, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x56, 0x44, 0x40, 0x53, 0x47, 0x44, 0x51, 0x1e, 0x9f, 0x12, 0x44, 0x53, 0x53, 0x48, 0x4d, 0x46, 0x1f, 0x47, 0x44, 0x51, 0x43, 0x41, 0x44, 0x40, 0x52, 0x53, 0xd2, 0x45, 0x51, 0x44, 0x44, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x44, 0x53, 0x47, 0x44, 0x51, 0x1e, 0x9f, 0x6, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x4e, 0x54, 0x51, 0x9f, 0x52, 0x40, 0x45, 0x44, 0x46, 0x54, 0x40, 0x51, 0x43, 0x52, 0x1d, 0x1f, 0x46, 0x4e, 0x4d, 0x44, 0x1d, 0x9f, 0x41, 0x54, 0x53, 0x1f, 0x56, 0x47, 0x48, 0x53, 0x47, 0x44, 0x51, 0x1e, 0x1f, 0x7, 0x40, 0x55, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x45, 0x4b, 0x4e, 0x56, 0x4d, 0x1f, 0x53, 0x4e, 0x1f, 0x52, 0x4e, 0x4c, 0xc4, ], [0x4d, 0x44, 0x56, 0x1f, 0x16, 0x44, 0x58, 0x51, 0x1f, 0x16, 0x47, 0x44, 0x4d, 0x9f, 0x42, 0x51, 0x54, 0x44, 0x4b, 0x1f, 0x13, 0x47, 0x51, 0x44, 0x40, 0x43, 0x52, 0x1f, 0x52, 0x4e, 0x4c, 0xc4, 0x4e, 0x53, 0x47, 0x44, 0x51, 0x52, 0x1f, 0x45, 0x44, 0x40, 0x51, 0x1e, 0x1f, 0x0, 0x51, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x52, 0x1f, 0x40, 0x56, 0x40, 0x58, 0x9f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x47, 0x44, 0x51, 0x44, 0x1e, 0x1f, 0x16, 0x47, 0x58, 0x1d, 0x9f, 0x4e, 0x47, 0x1d, 0x1f, 0x56, 0x47, 0x58, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4c, 0x4f, 0x53, 0xd8, 0x16, 0x44, 0x58, 0x51, 0x1e, 0x1f, 0x1c, 0xc, 0x42, 0x2, 0x40, 0x45, 0x45, 0x51, 0x44, 0x58, 0x1f, 0x9f, 0x9f, ]], +[[0xc, 0x51, 0x1b, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0xc, 0x51, 0x52, 0x1b, 0x9f, 0x3, 0x54, 0x51, 0x52, 0x4b, 0x44, 0x58, 0x1d, 0x1f, 0x4e, 0x45, 0x1f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0xd1, 0x45, 0x4e, 0x54, 0x51, 0x1d, 0x1f, 0xf, 0x51, 0x48, 0x55, 0x44, 0x53, 0x9f, 0x3, 0x51, 0x48, 0x55, 0x44, 0x1d, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x4f, 0x51, 0x4e, 0x54, 0x43, 0x9f, 0x53, 0x4e, 0x1f, 0x52, 0x40, 0x58, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x9f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x4f, 0x44, 0x51, 0x45, 0x44, 0x42, 0x53, 0x4b, 0x58, 0x9f, 0x4d, 0x4e, 0x51, 0x4c, 0x40, 0x4b, 0x1d, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x4a, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x4c, 0x54, 0x42, 0x47, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x9f, ], [0x56, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x40, 0x52, 0x53, 0x9f, 0x4f, 0x44, 0x4e, 0x4f, 0x4b, 0x44, 0x1f, 0x58, 0x4e, 0x54, 0x5d, 0x43, 0x9f, 0x44, 0x57, 0x4f, 0x44, 0x42, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x9f, 0x48, 0x4d, 0x55, 0x4e, 0x4b, 0x55, 0x44, 0x43, 0x1f, 0x48, 0x4d, 0x9f, 0x40, 0x4d, 0x58, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x9f, 0x4e, 0x51, 0x1f, 0x4c, 0x58, 0x52, 0x53, 0x44, 0x51, 0x48, 0x4e, 0x54, 0x52, 0x1d, 0x9f, 0x41, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x49, 0x54, 0x52, 0x53, 0x9f, 0x43, 0x48, 0x43, 0x4d, 0x5d, 0x53, 0x1f, 0x47, 0x4e, 0x4b, 0x43, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, ], [0x52, 0x54, 0x42, 0x47, 0x1f, 0x4d, 0x4e, 0x4d, 0x52, 0x44, 0x4d, 0x52, 0x44, 0x1b, 0x9f, 0x1c, 0x11, 0x4e, 0x56, 0x4b, 0x48, 0x4d, 0x46, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0xb, 0x4e, 0x55, 0x44, 0x1d, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x9f, 0x50, 0x54, 0x48, 0x42, 0x4a, 0x4b, 0x58, 0x1f, 0x40, 0x51, 0x51, 0x44, 0x52, 0x53, 0x52, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x46, 0x44, 0x4d, 0x53, 0x4b, 0x44, 0x1f, 0x47, 0x44, 0x40, 0x51, 0x53, 0x1d, 0x9f, 0x12, 0x44, 0x48, 0x59, 0x44, 0x43, 0x1f, 0x47, 0x48, 0x4c, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4c, 0xd8, 0x41, 0x44, 0x40, 0x54, 0x53, 0x48, 0x45, 0x54, 0x4b, 0x1f, 0x45, 0x4e, 0x51, 0x4c, 0x9f, 0x13, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x53, 0x40, 0x4a, 0x44, 0x4d, 0x9f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x4c, 0x44, 0x1d, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x9f, 0x4c, 0x40, 0x4d, 0x4d, 0x44, 0x51, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x52, 0x53, 0x48, 0x4b, 0xcb, ], [0x46, 0x51, 0x48, 0x44, 0x55, 0x44, 0x52, 0x1f, 0x4c, 0x44, 0x1b, 0x1f, 0xb, 0x4e, 0x55, 0x44, 0x1d, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x4f, 0x40, 0x51, 0x43, 0x4e, 0x4d, 0x52, 0x1f, 0x4d, 0x4e, 0x9f, 0x41, 0x44, 0x4b, 0x4e, 0x55, 0x44, 0x43, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x9f, 0x4b, 0x4e, 0x55, 0x48, 0x4d, 0x46, 0x1d, 0x1f, 0x53, 0x4e, 0x4e, 0x4a, 0x1f, 0x4c, 0x44, 0x1f, 0x52, 0xce, 0x52, 0x53, 0x51, 0x4e, 0x4d, 0x46, 0x4b, 0x58, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x43, 0x44, 0x4b, 0x48, 0x46, 0x47, 0x53, 0x1f, 0x48, 0x4d, 0x1f, 0x47, 0x48, 0x4c, 0x9f, 0x13, 0x47, 0x40, 0x53, 0x1d, 0x1f, 0x40, 0x52, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x52, 0x44, 0x44, 0x1d, 0x9f, 0x48, 0x53, 0x1f, 0x52, 0x53, 0x48, 0x4b, 0x4b, 0x1f, 0x40, 0x41, 0x40, 0x4d, 0x43, 0x4e, 0x4d, 0x52, 0x9f, ], [0x4c, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1b, 0x1b, 0x1b, 0x9f, 0x1c, 0x0, 0x4b, 0x48, 0x46, 0x47, 0x48, 0x44, 0x51, 0x48, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x1f, 0x42, 0x40, 0x4d, 0x4d, 0x4e, 0x53, 0x1f, 0x44, 0x57, 0x4f, 0x51, 0x44, 0x52, 0x52, 0x9f, 0x48, 0x53, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x52, 0x54, 0x51, 0x44, 0x4b, 0x58, 0x1f, 0x58, 0x4e, 0xd4, 0x40, 0x4d, 0x43, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x41, 0x4e, 0x43, 0x58, 0x1f, 0x47, 0x40, 0x55, 0xc4, 0x40, 0x1f, 0x4d, 0x4e, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x4e, 0x51, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0xc3, 0x41, 0x44, 0x1f, 0x40, 0x4d, 0x1f, 0x44, 0x57, 0x48, 0x52, 0x53, 0x44, 0x4d, 0x42, 0x44, 0x1f, 0x4e, 0xc5, 0x58, 0x4e, 0x54, 0x51, 0x52, 0x1f, 0x41, 0x44, 0x58, 0x4e, 0x4d, 0x43, 0x1f, 0x58, 0x4e, 0x54, 0x1b, 0x9f, 0x16, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x54, 0x52, 0x44, 0x9f, ], [0x4e, 0x45, 0x1f, 0x4c, 0x58, 0x1f, 0x42, 0x51, 0x44, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1d, 0x1f, 0x48, 0xc5, 0x8, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x44, 0x4d, 0x53, 0x48, 0x51, 0x44, 0x4b, 0x58, 0x9f, 0x42, 0x4e, 0x4d, 0x53, 0x40, 0x48, 0x4d, 0x44, 0x43, 0x1f, 0x47, 0x44, 0x51, 0x44, 0x1e, 0x1f, 0xc, 0xd8, 0x46, 0x51, 0x44, 0x40, 0x53, 0x1f, 0x4c, 0x48, 0x52, 0x44, 0x51, 0x48, 0x44, 0x52, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x41, 0x44, 0x44, 0x4d, 0x1f, 0x7, 0x44, 0x40, 0x53, 0x47, 0x42, 0x4b, 0x48, 0x45, 0x45, 0x5d, 0x52, 0x9f, 0x4c, 0x48, 0x52, 0x44, 0x51, 0x48, 0x44, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x8, 0x9f, 0x56, 0x40, 0x53, 0x42, 0x47, 0x44, 0x43, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x45, 0x44, 0x4b, 0x53, 0x9f, ], [0x44, 0x40, 0x42, 0x47, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x41, 0x44, 0x46, 0x48, 0x4d, 0x4d, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x4c, 0x58, 0x9f, 0x46, 0x51, 0x44, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x4e, 0x54, 0x46, 0x47, 0x53, 0x1f, 0x48, 0x4d, 0x9f, 0x4b, 0x48, 0x55, 0x48, 0x4d, 0x46, 0x1f, 0x48, 0x52, 0x1f, 0x47, 0x48, 0x4c, 0x52, 0x44, 0x4b, 0x45, 0x9b, 0x8, 0x45, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x44, 0x4b, 0x52, 0x44, 0x9f, 0x4f, 0x44, 0x51, 0x48, 0x52, 0x47, 0x44, 0x43, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x44, 0x9f, 0x51, 0x44, 0x4c, 0x40, 0x48, 0x4d, 0x44, 0x43, 0x1d, 0x1f, 0x8, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0xc3, 0x52, 0x53, 0x48, 0x4b, 0x4b, 0x1f, 0x42, 0x4e, 0x4d, 0x53, 0x48, 0x4d, 0x54, 0x44, 0x1f, 0x53, 0x4e, 0x9f, ], [0x41, 0x44, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x48, 0x45, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x44, 0x4b, 0x52, 0x44, 0x1f, 0x51, 0x44, 0x4c, 0x40, 0x48, 0x4d, 0x44, 0x43, 0x1d, 0x1f, 0x40, 0x4d, 0xc3, 0x47, 0x44, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x9f, 0x40, 0x4d, 0x4d, 0x48, 0x47, 0x48, 0x4b, 0x40, 0x53, 0x44, 0x43, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x54, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x53, 0x54, 0x51, 0x4d, 0x1f, 0x53, 0x4e, 0x1f, 0x40, 0x1f, 0x4c, 0x48, 0x46, 0x47, 0x53, 0x58, 0x9f, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x51, 0x1b, 0x1f, 0x8, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0xc3, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x44, 0x44, 0x4c, 0x1f, 0x40, 0x1f, 0x4f, 0x40, 0x51, 0x53, 0x1f, 0x4e, 0xc5, ], [0x48, 0x53, 0x1b, 0x1f, 0x1c, 0x1, 0x51, 0x4e, 0x4d, 0x53, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x43, 0x51, 0x44, 0x40, 0x4c, 0x53, 0x1f, 0x48, 0x4d, 0x9f, 0x4c, 0x58, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1d, 0x1f, 0x43, 0x51, 0x44, 0x40, 0x4c, 0x52, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x52, 0x53, 0x40, 0x58, 0x44, 0x43, 0x9f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4c, 0x44, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x40, 0x45, 0x53, 0x44, 0x51, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x42, 0x47, 0x40, 0x4d, 0x46, 0x44, 0xc3, 0x4c, 0x58, 0x1f, 0x48, 0x43, 0x44, 0x40, 0x52, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x58, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x46, 0x4e, 0x4d, 0x44, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x1f, 0x4c, 0x44, 0x1d, 0x9f, ], [0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x56, 0x48, 0x4d, 0x44, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x56, 0x40, 0x53, 0x44, 0x51, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x4b, 0x53, 0x44, 0x51, 0x44, 0xc3, 0x53, 0x47, 0x44, 0x1f, 0x42, 0x4e, 0x4b, 0x4e, 0x51, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x58, 0x9f, 0x4c, 0x48, 0x4d, 0x43, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x48, 0x52, 0x9f, 0x4e, 0x4d, 0x44, 0x1b, 0x1f, 0x8, 0x5d, 0x4c, 0x1f, 0x46, 0x4e, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x9f, 0x53, 0x44, 0x4b, 0x4b, 0x1f, 0x48, 0x53, 0x1f, 0x1c, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x53, 0x40, 0x4a, 0xc4, 0x42, 0x40, 0x51, 0x44, 0x1f, 0x4d, 0x4e, 0x53, 0x1f, 0x53, 0x4e, 0x1f, 0x52, 0x4c, 0x48, 0x4b, 0x44, 0x9f, 0x40, 0x53, 0x1f, 0x40, 0x4d, 0x58, 0x1f, 0x4f, 0x40, 0x51, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x48, 0x53, 0x9b, ], [0x1c, 0x1, 0x51, 0x4e, 0x4d, 0x53, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x13, 0x47, 0x48, 0x52, 0x1f, 0x52, 0x53, 0x4e, 0x51, 0x58, 0x1f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x53, 0x44, 0x40, 0x42, 0xc7, 0x47, 0x48, 0x52, 0x1f, 0x52, 0x4e, 0x4d, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x2, 0x51, 0x48, 0x52, 0x4f, 0x48, 0x4d, 0x1f, 0x2, 0x51, 0x48, 0x52, 0x4f, 0x48, 0x40, 0x4d, 0x9f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x4d, 0x44, 0x5d, 0x44, 0x51, 0x1f, 0x46, 0x4e, 0x1f, 0x41, 0x58, 0x9d, 0x5, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x43, 0x40, 0x58, 0x1f, 0x53, 0x4e, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x44, 0x4d, 0x43, 0x48, 0x4d, 0x46, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1d, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x56, 0x44, 0x1f, 0x48, 0x4d, 0x9f, ], [0x48, 0x53, 0x1f, 0x52, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x9f, 0x51, 0x44, 0x4c, 0x44, 0x4c, 0x41, 0x44, 0x51, 0x44, 0x43, 0x1b, 0x1f, 0x16, 0x44, 0x9f, 0x45, 0x44, 0x56, 0x1d, 0x1f, 0x56, 0x44, 0x1f, 0x47, 0x40, 0x4f, 0x4f, 0x58, 0x1f, 0x45, 0x44, 0x56, 0x9d, 0x56, 0x44, 0x1f, 0x41, 0x40, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x9f, 0x41, 0x51, 0x4e, 0x53, 0x47, 0x44, 0x51, 0x52, 0x1b, 0x1f, 0x5, 0x4e, 0x51, 0x1f, 0x47, 0x44, 0x9f, 0x53, 0x4e, 0x1c, 0x43, 0x40, 0x58, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x52, 0x47, 0x44, 0x43, 0x52, 0x9f, 0x47, 0x48, 0x52, 0x1f, 0x41, 0x4b, 0x4e, 0x4e, 0x43, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x4c, 0x44, 0x9f, 0x12, 0x47, 0x40, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x1f, 0x4c, 0x58, 0x9f, ], [0x41, 0x51, 0x4e, 0x53, 0x47, 0x44, 0x51, 0x1b, 0x9f, 0x1c, 0x12, 0x47, 0x40, 0x4a, 0x44, 0x52, 0x4f, 0x44, 0x40, 0x51, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x7, 0x4e, 0x56, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x4c, 0x44, 0x40, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x9f, 0x4b, 0x48, 0x45, 0x44, 0x1f, 0x48, 0x52, 0x1d, 0x1f, 0x4c, 0x44, 0x44, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x1f, 0x48, 0x53, 0x1b, 0x1f, 0x3, 0x4e, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x47, 0x54, 0x4d, 0x1f, 0x48, 0x53, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x42, 0x40, 0x4b, 0x4b, 0x1f, 0x48, 0x53, 0x1f, 0x47, 0x40, 0x51, 0x43, 0x9f, 0x4d, 0x40, 0x4c, 0x44, 0x52, 0x1b, 0x1f, 0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x4d, 0x4e, 0x53, 0x9f, 0x52, 0x4e, 0x1f, 0x41, 0x40, 0x43, 0x1f, 0x40, 0x52, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x40, 0x51, 0x44, 0x9b, 0x8, 0x53, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x52, 0x1f, 0x4f, 0x4e, 0x4e, 0x51, 0x44, 0x52, 0x53, 0x9f, ], [0x56, 0x47, 0x44, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x40, 0x51, 0x44, 0x9f, 0x51, 0x48, 0x42, 0x47, 0x44, 0x52, 0x53, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x45, 0x40, 0x54, 0x4b, 0x53, 0x1c, 0x45, 0x48, 0x4d, 0x43, 0x44, 0x51, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x9f, 0x45, 0x48, 0x4d, 0x43, 0x1f, 0x45, 0x40, 0x54, 0x4b, 0x53, 0x52, 0x1f, 0x44, 0x55, 0x44, 0x4d, 0x9f, 0x48, 0x4d, 0x1f, 0x4f, 0x40, 0x51, 0x40, 0x43, 0x48, 0x52, 0x44, 0x1b, 0x1f, 0xb, 0x4e, 0x55, 0x44, 0x9f, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1d, 0x1f, 0x4f, 0x4e, 0x4e, 0x51, 0x1f, 0x40, 0xd2, 0x48, 0x53, 0x1f, 0x48, 0x52, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x1f, 0x4c, 0x40, 0x58, 0x9f, 0x4f, 0x44, 0x51, 0x47, 0x40, 0x4f, 0x52, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x52, 0x4e, 0x4c, 0x44, 0x9f, ], [0x4f, 0x4b, 0x44, 0x40, 0x52, 0x40, 0x4d, 0x53, 0x1d, 0x9f, 0x53, 0x47, 0x51, 0x48, 0x4b, 0x4b, 0x48, 0x4d, 0x46, 0x1d, 0x9f, 0x46, 0x4b, 0x4e, 0x51, 0x48, 0x4e, 0x54, 0x52, 0x1f, 0x47, 0x4e, 0x54, 0x51, 0x52, 0x1d, 0x9f, 0x44, 0x55, 0x44, 0x4d, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x9f, 0x4f, 0x4e, 0x4e, 0x51, 0x47, 0x4e, 0x54, 0x52, 0x44, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x52, 0x44, 0x53, 0x53, 0x48, 0x4d, 0x46, 0x1f, 0x52, 0x54, 0x4d, 0x1f, 0x48, 0x52, 0x9f, 0x51, 0x44, 0x45, 0x4b, 0x44, 0x42, 0x53, 0x44, 0x43, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0xc4, 0x56, 0x48, 0x4d, 0x43, 0x4e, 0x56, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, ], [0x40, 0x4b, 0x4c, 0x52, 0x47, 0x4e, 0x54, 0x52, 0x44, 0x1f, 0x40, 0x52, 0x9f, 0x41, 0x51, 0x48, 0x46, 0x47, 0x53, 0x4b, 0x58, 0x1f, 0x40, 0x52, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x51, 0x48, 0x42, 0x47, 0x1f, 0x4c, 0x40, 0x4d, 0x5d, 0x52, 0x9f, 0x40, 0x41, 0x4e, 0x43, 0x44, 0x1b, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x4d, 0x4e, 0x56, 0x9f, 0x4c, 0x44, 0x4b, 0x53, 0x52, 0x1f, 0x41, 0x44, 0x45, 0x4e, 0x51, 0x44, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x43, 0x4e, 0x4e, 0x51, 0x1f, 0x40, 0x52, 0x1f, 0x44, 0x40, 0x51, 0x4b, 0x58, 0x1f, 0x48, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x4f, 0x51, 0x48, 0x4d, 0x46, 0x1b, 0x1f, 0x8, 0x1f, 0x43, 0x4e, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x44, 0x44, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x40, 0x9f, ], [0x50, 0x54, 0x48, 0x44, 0x53, 0x1f, 0x4c, 0x48, 0x4d, 0x43, 0x1f, 0x4c, 0x40, 0x58, 0x9f, 0x4b, 0x48, 0x55, 0x44, 0x1f, 0x40, 0x52, 0x9f, 0x42, 0x4e, 0x4d, 0x53, 0x44, 0x4d, 0x53, 0x44, 0x43, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x9d, 0x40, 0x4d, 0x43, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x40, 0x52, 0x9f, 0x42, 0x47, 0x44, 0x44, 0x51, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x4e, 0x54, 0x46, 0x47, 0x53, 0x52, 0x9d, 0x40, 0x52, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x1f, 0x4f, 0x40, 0x4b, 0x40, 0x42, 0x44, 0x1b, 0x9f, 0x1c, 0x13, 0x47, 0x4e, 0x51, 0x44, 0x40, 0x54, 0x1f, 0x9f, 0x9f, ]], +[[0x16, 0x44, 0x1f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x4b, 0x44, 0x40, 0x51, 0x4d, 0x1f, 0x53, 0x4e, 0x9f, 0x51, 0x44, 0x40, 0x56, 0x40, 0x4a, 0x44, 0x4d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4a, 0x44, 0x44, 0x4f, 0x9f, 0x4e, 0x54, 0x51, 0x52, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x40, 0x56, 0x40, 0x4a, 0x44, 0x1d, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x41, 0x58, 0x1f, 0x4c, 0x44, 0x42, 0x47, 0x40, 0x4d, 0x48, 0x42, 0x40, 0x4b, 0x9f, 0x40, 0x48, 0x43, 0x52, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x41, 0x58, 0x1f, 0x40, 0x4d, 0x9f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x9f, 0x44, 0x57, 0x4f, 0x44, 0x42, 0x53, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0xc4, 0x43, 0x40, 0x56, 0x4d, 0x1d, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x43, 0x4e, 0x44, 0x52, 0x9f, ], [0x4d, 0x4e, 0x53, 0x1f, 0x45, 0x4e, 0x51, 0x52, 0x40, 0x4a, 0x44, 0x1f, 0x54, 0x52, 0x9f, 0x44, 0x55, 0x44, 0x4d, 0x1f, 0x48, 0x4d, 0x1f, 0x4e, 0x54, 0x51, 0x9f, 0x52, 0x4e, 0x54, 0x4d, 0x43, 0x44, 0x52, 0x53, 0x1f, 0x52, 0x4b, 0x44, 0x44, 0x4f, 0x1b, 0x1f, 0x8, 0x9f, 0x4a, 0x4d, 0x4e, 0x56, 0x1f, 0x4e, 0x45, 0x1f, 0x4d, 0x4e, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x44, 0x4d, 0x42, 0x4e, 0x54, 0x51, 0x40, 0x46, 0x48, 0x4d, 0x46, 0x1f, 0x45, 0x40, 0x42, 0x53, 0x9f, 0x53, 0x47, 0x40, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x54, 0x4d, 0x50, 0x54, 0x44, 0x52, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x41, 0x4b, 0x44, 0x9f, 0x40, 0x41, 0x48, 0x4b, 0x48, 0x53, 0x58, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x53, 0x4e, 0x9f, ], [0x44, 0x4b, 0x44, 0x55, 0x40, 0x53, 0x44, 0x1f, 0x47, 0x48, 0x52, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x9f, 0x41, 0x58, 0x1f, 0x40, 0x1f, 0x42, 0x4e, 0x4d, 0x52, 0x42, 0x48, 0x4e, 0x54, 0x52, 0x9f, 0x44, 0x4d, 0x43, 0x44, 0x40, 0x55, 0x4e, 0x54, 0x51, 0x1b, 0x1f, 0x8, 0x53, 0x1f, 0x48, 0x52, 0x9f, 0x52, 0x4e, 0x4c, 0x44, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x9f, 0x40, 0x41, 0x4b, 0x44, 0x1f, 0x53, 0x4e, 0x1f, 0x4f, 0x40, 0x48, 0x4d, 0x53, 0x1f, 0x40, 0x9f, 0x4f, 0x40, 0x51, 0x53, 0x48, 0x42, 0x54, 0x4b, 0x40, 0x51, 0x9f, 0x4f, 0x48, 0x42, 0x53, 0x54, 0x51, 0x44, 0x1d, 0x1f, 0x4e, 0x51, 0x1f, 0x53, 0x4e, 0x9f, 0x42, 0x40, 0x51, 0x55, 0x44, 0x1f, 0x40, 0x1f, 0x52, 0x53, 0x40, 0x53, 0x54, 0x44, 0x1d, 0x9f, ], [0x40, 0x4d, 0x43, 0x1f, 0x52, 0x4e, 0x1f, 0x53, 0x4e, 0x1f, 0x4c, 0x40, 0x4a, 0x44, 0x1f, 0x40, 0x9f, 0x45, 0x44, 0x56, 0x1f, 0x4e, 0x41, 0x49, 0x44, 0x42, 0x53, 0x52, 0x9f, 0x41, 0x44, 0x40, 0x54, 0x53, 0x48, 0x45, 0x54, 0x4b, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x1f, 0x48, 0x53, 0x9f, 0x48, 0x52, 0x1f, 0x45, 0x40, 0x51, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x46, 0x4b, 0x4e, 0x51, 0x48, 0x4e, 0x54, 0x52, 0x1f, 0x53, 0x4e, 0x1f, 0x42, 0x40, 0x51, 0x55, 0x44, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x40, 0x48, 0x4d, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x55, 0x44, 0x51, 0xd8, 0x40, 0x53, 0x4c, 0x4e, 0x52, 0x4f, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4c, 0x44, 0x43, 0x48, 0x54, 0x4c, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, ], [0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x56, 0x44, 0x1f, 0x4b, 0x4e, 0x4e, 0x4a, 0x1d, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x4c, 0x4e, 0x51, 0x40, 0x4b, 0x4b, 0x58, 0x1f, 0x56, 0x44, 0x9f, 0x42, 0x40, 0x4d, 0x1f, 0x43, 0x4e, 0x1b, 0x1f, 0x13, 0x4e, 0x1f, 0x40, 0x45, 0x45, 0x44, 0x42, 0x53, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x50, 0x54, 0x40, 0x4b, 0x48, 0x53, 0x58, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0xc4, 0x43, 0x40, 0x58, 0x1d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x47, 0x48, 0x46, 0x47, 0x44, 0x52, 0x53, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x51, 0x53, 0x52, 0x1b, 0x9f, 0x1c, 0x13, 0x47, 0x4e, 0x51, 0x44, 0x40, 0x54, 0x1f, 0x9f, 0x9f, ]], +[[0x8, 0x45, 0x1f, 0x4e, 0x4d, 0x44, 0x1f, 0x40, 0x43, 0x55, 0x40, 0x4d, 0x42, 0x44, 0x52, 0x9f, 0x42, 0x4e, 0x4d, 0x45, 0x48, 0x43, 0x44, 0x4d, 0x53, 0x4b, 0x58, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0xc4, 0x43, 0x48, 0x51, 0x44, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x47, 0x48, 0x52, 0x9f, 0x43, 0x51, 0x44, 0x40, 0x4c, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x44, 0x4d, 0x43, 0x44, 0x40, 0x55, 0x4e, 0x51, 0x52, 0x1f, 0x53, 0x4e, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x47, 0x44, 0x9f, 0x47, 0x40, 0x52, 0x1f, 0x48, 0x4c, 0x40, 0x46, 0x48, 0x4d, 0x44, 0x43, 0x1d, 0x1f, 0x47, 0x44, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4c, 0x44, 0x44, 0x53, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x1f, 0x40, 0x9f, ], [0x52, 0x54, 0x42, 0x42, 0x44, 0x52, 0x52, 0x1f, 0x54, 0x4d, 0x44, 0x57, 0x4f, 0x44, 0x42, 0x53, 0x44, 0xc3, 0x48, 0x4d, 0x1f, 0x42, 0x4e, 0x4c, 0x4c, 0x4e, 0x4d, 0x1f, 0x47, 0x4e, 0x54, 0x51, 0x52, 0x1b, 0x9f, 0x7, 0x44, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4f, 0x54, 0x53, 0x1f, 0x52, 0x4e, 0x4c, 0x44, 0x9f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x41, 0x44, 0x47, 0x48, 0x4d, 0x43, 0x1d, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4f, 0x40, 0x52, 0x52, 0x1f, 0x40, 0x4d, 0x9f, 0x48, 0x4d, 0x55, 0x48, 0x52, 0x48, 0x41, 0x4b, 0x44, 0x9f, 0x41, 0x4e, 0x54, 0x4d, 0x43, 0x40, 0x51, 0x58, 0x1b, 0x1f, 0xd, 0x44, 0x56, 0x1d, 0x9f, 0x54, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x40, 0x4b, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, ], [0x4c, 0x4e, 0x51, 0x44, 0x1f, 0x4b, 0x48, 0x41, 0x44, 0x51, 0x40, 0x4b, 0x1f, 0x4b, 0x40, 0x56, 0x52, 0x9f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x41, 0x44, 0x46, 0x48, 0x4d, 0x1f, 0x53, 0x4e, 0x9f, 0x44, 0x52, 0x53, 0x40, 0x41, 0x4b, 0x48, 0x52, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x52, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x40, 0x51, 0x4e, 0x54, 0x4d, 0x43, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x48, 0x4d, 0x1f, 0x47, 0x48, 0x4c, 0x1d, 0x1f, 0x4e, 0xd1, 0x53, 0x47, 0x44, 0x1f, 0x4e, 0x4b, 0x43, 0x1f, 0x4b, 0x40, 0x56, 0x52, 0x1f, 0x41, 0x44, 0x9f, 0x44, 0x57, 0x4f, 0x40, 0x4d, 0x43, 0x44, 0x43, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x48, 0x4d, 0x53, 0x44, 0x51, 0x4f, 0x51, 0x44, 0x53, 0x44, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x47, 0x48, 0xd2, ], [0x45, 0x40, 0x55, 0x4e, 0x51, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x4b, 0x48, 0x41, 0x44, 0x51, 0x40, 0x4b, 0x1f, 0x52, 0x44, 0x4d, 0x52, 0x44, 0x1d, 0x1f, 0x40, 0x4d, 0xc3, 0x47, 0x44, 0x1f, 0x56, 0x48, 0x4b, 0x4b, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x42, 0x44, 0x4d, 0x52, 0x44, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x47, 0x48, 0x46, 0x47, 0x44, 0x51, 0x1f, 0x4e, 0x51, 0x43, 0x44, 0x51, 0x1f, 0x4e, 0x45, 0x9f, 0x41, 0x44, 0x48, 0x4d, 0x46, 0x52, 0x1b, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x8, 0x5d, 0x4c, 0x1f, 0x4d, 0x4e, 0x41, 0x4e, 0x43, 0x58, 0x1a, 0x1f, 0x16, 0x47, 0x4e, 0x9f, 0x40, 0x51, 0x44, 0x1f, 0x58, 0x4e, 0x54, 0x1e, 0x1f, 0x0, 0x51, 0x44, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x4d, 0x4e, 0x41, 0x4e, 0x43, 0x58, 0x1d, 0x1f, 0x53, 0x4e, 0x4e, 0x1e, 0x1f, 0x13, 0x47, 0x44, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x5d, 0x52, 0x1f, 0x40, 0x1f, 0x4f, 0x40, 0x48, 0x51, 0x1f, 0x4e, 0x45, 0x9f, 0x54, 0x52, 0x1d, 0x1f, 0x43, 0x4e, 0x4d, 0x5d, 0x53, 0x1f, 0x53, 0x44, 0x4b, 0x4b, 0x1a, 0x9f, 0x13, 0x47, 0x44, 0x58, 0x5d, 0x43, 0x1f, 0x41, 0x40, 0x4d, 0x48, 0x52, 0x47, 0x1f, 0x54, 0x52, 0x1d, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x4a, 0x4d, 0x4e, 0x56, 0x1b, 0x1f, 0x7, 0x4e, 0x56, 0x9f, 0x43, 0x51, 0x44, 0x40, 0x51, 0x58, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x9f, ], [0x52, 0x4e, 0x4c, 0x44, 0x41, 0x4e, 0x43, 0x58, 0x1a, 0x1f, 0x7, 0x4e, 0x56, 0x9f, 0x4f, 0x54, 0x41, 0x4b, 0x48, 0x42, 0x1d, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x40, 0x9f, 0x45, 0x51, 0x4e, 0x46, 0x1f, 0x13, 0x4e, 0x1f, 0x53, 0x44, 0x4b, 0x4b, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x9f, 0x4d, 0x40, 0x4c, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4b, 0x48, 0x55, 0x44, 0x4b, 0x4e, 0x4d, 0x46, 0x9f, 0x43, 0x40, 0x58, 0x1f, 0x13, 0x4e, 0x1f, 0x40, 0x4d, 0x1f, 0x40, 0x43, 0x4c, 0x48, 0x51, 0x48, 0x4d, 0xc6, 0x41, 0x4e, 0x46, 0x1a, 0x1f, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x48, 0x4d, 0x52, 0x4e, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, ]], +[[0x7, 0x4e, 0x56, 0x1f, 0x47, 0x40, 0x4f, 0x4f, 0x58, 0x1f, 0x48, 0x52, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4b, 0x48, 0x53, 0x53, 0x4b, 0x44, 0x1f, 0x52, 0x53, 0x4e, 0x4d, 0x44, 0x1f, 0x13, 0x47, 0x40, 0x53, 0x9f, 0x51, 0x40, 0x4c, 0x41, 0x4b, 0x44, 0x52, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x51, 0x4e, 0x40, 0x43, 0x1f, 0x40, 0x4b, 0x4e, 0x4d, 0x44, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x43, 0x4e, 0x44, 0x52, 0x4d, 0x5d, 0x53, 0x1f, 0x42, 0x40, 0x51, 0x44, 0x1f, 0x40, 0x41, 0x4e, 0x54, 0xd3, 0x42, 0x40, 0x51, 0x44, 0x44, 0x51, 0x52, 0x1d, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x44, 0x57, 0x48, 0x46, 0x44, 0x4d, 0x42, 0x48, 0x44, 0x52, 0x1f, 0x4d, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x45, 0x44, 0x40, 0x51, 0x52, 0x1b, 0x1f, 0x16, 0x47, 0x4e, 0x52, 0x44, 0x1f, 0x42, 0x4e, 0x40, 0x53, 0x9f, ], [0x4e, 0x45, 0x1f, 0x44, 0x4b, 0x44, 0x4c, 0x44, 0x4d, 0x53, 0x40, 0x4b, 0x1f, 0x41, 0x51, 0x4e, 0x56, 0xcd, 0x0, 0x1f, 0x4f, 0x40, 0x52, 0x52, 0x48, 0x4d, 0x46, 0x1f, 0x54, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0xc4, 0x4f, 0x54, 0x53, 0x1f, 0x4e, 0x4d, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x9f, 0x48, 0x4d, 0x43, 0x44, 0x4f, 0x44, 0x4d, 0x43, 0x44, 0x4d, 0x53, 0x1f, 0x40, 0x52, 0x1f, 0x53, 0x47, 0xc4, 0x52, 0x54, 0x4d, 0x1d, 0x1f, 0x0, 0x52, 0x52, 0x4e, 0x42, 0x48, 0x40, 0x53, 0x44, 0x52, 0x1f, 0x4e, 0xd1, 0x46, 0x4b, 0x4e, 0x56, 0x52, 0x1f, 0x40, 0x4b, 0x4e, 0x4d, 0x44, 0x1d, 0x9f, 0x5, 0x54, 0x4b, 0x45, 0x48, 0x4b, 0x4b, 0x48, 0x4d, 0x46, 0x9f, 0x40, 0x41, 0x52, 0x4e, 0x4b, 0x54, 0x53, 0x44, 0x1f, 0x43, 0x44, 0x42, 0x51, 0x44, 0x44, 0x1f, 0x8, 0xcd, ], [0x42, 0x40, 0x52, 0x54, 0x40, 0x4b, 0x1f, 0x52, 0x48, 0x4c, 0x4f, 0x4b, 0x48, 0x42, 0x48, 0x53, 0x58, 0x9b, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x48, 0x4d, 0x52, 0x4e, 0x4d, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], +[[0x1, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x1f, 0x8, 0x1f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x52, 0x53, 0x4e, 0x4f, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x43, 0x44, 0x40, 0x53, 0xc7, 0x7, 0x44, 0x1f, 0x4a, 0x48, 0x4d, 0x43, 0x4b, 0x58, 0x1f, 0x52, 0x53, 0x4e, 0x4f, 0x4f, 0x44, 0x43, 0x9f, 0x45, 0x4e, 0x51, 0x1f, 0x4c, 0x44, 0x1b, 0x1f, 0x13, 0x47, 0x44, 0x9f, 0x42, 0x40, 0x51, 0x51, 0x48, 0x40, 0x46, 0x44, 0x1f, 0x47, 0x44, 0x4b, 0x43, 0x1f, 0x41, 0x54, 0x53, 0x9f, 0x49, 0x54, 0x52, 0x53, 0x1f, 0x4e, 0x54, 0x51, 0x52, 0x44, 0x4b, 0x55, 0x44, 0x52, 0x1f, 0x0, 0x4d, 0xc3, 0x48, 0x4c, 0x4c, 0x4e, 0x51, 0x53, 0x40, 0x4b, 0x48, 0x53, 0x58, 0x9f, 0x1c, 0x3, 0x48, 0x42, 0x4a, 0x48, 0x4d, 0x52, 0x4e, 0x4d, 0x1f, 0x9f, ]], +[[0x5, 0x4e, 0x51, 0x1d, 0x1f, 0x4b, 0x48, 0x4a, 0x44, 0x1f, 0x40, 0x4b, 0x4c, 0x4e, 0x52, 0x53, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x4e, 0x4d, 0x44, 0x1f, 0x44, 0x4b, 0x52, 0x44, 0x1f, 0x48, 0x4d, 0x9f, 0x4e, 0x54, 0x51, 0x1f, 0x42, 0x4e, 0x54, 0x4d, 0x53, 0x51, 0x58, 0x1d, 0x1f, 0x8, 0x9f, 0x52, 0x53, 0x40, 0x51, 0x53, 0x44, 0x43, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x56, 0x48, 0x53, 0x47, 0x9f, 0x4c, 0x58, 0x1f, 0x52, 0x47, 0x40, 0x51, 0x44, 0x1f, 0x4e, 0x45, 0x9f, 0x4e, 0x4f, 0x53, 0x48, 0x4c, 0x48, 0x52, 0x4c, 0x1b, 0x1f, 0x8, 0x9f, 0x41, 0x44, 0x4b, 0x48, 0x44, 0x55, 0x44, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x47, 0x40, 0x51, 0x43, 0x9f, 0x56, 0x4e, 0x51, 0x4a, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x51, 0x4e, 0x46, 0x51, 0x44, 0x52, 0x52, 0x9f, ], [0x40, 0x4d, 0x43, 0x1f, 0x40, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x1d, 0x1f, 0x41, 0x54, 0x53, 0x9f, 0x4d, 0x4e, 0x56, 0x1d, 0x1f, 0x40, 0x45, 0x53, 0x44, 0x51, 0x1f, 0x45, 0x48, 0x51, 0x52, 0x53, 0x9f, 0x41, 0x44, 0x48, 0x4d, 0x46, 0x1f, 0x5d, 0x45, 0x4e, 0x51, 0x5d, 0x9f, 0x52, 0x4e, 0x42, 0x48, 0x44, 0x53, 0x58, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x53, 0x47, 0x44, 0x4d, 0x9f, 0x5d, 0x40, 0x46, 0x40, 0x48, 0x4d, 0x52, 0x53, 0x5d, 0x1f, 0x48, 0x53, 0x1d, 0x1f, 0x8, 0x9f, 0x40, 0x52, 0x52, 0x48, 0x46, 0x4d, 0x1f, 0x4c, 0x58, 0x52, 0x44, 0x4b, 0x45, 0x1f, 0x4d, 0x4e, 0x9f, 0x51, 0x40, 0x4d, 0x4a, 0x1f, 0x4e, 0x51, 0x1f, 0x40, 0x4d, 0x58, 0x1f, 0x4b, 0x48, 0x4c, 0x48, 0x53, 0x9d, 0x40, 0x4d, 0x43, 0x1f, 0x52, 0x54, 0x42, 0x47, 0x1f, 0x40, 0x4d, 0x9f, ], [0x40, 0x53, 0x53, 0x48, 0x53, 0x54, 0x43, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x55, 0x44, 0x51, 0x58, 0x9f, 0x4c, 0x54, 0x42, 0x47, 0x1f, 0x40, 0x46, 0x40, 0x48, 0x4d, 0x52, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x53, 0x51, 0x44, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x53, 0x48, 0x4c, 0x44, 0x52, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x4c, 0x58, 0x9f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1f, 0x47, 0x40, 0x52, 0x1f, 0x41, 0x44, 0x42, 0x4e, 0x4c, 0x44, 0x9f, 0x4e, 0x4d, 0x44, 0x1f, 0x4e, 0x45, 0x1f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x9f, 0x4f, 0x4e, 0x52, 0x52, 0x48, 0x41, 0x48, 0x4b, 0x48, 0x53, 0x48, 0x44, 0x52, 0x1b, 0x9f, 0x16, 0x47, 0x40, 0x53, 0x1f, 0x40, 0x1f, 0x4f, 0x47, 0x51, 0x40, 0x52, 0x44, 0x1f, 0x1c, 0x9f, ], [0x52, 0x53, 0x48, 0x4b, 0x4b, 0x1f, 0x48, 0x53, 0x5d, 0x52, 0x1f, 0x40, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x9f, 0x4f, 0x47, 0x51, 0x40, 0x52, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x9f, 0x55, 0x48, 0x44, 0x56, 0x1f, 0x4e, 0x45, 0x1f, 0x4b, 0x48, 0x45, 0x44, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x40, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x52, 0x47, 0x4e, 0x54, 0x4b, 0x43, 0x4d, 0x5d, 0x53, 0x9f, 0x40, 0x42, 0x42, 0x44, 0x4f, 0x53, 0x1f, 0x40, 0x4d, 0x58, 0x1f, 0x4e, 0x53, 0x47, 0x44, 0x51, 0x1b, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x4c, 0x54, 0x42, 0x47, 0x1f, 0x8, 0x5d, 0x55, 0x44, 0x9f, 0x4b, 0x44, 0x40, 0x51, 0x4d, 0x44, 0x43, 0x9f, 0x54, 0x4d, 0x43, 0x44, 0x51, 0x46, 0x51, 0x4e, 0x54, 0x4d, 0x43, 0x1b, 0x1f, 0x14, 0x4d, 0x53, 0x48, 0xcb, ], [0x52, 0x4e, 0x4c, 0x44, 0x1f, 0x46, 0x40, 0x4d, 0x46, 0x1f, 0x52, 0x54, 0x42, 0x42, 0x44, 0x44, 0x43, 0xd2, 0x48, 0x4d, 0x1f, 0x4f, 0x54, 0x53, 0x53, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0x1f, 0x52, 0x53, 0x51, 0x40, 0x48, 0x53, 0x9f, 0x49, 0x40, 0x42, 0x4a, 0x44, 0x53, 0x1d, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x43, 0x44, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x48, 0x52, 0x9f, 0x4f, 0x4e, 0x52, 0x52, 0x48, 0x41, 0x48, 0x4b, 0x48, 0x53, 0x58, 0x1b, 0x9f, 0x1c, 0x4, 0x4b, 0x4b, 0x48, 0x52, 0x4e, 0x4d, 0x1f, 0x9f, 0x9f, ]], +] \ No newline at end of file diff --git a/worlds/smw/Names/LocationName.py b/worlds/smw/Names/LocationName.py new file mode 100644 index 0000000000..2b42cd21a4 --- /dev/null +++ b/worlds/smw/Names/LocationName.py @@ -0,0 +1,364 @@ +# Level Definitions +yoshis_house = "Yoshi's House" +yoshis_house_tile = "Yoshi's House - Tile" + +yoshis_island_1_exit_1 = "Yoshi's Island 1 - Normal Exit" +yoshis_island_1_dragon = "Yoshi's Island 1 - Dragon Coins" +yoshis_island_2_exit_1 = "Yoshi's Island 2 - Normal Exit" +yoshis_island_2_dragon = "Yoshi's Island 2 - Dragon Coins" +yoshis_island_3_exit_1 = "Yoshi's Island 3 - Normal Exit" +yoshis_island_3_dragon = "Yoshi's Island 3 - Dragon Coins" +yoshis_island_4_exit_1 = "Yoshi's Island 4 - Normal Exit" +yoshis_island_4_dragon = "Yoshi's Island 4 - Dragon Coins" +yoshis_island_castle = "#1 Iggy's Castle - Normal Exit" +yoshis_island_koopaling = "#1 Iggy's Castle - Boss" + +yellow_switch_palace = "Yellow Switch Palace" + +donut_plains_1_exit_1 = "Donut Plains 1 - Normal Exit" +donut_plains_1_exit_2 = "Donut Plains 1 - Secret Exit" +donut_plains_1_dragon = "Donut Plains 1 - Dragon Coins" +donut_plains_2_exit_1 = "Donut Plains 2 - Normal Exit" +donut_plains_2_exit_2 = "Donut Plains 2 - Secret Exit" +donut_plains_2_dragon = "Donut Plains 2 - Dragon Coins" +donut_plains_3_exit_1 = "Donut Plains 3 - Normal Exit" +donut_plains_3_dragon = "Donut Plains 3 - Dragon Coins" +donut_plains_4_exit_1 = "Donut Plains 4 - Normal Exit" +donut_plains_4_dragon = "Donut Plains 4 - Dragon Coins" +donut_secret_1_exit_1 = "Donut Secret 1 - Normal Exit" +donut_secret_1_exit_2 = "Donut Secret 1 - Secret Exit" +donut_secret_1_dragon = "Donut Secret 1 - Dragon Coins" +donut_secret_2_exit_1 = "Donut Secret 2 - Normal Exit" +donut_secret_2_dragon = "Donut Secret 2 - Dragon Coins" +donut_ghost_house_exit_1 = "Donut Ghost House - Normal Exit" +donut_ghost_house_exit_2 = "Donut Ghost House - Secret Exit" +donut_secret_house_exit_1 = "Donut Secret House - Normal Exit" +donut_secret_house_exit_2 = "Donut Secret House - Secret Exit" +donut_plains_castle = "#2 Morton's Castle - Normal Exit" +donut_plains_koopaling = "#2 Morton's Castle - Boss" + +green_switch_palace = "Green Switch Palace" + +vanilla_dome_1_exit_1 = "Vanilla Dome 1 - Normal Exit" +vanilla_dome_1_exit_2 = "Vanilla Dome 1 - Secret Exit" +vanilla_dome_1_dragon = "Vanilla Dome 1 - Dragon Coins" +vanilla_dome_2_exit_1 = "Vanilla Dome 2 - Normal Exit" +vanilla_dome_2_exit_2 = "Vanilla Dome 2 - Secret Exit" +vanilla_dome_2_dragon = "Vanilla Dome 2 - Dragon Coins" +vanilla_dome_3_exit_1 = "Vanilla Dome 3 - Normal Exit" +vanilla_dome_3_dragon = "Vanilla Dome 3 - Dragon Coins" +vanilla_dome_4_exit_1 = "Vanilla Dome 4 - Normal Exit" +vanilla_dome_4_dragon = "Vanilla Dome 4 - Dragon Coins" +vanilla_secret_1_exit_1 = "Vanilla Secret 1 - Normal Exit" +vanilla_secret_1_exit_2 = "Vanilla Secret 1 - Secret Exit" +vanilla_secret_1_dragon = "Vanilla Secret 1 - Dragon Coins" +vanilla_secret_2_exit_1 = "Vanilla Secret 2 - Normal Exit" +vanilla_secret_2_dragon = "Vanilla Secret 2 - Dragon Coins" +vanilla_secret_3_exit_1 = "Vanilla Secret 3 - Normal Exit" +vanilla_secret_3_dragon = "Vanilla Secret 3 - Dragon Coins" +vanilla_ghost_house_exit_1 = "Vanilla Ghost House - Normal Exit" +vanilla_ghost_house_dragon = "Vanilla Ghost House - Dragon Coins" +vanilla_fortress = "Vanilla Fortress - Normal Exit" +vanilla_reznor = "Vanilla Fortress - Boss" +vanilla_dome_castle = "#3 Lemmy's Castle - Normal Exit" +vanilla_dome_koopaling = "#3 Lemmy's Castle - Boss" + +red_switch_palace = "Red Switch Palace" + +butter_bridge_1_exit_1 = "Butter Bridge 1 - Normal Exit" +butter_bridge_1_dragon = "Butter Bridge 1 - Dragon Coins" +butter_bridge_2_exit_1 = "Butter Bridge 2 - Normal Exit" +butter_bridge_2_dragon = "Butter Bridge 2 - Dragon Coins" +cheese_bridge_exit_1 = "Cheese Bridge - Normal Exit" +cheese_bridge_exit_2 = "Cheese Bridge - Secret Exit" +cheese_bridge_dragon = "Cheese Bridge - Dragon Coins" +cookie_mountain_exit_1 = "Cookie Mountain - Normal Exit" +cookie_mountain_dragon = "Cookie Mountain - Dragon Coins" +soda_lake_exit_1 = "Soda Lake - Normal Exit" +soda_lake_dragon = "Soda Lake - Dragon Coins" +twin_bridges_castle = "#4 Ludwig's Castle - Normal Exit" +twin_bridges_koopaling = "#4 Ludwig's Castle - Boss" + +forest_of_illusion_1_exit_1 = "Forest of Illusion 1 - Normal Exit" +forest_of_illusion_1_exit_2 = "Forest of Illusion 1 - Secret Exit" +forest_of_illusion_2_exit_1 = "Forest of Illusion 2 - Normal Exit" +forest_of_illusion_2_exit_2 = "Forest of Illusion 2 - Secret Exit" +forest_of_illusion_2_dragon = "Forest of Illusion 2 - Dragon Coins" +forest_of_illusion_3_exit_1 = "Forest of Illusion 3 - Normal Exit" +forest_of_illusion_3_exit_2 = "Forest of Illusion 3 - Secret Exit" +forest_of_illusion_3_dragon = "Forest of Illusion 3 - Dragon Coins" +forest_of_illusion_4_exit_1 = "Forest of Illusion 4 - Normal Exit" +forest_of_illusion_4_exit_2 = "Forest of Illusion 4 - Secret Exit" +forest_of_illusion_4_dragon = "Forest of Illusion 4 - Dragon Coins" +forest_ghost_house_exit_1 = "Forest Ghost House - Normal Exit" +forest_ghost_house_exit_2 = "Forest Ghost House - Secret Exit" +forest_ghost_house_dragon = "Forest Ghost House - Dragon Coins" +forest_secret_exit_1 = "Forest Secret - Normal Exit" +forest_secret_dragon = "Forest Secret - Dragon Coins" +forest_fortress = "Forest Fortress - Normal Exit" +forest_reznor = "Forest Fortress - Boss" +forest_castle = "#5 Roy's Castle - Normal Exit" +forest_castle_dragon = "#5 Roy's Castle - Dragon Coins" +forest_koopaling = "#5 Roy's Castle - Boss" + +blue_switch_palace = "Blue Switch Palace" + +chocolate_island_1_exit_1 = "Chocolate Island 1 - Normal Exit" +chocolate_island_1_dragon = "Chocolate Island 1 - Dragon Coins" +chocolate_island_2_exit_1 = "Chocolate Island 2 - Normal Exit" +chocolate_island_2_exit_2 = "Chocolate Island 2 - Secret Exit" +chocolate_island_2_dragon = "Chocolate Island 2 - Dragon Coins" +chocolate_island_3_exit_1 = "Chocolate Island 3 - Normal Exit" +chocolate_island_3_exit_2 = "Chocolate Island 3 - Secret Exit" +chocolate_island_3_dragon = "Chocolate Island 3 - Dragon Coins" +chocolate_island_4_exit_1 = "Chocolate Island 4 - Normal Exit" +chocolate_island_4_dragon = "Chocolate Island 4 - Dragon Coins" +chocolate_island_5_exit_1 = "Chocolate Island 5 - Normal Exit" +chocolate_island_5_dragon = "Chocolate Island 5 - Dragon Coins" +chocolate_ghost_house_exit_1 = "Choco-Ghost House - Normal Exit" +chocolate_secret_exit_1 = "Chocolate Secret - Normal Exit" +chocolate_fortress = "Chocolate Fortress - Normal Exit" +chocolate_reznor = "Chocolate Fortress Defeat" +chocolate_castle = "#6 Wendy's Castle - Normal Exit" +chocolate_koopaling = "#6 Wendy's Castle - Boss" + +sunken_ghost_ship = "Sunken Ghost Ship - Normal Exit" +sunken_ghost_ship_dragon = "Sunken Ghost Ship - Dragon Coins" + +valley_of_bowser_1_exit_1 = "Valley of Bowser 1 - Normal Exit" +valley_of_bowser_1_dragon = "Valley of Bowser 1 - Dragon Coins" +valley_of_bowser_2_exit_1 = "Valley of Bowser 2 - Normal Exit" +valley_of_bowser_2_exit_2 = "Valley of Bowser 2 - Secret Exit" +valley_of_bowser_2_dragon = "Valley of Bowser 2 - Dragon Coins" +valley_of_bowser_3_exit_1 = "Valley of Bowser 3 - Normal Exit" +valley_of_bowser_3_dragon = "Valley of Bowser 3 - Dragon Coins" +valley_of_bowser_4_exit_1 = "Valley of Bowser 4 - Normal Exit" +valley_of_bowser_4_exit_2 = "Valley of Bowser 4 - Secret Exit" +valley_ghost_house_exit_1 = "Valley Ghost House - Normal Exit" +valley_ghost_house_exit_2 = "Valley Ghost House - Secret Exit" +valley_ghost_house_dragon = "Valley Ghost House - Dragon Coins" +valley_fortress = "Valley Fortress - Normal Exit" +valley_reznor = "Valley Fortress - Boss" +valley_castle = "#7 Larry's Castle - Normal Exit" +valley_castle_dragon = "#7 Larry's Castle - Dragon Coins" +valley_koopaling = "#7 Larry's Castle - Boss" + +front_door = "Front Door" +back_door = "Back Door" +bowser = "Bowser" + +star_road_1_exit_1 = "Star Road 1 - Normal Exit" +star_road_1_exit_2 = "Star Road 1 - Secret Exit" +star_road_1_dragon = "Star Road 1 - Dragon Coins" +star_road_2_exit_1 = "Star Road 2 - Normal Exit" +star_road_2_exit_2 = "Star Road 2 - Secret Exit" +star_road_3_exit_1 = "Star Road 3 - Normal Exit" +star_road_3_exit_2 = "Star Road 3 - Secret Exit" +star_road_4_exit_1 = "Star Road 4 - Normal Exit" +star_road_4_exit_2 = "Star Road 4 - Secret Exit" +star_road_5_exit_1 = "Star Road 5 - Normal Exit" +star_road_5_exit_2 = "Star Road 5 - Secret Exit" + +special_zone_1_exit_1 = "Gnarly - Normal Exit" +special_zone_1_dragon = "Gnarly - Dragon Coins" +special_zone_2_exit_1 = "Tubular - Normal Exit" +special_zone_2_dragon = "Tubular - Dragon Coins" +special_zone_3_exit_1 = "Way Cool - Normal Exit" +special_zone_3_dragon = "Way Cool - Dragon Coins" +special_zone_4_exit_1 = "Awesome - Normal Exit" +special_zone_4_dragon = "Awesome - Dragon Coins" +special_zone_5_exit_1 = "Groovy - Normal Exit" +special_zone_5_dragon = "Groovy - Dragon Coins" +special_zone_6_exit_1 = "Mondo - Normal Exit" +special_zone_6_dragon = "Mondo - Dragon Coins" +special_zone_7_exit_1 = "Outrageous - Normal Exit" +special_zone_7_dragon = "Outrageous - Dragon Coins" +special_zone_8_exit_1 = "Funky - Normal Exit" +special_zone_8_dragon = "Funky - Dragon Coins" + + +# Region Definitions +menu_region = "Menu" + +yoshis_island_region = "Yoshi's Island" +donut_plains_region = "Donut Plains" +vanilla_dome_region = "Vanilla Dome" +twin_bridges_region = "Twin Bridges" +forest_of_illusion_region = "Forest of Illusion" +chocolate_island_region = "Chocolate Island" +valley_of_bowser_region = "Valley of Bowser" +star_road_region = "Star Road" +special_zone_region = "Special Zone" + +yoshis_island_1_tile = "Yoshi's Island 1 - Tile" +yoshis_island_1_region = "Yoshi's Island 1" +yoshis_island_2_tile = "Yoshi's Island 2 - Tile" +yoshis_island_2_region = "Yoshi's Island 2" +yoshis_island_3_tile = "Yoshi's Island 3 - Tile" +yoshis_island_3_region = "Yoshi's Island 3" +yoshis_island_4_tile = "Yoshi's Island 4 - Tile" +yoshis_island_4_region = "Yoshi's Island 4" +yoshis_island_castle_tile = "#1 Iggy's Castle - Tile" +yoshis_island_castle_region = "#1 Iggy's Castle" + +yellow_switch_palace_tile = "Yellow Switch Palace - Tile" + +donut_plains_1_tile = "Donut Plains 1 - Tile" +donut_plains_1_region = "Donut Plains 1" +donut_plains_2_tile = "Donut Plains 2 - Tile" +donut_plains_2_region = "Donut Plains 2" +donut_plains_3_tile = "Donut Plains 3 - Tile" +donut_plains_3_region = "Donut Plains 3" +donut_plains_4_tile = "Donut Plains 4 - Tile" +donut_plains_4_region = "Donut Plains 4" +donut_secret_1_tile = "Donut Secret 1 - Tile" +donut_secret_1_region = "Donut Secret 1" +donut_secret_2_tile = "Donut Secret 2 - Tile" +donut_secret_2_region = "Donut Secret 2" +donut_ghost_house_tile = "Donut Ghost House - Tile" +donut_ghost_house_region = "Donut Ghost House" +donut_secret_house_tile = "Donut Secret House - Tile" +donut_secret_house_region = "Donut Secret House" +donut_plains_castle_tile = "#2 Morton's Castle - Tile" +donut_plains_castle_region = "#2 Morton's Castle" +donut_plains_top_secret = "Top Secret Area" +donut_plains_top_secret_tile = "Top Secret Area - Tile" +donut_plains_star_road = "Donut Plains - Star Road" + +green_switch_palace_tile = "Green Switch Palace - Tile" + +vanilla_dome_1_tile = "Vanilla Dome 1 - Tile" +vanilla_dome_1_region = "Vanilla Dome 1" +vanilla_dome_2_tile = "Vanilla Dome 2 - Tile" +vanilla_dome_2_region = "Vanilla Dome 2" +vanilla_dome_3_tile = "Vanilla Dome 3 - Tile" +vanilla_dome_3_region = "Vanilla Dome 3" +vanilla_dome_4_tile = "Vanilla Dome 4 - Tile" +vanilla_dome_4_region = "Vanilla Dome 4" +vanilla_secret_1_tile = "Vanilla Secret 1 - Tile" +vanilla_secret_1_region = "Vanilla Secret 1" +vanilla_secret_2_tile = "Vanilla Secret 2 - Tile" +vanilla_secret_2_region = "Vanilla Secret 2" +vanilla_secret_3_tile = "Vanilla Secret 3 - Tile" +vanilla_secret_3_region = "Vanilla Secret 3" +vanilla_ghost_house_tile = "Vanilla Ghost House - Tile" +vanilla_ghost_house_region = "Vanilla Ghost House" +vanilla_fortress_tile = "Vanilla Fortress - Tile" +vanilla_fortress_region = "Vanilla Fortress" +vanilla_dome_castle_tile = "#3 Lemmy's Castle - Tile" +vanilla_dome_castle_region = "#3 Lemmy's Castle" +vanilla_dome_star_road = "Vanilla Dome - Star Road" + +red_switch_palace_tile = "Red Switch Palace - Tile" + +butter_bridge_1_tile = "Butter Bridge 1 - Tile" +butter_bridge_1_region = "Butter Bridge 1" +butter_bridge_2_tile = "Butter Bridge 2 - Tile" +butter_bridge_2_region = "Butter Bridge 2" +cheese_bridge_tile = "Cheese Bridge - Tile" +cheese_bridge_region = "Cheese Bridge" +cookie_mountain_tile = "Cookie Mountain - Tile" +cookie_mountain_region = "Cookie Mountain" +soda_lake_tile = "Soda Lake - Tile" +soda_lake_region = "Soda Lake" +twin_bridges_castle_tile = "#4 Ludwig's Castle - Tile" +twin_bridges_castle_region = "#4 Ludwig's Castle" +twin_bridges_star_road = "Twin Bridges - Star Road" + +forest_of_illusion_1_tile = "Forest of Illusion 1 - Tile" +forest_of_illusion_1_region = "Forest of Illusion 1" +forest_of_illusion_2_tile = "Forest of Illusion 2 - Tile" +forest_of_illusion_2_region = "Forest of Illusion 2" +forest_of_illusion_3_tile = "Forest of Illusion 3 - Tile" +forest_of_illusion_3_region = "Forest of Illusion 3" +forest_of_illusion_4_tile = "Forest of Illusion 4 - Tile" +forest_of_illusion_4_region = "Forest of Illusion 4" +forest_ghost_house_tile = "Forest Ghost House - Tile" +forest_ghost_house_region = "Forest Ghost House" +forest_secret_tile = "Forest Secret - Tile" +forest_secret_region = "Forest Secret" +forest_fortress_tile = "Forest Fortress - Tile" +forest_fortress_region = "Forest Fortress" +forest_castle_tile = "#5 Roy's Castle - Tile" +forest_castle_region = "#5 Roy's Castle" +forest_star_road = "Forest of Illusion - Star Road" + +blue_switch_palace_tile = "Blue Switch Palace - Tile" + +chocolate_island_1_tile = "Chocolate Island 1 - Tile" +chocolate_island_1_region = "Chocolate Island 1" +chocolate_island_2_tile = "Chocolate Island 2 - Tile" +chocolate_island_2_region = "Chocolate Island 2" +chocolate_island_3_tile = "Chocolate Island 3 - Tile" +chocolate_island_3_region = "Chocolate Island 3" +chocolate_island_4_tile = "Chocolate Island 4 - Tile" +chocolate_island_4_region = "Chocolate Island 4" +chocolate_island_5_tile = "Chocolate Island 5 - Tile" +chocolate_island_5_region = "Chocolate Island 5" +chocolate_ghost_house_tile = "Choco-Ghost House - Tile" +chocolate_ghost_house_region = "Choco-Ghost House" +chocolate_secret_tile = "Chocolate Secret - Tile" +chocolate_secret_region = "Chocolate Secret" +chocolate_fortress_tile = "Chocolate Fortress - Tile" +chocolate_fortress_region = "Chocolate Fortress" +chocolate_castle_tile = "#6 Wendy's Castle - Tile" +chocolate_castle_region = "#6 Wendy's Castle" + +sunken_ghost_ship_tile = "Sunken Ghost Ship - Tile" +sunken_ghost_ship_region = "Sunken Ghost Ship" + +valley_of_bowser_1_tile = "Valley of Bowser 1 - Tile" +valley_of_bowser_1_region = "Valley of Bowser 1" +valley_of_bowser_2_tile = "Valley of Bowser 2 - Tile" +valley_of_bowser_2_region = "Valley of Bowser 2" +valley_of_bowser_3_tile = "Valley of Bowser 3 - Tile" +valley_of_bowser_3_region = "Valley of Bowser 3" +valley_of_bowser_4_tile = "Valley of Bowser 4 - Tile" +valley_of_bowser_4_region = "Valley of Bowser 4" +valley_ghost_house_tile = "Valley Ghost House - Tile" +valley_ghost_house_region = "Valley Ghost House" +valley_fortress_tile = "Valley Fortress - Tile" +valley_fortress_region = "Valley Fortress" +valley_castle_tile = "#7 Larry's Castle - Tile" +valley_castle_region = "#7 Larry's Castle" +valley_star_road = "Valley of Bowser - Star Road" + +front_door_tile = "Front Door - Tile" +back_door_tile = "Back Door - Tile" +bowser_region = "Bowser - Region" + +star_road_donut = "Star Road - Donut Plains" +star_road_1_tile = "Star Road 1 - Tile" +star_road_1_region = "Star Road 1" +star_road_vanilla = "Star Road - Vanilla Dome" +star_road_2_tile = "Star Road 2 - Tile" +star_road_2_region = "Star Road 2" +star_road_twin_bridges = "Star Road - Twin Bridges" +star_road_3_tile = "Star Road 3 - Tile" +star_road_3_region = "Star Road 3" +star_road_forest = "Star Road - Forest of Illusion" +star_road_4_tile = "Star Road 4 - Tile" +star_road_4_region = "Star Road 4" +star_road_valley = "Star Road - Valley of Bowser" +star_road_5_tile = "Star Road 5 - Tile" +star_road_5_region = "Star Road 5" +star_road_special = "Star Road - Special Zone" + +special_star_road = "Special Zone - Star Road" +special_zone_1_tile = "Gnarly - Tile" +special_zone_1_region = "Gnarly" +special_zone_2_tile = "Tubular - Tile" +special_zone_2_region = "Tubular" +special_zone_3_tile = "Way Cool - Tile" +special_zone_3_region = "Way Cool" +special_zone_4_tile = "Awesome - Tile" +special_zone_4_region = "Awesome" +special_zone_5_tile = "Groovy - Tile" +special_zone_5_region = "Groovy" +special_zone_6_tile = "Mondo - Tile" +special_zone_6_region = "Mondo" +special_zone_7_tile = "Outrageous - Tile" +special_zone_7_region = "Outrageous" +special_zone_8_tile = "Funky - Tile" +special_zone_8_region = "Funky" +special_complete = "Special Zone - Star Road - Complete" diff --git a/worlds/smw/Names/TextBox.py b/worlds/smw/Names/TextBox.py new file mode 100644 index 0000000000..cecf088661 --- /dev/null +++ b/worlds/smw/Names/TextBox.py @@ -0,0 +1,140 @@ + +from BaseClasses import MultiWorld + +import math + + +text_mapping = { + "A": 0x00, "B": 0x01, "C": 0x02, "D": 0x03, "E": 0x04, "F": 0x05, "G": 0x06, "H": 0x07, "I": 0x08, "J": 0x09, + "K": 0x0A, "L": 0x0B, "M": 0x0C, "N": 0x0D, "O": 0x0E, "P": 0x0F, "Q": 0x10, "R": 0x11, "S": 0x12, "T": 0x13, + "U": 0x14, "V": 0x15, "W": 0x16, "X": 0x17, "Y": 0x18, "Z": 0x19, + + "!": 0x1A, ".": 0x1B, "-": 0x1C, ",": 0x1D, "?": 0x1E, " ": 0x1F, + + "0": 0x22, "1": 0x23, "2": 0x24, "3": 0x25, "4": 0x26, "5": 0x27, "6": 0x28, "7": 0x29, "8": 0x2A, "9": 0x2B, + + "a": 0x40, "b": 0x41, "c": 0x42, "d": 0x43, "e": 0x44, "f": 0x45, "g": 0x46, "h": 0x47, "i": 0x48, "j": 0x49, + "k": 0x4A, "l": 0x4B, "m": 0x4C, "n": 0x4D, "o": 0x4E, "p": 0x4F, "q": 0x50, "r": 0x51, "s": 0x52, "t": 0x53, + "u": 0x54, "v": 0x55, "w": 0x56, "x": 0x57, "y": 0x58, "z": 0x59, + + "#": 0x5A, "(": 0x5B, ")": 0x5C, "'": 0x5D +} + +title_text_mapping = { + "A": [0x0A, 0x38], "B": [0x0B, 0x38], "C": [0x0C, 0x38], "D": [0x0D, 0x38], "E": [0x0E, 0x38], + "F": [0x0F, 0x38], "G": [0x10, 0x38], "H": [0x11, 0x38], "I": [0x12, 0x38], "J": [0x13, 0x38], + "K": [0x14, 0x38], "L": [0x15, 0x38], "M": [0x16, 0x38], "N": [0x17, 0x38], "O": [0x18, 0x38], + "P": [0x19, 0x38], "Q": [0x1A, 0x38], "R": [0x1B, 0x38], "S": [0x1C, 0x38], "T": [0x1D, 0x38], + "U": [0x1E, 0x38], "V": [0x1F, 0x38], "W": [0x20, 0x38], "X": [0x21, 0x38], "Y": [0x22, 0x38], + "Z": [0x23, 0x38], " ": [0xFC, 0x38], ".": [0x24, 0x38], + "0": [0x00, 0x38], "1": [0x01, 0x38], "2": [0x02, 0x38], "3": [0x03, 0x38], "4": [0x04, 0x38], + "5": [0x05, 0x38], "6": [0x06, 0x38], "7": [0x07, 0x38], "8": [0x08, 0x38], "9": [0x09, 0x38], +} + + +def string_to_bytes(input_string): + out_array = bytearray() + for letter in input_string: + out_array.append(text_mapping[letter] if letter in text_mapping else text_mapping["."]) + + return out_array + + +def generate_text_box(input_string): + out_bytes = bytearray() + box_line_count = 0 + box_line_chr_count = 0 + for word in input_string.split(): + if box_line_chr_count + len(word) > 18: + out_bytes[-1] += 0x80 + box_line_count += 1 + box_line_chr_count = 0 + + out_bytes.extend(string_to_bytes(word)) + box_line_chr_count += len(word) + + if box_line_chr_count < 18: + box_line_chr_count += 1 + out_bytes.append(0x1F) + + for i in range(box_line_count, 8): + out_bytes.append(0x9F) + + return out_bytes + + +def generate_goal_text(world: MultiWorld, player: int): + out_array = bytearray() + if world.goal[player] == "yoshi_egg_hunt": + required_yoshi_eggs = max(math.floor( + world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + out_array += bytearray([0x9F, 0x9F]) + out_array += string_to_bytes(" You must acquire") + out_array[-1] += 0x80 + out_array += string_to_bytes(f' {required_yoshi_eggs:02} Yoshi Eggs,') + out_array[-1] += 0x80 + out_array += string_to_bytes("then return here.") + out_array[-1] += 0x80 + out_array += bytearray([0x9F, 0x9F, 0x9F]) + else: + bosses_required = world.bosses_required[player].value + out_array += bytearray([0x9F, 0x9F]) + out_array += string_to_bytes(" You must defeat") + out_array[-1] += 0x80 + out_array += string_to_bytes(f' {bosses_required:02} Bosses,') + out_array[-1] += 0x80 + out_array += string_to_bytes("then defeat Bowser") + out_array[-1] += 0x80 + out_array += bytearray([0x9F, 0x9F, 0x9F]) + + return out_array + + +def generate_received_text(item_name: str, player_name: str): + out_array = bytearray() + + item_name = item_name[:18] + player_name = player_name[:18] + + item_buffer = max(0, math.floor((18 - len(item_name)) / 2)) + player_buffer = max(0, math.floor((18 - len(player_name)) / 2)) + + out_array += bytearray([0x9F, 0x9F]) + out_array += string_to_bytes(" Received") + out_array[-1] += 0x80 + out_array += bytearray([0x1F] * item_buffer) + out_array += string_to_bytes(item_name) + out_array[-1] += 0x80 + out_array += string_to_bytes(" from") + out_array[-1] += 0x80 + out_array += bytearray([0x1F] * player_buffer) + out_array += string_to_bytes(player_name) + out_array[-1] += 0x80 + out_array += bytearray([0x9F, 0x9F]) + + return out_array + + +def generate_sent_text(item_name: str, player_name: str): + out_array = bytearray() + + item_name = item_name[:18] + player_name = player_name[:18] + + item_buffer = max(0, math.floor((18 - len(item_name)) / 2)) + player_buffer = max(0, math.floor((18 - len(player_name)) / 2)) + + out_array += bytearray([0x9F, 0x9F]) + out_array += string_to_bytes(" Sent") + out_array[-1] += 0x80 + out_array += bytearray([0x1F] * item_buffer) + out_array += string_to_bytes(item_name) + out_array[-1] += 0x80 + out_array += string_to_bytes(" to") + out_array[-1] += 0x80 + out_array += bytearray([0x1F] * player_buffer) + out_array += string_to_bytes(player_name) + out_array[-1] += 0x80 + out_array += bytearray([0x9F, 0x9F]) + + return out_array diff --git a/worlds/smw/Options.py b/worlds/smw/Options.py new file mode 100644 index 0000000000..80af63f5a4 --- /dev/null +++ b/worlds/smw/Options.py @@ -0,0 +1,236 @@ +import typing + +from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList + + +class Goal(Choice): + """ + Determines the goal of the seed + Bowser: Defeat Koopalings, reach Bowser's Castle and defeat Bowser + Yoshi Egg Hunt: Find a certain number of Yoshi Eggs + """ + display_name = "Goal" + option_bowser = 0 + option_yoshi_egg_hunt = 1 + default = 0 + + +class BossesRequired(Range): + """ + How many Bosses (Koopalings or Reznor) must be defeated in order to defeat Bowser + """ + display_name = "Bosses Required" + range_start = 0 + range_end = 11 + default = 7 + + +class NumberOfYoshiEggs(Range): + """ + How many Yoshi Eggs are in the pool for Yoshi Egg Hunt + """ + display_name = "Total Number of Yoshi Eggs" + range_start = 1 + range_end = 80 + default = 50 + + +class PercentageOfYoshiEggs(Range): + """ + What Percentage of Yoshi Eggs are required to finish Yoshi Egg Hunt + """ + display_name = "Required Percentage of Yoshi Eggs" + range_start = 1 + range_end = 100 + default = 100 + + +class DragonCoinChecks(Toggle): + """ + Whether collecting 5 Dragon Coins in each level will grant a check + """ + display_name = "Dragon Coin Checks" + + +class BowserCastleDoors(Choice): + """ + How the doors of Bowser's Castle behave + Vanilla: Front and Back Doors behave as vanilla + Fast: Both doors behave as the Back Door + Slow: Both doors behave as the Front Door + "Front Door" requires beating all 8 Rooms + "Back Door" only requires going through the dark hallway to Bowser + """ + display_name = "Bowser Castle Doors" + option_vanilla = 0 + option_fast = 1 + option_slow = 2 + default = 0 + + +class LevelShuffle(Toggle): + """ + Whether levels are shuffled + """ + display_name = "Level Shuffle" + + +class SwapDonutGhostHouseExits(Toggle): + """ + If enabled, this option will swap which overworld direction the two exits of the level at the Donut Ghost House overworld tile go: + False: Normal Exit goes up, Secret Exit goes right. + True: Normal Exit goes right, Secret Exit goes up. + """ + display_name = "Swap Donut GH Exits" + + +class DisplaySentItemPopups(Choice): + """ + What messages to display in-game for items sent + """ + display_name = "Display Sent Item Popups" + option_none = 0 + option_all = 1 + default = 1 + + +class DisplayReceivedItemPopups(Choice): + """ + What messages to display in-game for items received + """ + display_name = "Display Received Item Popups" + option_none = 0 + option_all = 1 + option_progression = 2 + default = 2 + + +class TrapFillPercentage(Range): + """ + Replace a percentage of junk items in the item pool with random traps + """ + display_name = "Trap Fill Percentage" + range_start = 0 + range_end = 100 + default = 0 + + +class BaseTrapWeight(Choice): + """ + Base Class for Trap Weights + """ + option_none = 0 + option_low = 1 + option_medium = 2 + option_high = 4 + default = 2 + + +class IceTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes the level to become slippery + """ + display_name = "Ice Trap Weight" + + +class StunTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which briefly stuns Mario + """ + display_name = "Stun Trap Weight" + + +class LiteratureTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes the player to read literature + """ + display_name = "Literature Trap Weight" + + +class Autosave(DefaultOnToggle): + """ + Whether a save prompt will appear after every level + """ + display_name = "Autosave" + + +class MusicShuffle(Choice): + """ + Music shuffle type + None: No Music is shuffled + Consistent: Each music track is consistently shuffled throughout the game + Full: Each individual level has a random music track + Singularity: The entire game uses one song for overworld and one song for levels + """ + display_name = "Music Shuffle" + option_none = 0 + option_consistent = 1 + option_full = 2 + option_singularity = 3 + default = 0 + + +class MarioPalette(Choice): + """ + Mario palette color + """ + display_name = "Mario Palette" + option_mario = 0 + option_luigi = 1 + option_wario = 2 + option_waluigi = 3 + option_geno = 4 + option_princess = 5 + option_dark = 6 + option_sponge = 7 + default = 0 + + +class ForegroundPaletteShuffle(Toggle): + """ + Whether to shuffle level foreground palettes + """ + display_name = "Foreground Palette Shuffle" + + +class BackgroundPaletteShuffle(Toggle): + """ + Whether to shuffle level background palettes + """ + display_name = "Background Palette Shuffle" + + +class StartingLifeCount(Range): + """ + How many extra lives to start the game with + """ + display_name = "Starting Life Count" + range_start = 1 + range_end = 99 + default = 5 + + + +smw_options: typing.Dict[str, type(Option)] = { + "death_link": DeathLink, + "goal": Goal, + "bosses_required": BossesRequired, + "number_of_yoshi_eggs": NumberOfYoshiEggs, + "percentage_of_yoshi_eggs": PercentageOfYoshiEggs, + "dragon_coin_checks": DragonCoinChecks, + "bowser_castle_doors": BowserCastleDoors, + "level_shuffle": LevelShuffle, + "swap_donut_gh_exits": SwapDonutGhostHouseExits, + #"display_sent_item_popups": DisplaySentItemPopups, + "display_received_item_popups": DisplayReceivedItemPopups, + "trap_fill_percentage": TrapFillPercentage, + "ice_trap_weight": IceTrapWeight, + "stun_trap_weight": StunTrapWeight, + "literature_trap_weight": LiteratureTrapWeight, + "autosave": Autosave, + "music_shuffle": MusicShuffle, + "mario_palette": MarioPalette, + "foreground_palette_shuffle": ForegroundPaletteShuffle, + "background_palette_shuffle": BackgroundPaletteShuffle, + "starting_life_count": StartingLifeCount, +} diff --git a/worlds/smw/Regions.py b/worlds/smw/Regions.py new file mode 100644 index 0000000000..7af7b888c9 --- /dev/null +++ b/worlds/smw/Regions.py @@ -0,0 +1,1187 @@ +import typing + +from BaseClasses import MultiWorld, Region, RegionType, Entrance +from .Locations import SMWLocation +from .Levels import level_info_dict +from .Names import LocationName, ItemName +from ..generic.Rules import add_rule, set_rule + + +def create_regions(world, player: int, active_locations): + menu_region = create_region(world, player, active_locations, 'Menu', None) + + yoshis_island_region = create_region(world, player, active_locations, LocationName.yoshis_island_region, None) + donut_plains_region = create_region(world, player, active_locations, LocationName.donut_plains_region, None) + vanilla_dome_region = create_region(world, player, active_locations, LocationName.vanilla_dome_region, None) + twin_bridges_region = create_region(world, player, active_locations, LocationName.twin_bridges_region, None) + forest_of_illusion_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_region, None) + chocolate_island_region = create_region(world, player, active_locations, LocationName.chocolate_island_region, None) + valley_of_bowser_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_region, None) + star_road_region = create_region(world, player, active_locations, LocationName.star_road_region, None) + special_zone_region = create_region(world, player, active_locations, LocationName.special_zone_region, None) + + + yoshis_house_tile = create_region(world, player, active_locations, LocationName.yoshis_house_tile, None) + + yoshis_house_region_locations = [] + if world.goal[player] == "yoshi_egg_hunt": + yoshis_house_region_locations.append(LocationName.yoshis_house) + yoshis_house_region = create_region(world, player, active_locations, LocationName.yoshis_house, + yoshis_house_region_locations) + + yoshis_island_1_tile = create_region(world, player, active_locations, LocationName.yoshis_island_1_tile, None) + yoshis_island_1_region = create_region(world, player, active_locations, LocationName.yoshis_island_1_region, None) + yoshis_island_1_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_1_exit_1, + [LocationName.yoshis_island_1_exit_1]) + + yoshis_island_2_tile = create_region(world, player, active_locations, LocationName.yoshis_island_2_tile, None) + yoshis_island_2_region = create_region(world, player, active_locations, LocationName.yoshis_island_2_region, None) + yoshis_island_2_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_2_exit_1, + [LocationName.yoshis_island_2_exit_1]) + + yoshis_island_3_tile = create_region(world, player, active_locations, LocationName.yoshis_island_3_tile, None) + yoshis_island_3_region = create_region(world, player, active_locations, LocationName.yoshis_island_3_region, None) + yoshis_island_3_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_3_exit_1, + [LocationName.yoshis_island_3_exit_1]) + + yoshis_island_4_tile = create_region(world, player, active_locations, LocationName.yoshis_island_4_tile, None) + yoshis_island_4_region = create_region(world, player, active_locations, LocationName.yoshis_island_4_region, None) + yoshis_island_4_exit_1 = create_region(world, player, active_locations, LocationName.yoshis_island_4_exit_1, + [LocationName.yoshis_island_4_exit_1]) + + yoshis_island_castle_tile = create_region(world, player, active_locations, LocationName.yoshis_island_castle_tile, None) + yoshis_island_castle_region = create_region(world, player, active_locations, LocationName.yoshis_island_castle_region, None) + yoshis_island_castle = create_region(world, player, active_locations, LocationName.yoshis_island_castle, + [LocationName.yoshis_island_castle, LocationName.yoshis_island_koopaling]) + + yellow_switch_palace_tile = create_region(world, player, active_locations, LocationName.yellow_switch_palace_tile, None) + yellow_switch_palace = create_region(world, player, active_locations, LocationName.yellow_switch_palace, + [LocationName.yellow_switch_palace]) + + + donut_plains_1_tile = create_region(world, player, active_locations, LocationName.donut_plains_1_tile, None) + donut_plains_1_region = create_region(world, player, active_locations, LocationName.donut_plains_1_region, None) + donut_plains_1_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_1_exit_1, + [LocationName.donut_plains_1_exit_1]) + donut_plains_1_exit_2 = create_region(world, player, active_locations, LocationName.donut_plains_1_exit_2, + [LocationName.donut_plains_1_exit_2]) + + donut_plains_2_tile = create_region(world, player, active_locations, LocationName.donut_plains_2_tile, None) + donut_plains_2_region = create_region(world, player, active_locations, LocationName.donut_plains_2_region, None) + donut_plains_2_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_2_exit_1, + [LocationName.donut_plains_2_exit_1]) + donut_plains_2_exit_2 = create_region(world, player, active_locations, LocationName.donut_plains_2_exit_2, + [LocationName.donut_plains_2_exit_2]) + + donut_plains_3_tile = create_region(world, player, active_locations, LocationName.donut_plains_3_tile, None) + donut_plains_3_region = create_region(world, player, active_locations, LocationName.donut_plains_3_region, None) + donut_plains_3_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_3_exit_1, + [LocationName.donut_plains_3_exit_1]) + + donut_plains_4_tile = create_region(world, player, active_locations, LocationName.donut_plains_4_tile, None) + donut_plains_4_region = create_region(world, player, active_locations, LocationName.donut_plains_4_region, None) + donut_plains_4_exit_1 = create_region(world, player, active_locations, LocationName.donut_plains_4_exit_1, + [LocationName.donut_plains_4_exit_1]) + + donut_secret_1_tile = create_region(world, player, active_locations, LocationName.donut_secret_1_tile, None) + donut_secret_1_region = create_region(world, player, active_locations, LocationName.donut_secret_1_region, None) + donut_secret_1_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_1_exit_1, + [LocationName.donut_secret_1_exit_1]) + donut_secret_1_exit_2 = create_region(world, player, active_locations, LocationName.donut_secret_1_exit_2, + [LocationName.donut_secret_1_exit_2]) + + donut_secret_2_tile = create_region(world, player, active_locations, LocationName.donut_secret_2_tile, None) + donut_secret_2_region = create_region(world, player, active_locations, LocationName.donut_secret_2_region, None) + donut_secret_2_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_2_exit_1, + [LocationName.donut_secret_2_exit_1]) + + donut_ghost_house_tile = create_region(world, player, active_locations, LocationName.donut_ghost_house_tile, None) + donut_ghost_house_region = create_region(world, player, active_locations, LocationName.donut_ghost_house_region, None) + donut_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.donut_ghost_house_exit_1, + [LocationName.donut_ghost_house_exit_1]) + donut_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.donut_ghost_house_exit_2, + [LocationName.donut_ghost_house_exit_2]) + + donut_secret_house_tile = create_region(world, player, active_locations, LocationName.donut_secret_house_tile, None) + donut_secret_house_region = create_region(world, player, active_locations, LocationName.donut_secret_house_region, None) + donut_secret_house_exit_1 = create_region(world, player, active_locations, LocationName.donut_secret_house_exit_1, + [LocationName.donut_secret_house_exit_1]) + donut_secret_house_exit_2 = create_region(world, player, active_locations, LocationName.donut_secret_house_exit_2, + [LocationName.donut_secret_house_exit_2]) + + donut_plains_castle_tile = create_region(world, player, active_locations, LocationName.donut_plains_castle_tile, None) + donut_plains_castle_region = create_region(world, player, active_locations, LocationName.donut_plains_castle_region, None) + donut_plains_castle = create_region(world, player, active_locations, LocationName.donut_plains_castle, + [LocationName.donut_plains_castle, LocationName.donut_plains_koopaling]) + + green_switch_palace_tile = create_region(world, player, active_locations, LocationName.green_switch_palace_tile, None) + green_switch_palace = create_region(world, player, active_locations, LocationName.green_switch_palace, + [LocationName.green_switch_palace]) + + donut_plains_top_secret_tile = create_region(world, player, active_locations, LocationName.donut_plains_top_secret_tile, None) + donut_plains_top_secret = create_region(world, player, active_locations, LocationName.donut_plains_top_secret, None) + + + vanilla_dome_1_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_1_tile, None) + vanilla_dome_1_region = create_region(world, player, active_locations, LocationName.vanilla_dome_1_region, None) + vanilla_dome_1_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_1_exit_1, + [LocationName.vanilla_dome_1_exit_1]) + vanilla_dome_1_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_dome_1_exit_2, + [LocationName.vanilla_dome_1_exit_2]) + + vanilla_dome_2_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_2_tile, None) + vanilla_dome_2_region = create_region(world, player, active_locations, LocationName.vanilla_dome_2_region, None) + vanilla_dome_2_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_2_exit_1, + [LocationName.vanilla_dome_2_exit_1]) + vanilla_dome_2_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_dome_2_exit_2, + [LocationName.vanilla_dome_2_exit_2]) + + vanilla_dome_3_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_3_tile, None) + vanilla_dome_3_region = create_region(world, player, active_locations, LocationName.vanilla_dome_3_region, None) + vanilla_dome_3_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_3_exit_1, + [LocationName.vanilla_dome_3_exit_1]) + + vanilla_dome_4_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_4_tile, None) + vanilla_dome_4_region = create_region(world, player, active_locations, LocationName.vanilla_dome_4_region, None) + vanilla_dome_4_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_dome_4_exit_1, + [LocationName.vanilla_dome_4_exit_1]) + + vanilla_secret_1_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_1_tile, None) + vanilla_secret_1_region = create_region(world, player, active_locations, LocationName.vanilla_secret_1_region, None) + vanilla_secret_1_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_1_exit_1, + [LocationName.vanilla_secret_1_exit_1]) + vanilla_secret_1_exit_2 = create_region(world, player, active_locations, LocationName.vanilla_secret_1_exit_2, + [LocationName.vanilla_secret_1_exit_2]) + + vanilla_secret_2_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_2_tile, None) + vanilla_secret_2_region = create_region(world, player, active_locations, LocationName.vanilla_secret_2_region, None) + vanilla_secret_2_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_2_exit_1, + [LocationName.vanilla_secret_2_exit_1]) + + vanilla_secret_3_tile = create_region(world, player, active_locations, LocationName.vanilla_secret_3_tile, None) + vanilla_secret_3_region = create_region(world, player, active_locations, LocationName.vanilla_secret_3_region, None) + vanilla_secret_3_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_secret_3_exit_1, + [LocationName.vanilla_secret_3_exit_1]) + + vanilla_ghost_house_tile = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_tile, None) + vanilla_ghost_house_region = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_region, None) + vanilla_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.vanilla_ghost_house_exit_1, + [LocationName.vanilla_ghost_house_exit_1]) + + vanilla_fortress_tile = create_region(world, player, active_locations, LocationName.vanilla_fortress_tile, None) + vanilla_fortress_region = create_region(world, player, active_locations, LocationName.vanilla_fortress_region, None) + vanilla_fortress = create_region(world, player, active_locations, LocationName.vanilla_fortress, + [LocationName.vanilla_fortress, LocationName.vanilla_reznor]) + + vanilla_dome_castle_tile = create_region(world, player, active_locations, LocationName.vanilla_dome_castle_tile, None) + vanilla_dome_castle_region = create_region(world, player, active_locations, LocationName.vanilla_dome_castle_region, None) + vanilla_dome_castle = create_region(world, player, active_locations, LocationName.vanilla_dome_castle, + [LocationName.vanilla_dome_castle, LocationName.vanilla_dome_koopaling]) + + red_switch_palace_tile = create_region(world, player, active_locations, LocationName.red_switch_palace_tile, None) + red_switch_palace = create_region(world, player, active_locations, LocationName.red_switch_palace, + [LocationName.red_switch_palace]) + + + butter_bridge_1_tile = create_region(world, player, active_locations, LocationName.butter_bridge_1_tile, None) + butter_bridge_1_region = create_region(world, player, active_locations, LocationName.butter_bridge_1_region, None) + butter_bridge_1_exit_1 = create_region(world, player, active_locations, LocationName.butter_bridge_1_exit_1, + [LocationName.butter_bridge_1_exit_1]) + + butter_bridge_2_tile = create_region(world, player, active_locations, LocationName.butter_bridge_2_tile, None) + butter_bridge_2_region = create_region(world, player, active_locations, LocationName.butter_bridge_2_region, None) + butter_bridge_2_exit_1 = create_region(world, player, active_locations, LocationName.butter_bridge_2_exit_1, + [LocationName.butter_bridge_2_exit_1]) + + cheese_bridge_tile = create_region(world, player, active_locations, LocationName.cheese_bridge_tile, None) + cheese_bridge_region = create_region(world, player, active_locations, LocationName.cheese_bridge_region, None) + cheese_bridge_exit_1 = create_region(world, player, active_locations, LocationName.cheese_bridge_exit_1, + [LocationName.cheese_bridge_exit_1]) + cheese_bridge_exit_2 = create_region(world, player, active_locations, LocationName.cheese_bridge_exit_2, + [LocationName.cheese_bridge_exit_2]) + + cookie_mountain_tile = create_region(world, player, active_locations, LocationName.cookie_mountain_tile, None) + cookie_mountain_region = create_region(world, player, active_locations, LocationName.cookie_mountain_region, None) + cookie_mountain_exit_1 = create_region(world, player, active_locations, LocationName.cookie_mountain_exit_1, + [LocationName.cookie_mountain_exit_1]) + + soda_lake_tile = create_region(world, player, active_locations, LocationName.soda_lake_tile, None) + soda_lake_region = create_region(world, player, active_locations, LocationName.soda_lake_region, None) + soda_lake_exit_1 = create_region(world, player, active_locations, LocationName.soda_lake_exit_1, + [LocationName.soda_lake_exit_1]) + + twin_bridges_castle_tile = create_region(world, player, active_locations, LocationName.twin_bridges_castle_tile, None) + twin_bridges_castle_region = create_region(world, player, active_locations, LocationName.twin_bridges_castle_region, None) + twin_bridges_castle = create_region(world, player, active_locations, LocationName.twin_bridges_castle, + [LocationName.twin_bridges_castle, LocationName.twin_bridges_koopaling]) + + + forest_of_illusion_1_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_tile, None) + forest_of_illusion_1_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_region, None) + forest_of_illusion_1_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_exit_1, + [LocationName.forest_of_illusion_1_exit_1]) + forest_of_illusion_1_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_1_exit_2, + [LocationName.forest_of_illusion_1_exit_2]) + + forest_of_illusion_2_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_tile, None) + forest_of_illusion_2_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_region, None) + forest_of_illusion_2_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_exit_1, + [LocationName.forest_of_illusion_2_exit_1]) + forest_of_illusion_2_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_2_exit_2, + [LocationName.forest_of_illusion_2_exit_2]) + + forest_of_illusion_3_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_tile, None) + forest_of_illusion_3_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_region, None) + forest_of_illusion_3_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_exit_1, + [LocationName.forest_of_illusion_3_exit_1]) + forest_of_illusion_3_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_3_exit_2, + [LocationName.forest_of_illusion_3_exit_2]) + + forest_of_illusion_4_tile = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_tile, None) + forest_of_illusion_4_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_region, None) + forest_of_illusion_4_exit_1 = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_exit_1, + [LocationName.forest_of_illusion_4_exit_1]) + forest_of_illusion_4_exit_2 = create_region(world, player, active_locations, LocationName.forest_of_illusion_4_exit_2, + [LocationName.forest_of_illusion_4_exit_2]) + + forest_ghost_house_tile = create_region(world, player, active_locations, LocationName.forest_ghost_house_tile, None) + forest_ghost_house_region = create_region(world, player, active_locations, LocationName.forest_ghost_house_region, None) + forest_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.forest_ghost_house_exit_1, + [LocationName.forest_ghost_house_exit_1]) + forest_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.forest_ghost_house_exit_2, + [LocationName.forest_ghost_house_exit_2]) + + forest_secret_tile = create_region(world, player, active_locations, LocationName.forest_secret_tile, None) + forest_secret_region = create_region(world, player, active_locations, LocationName.forest_secret_region, None) + forest_secret_exit_1 = create_region(world, player, active_locations, LocationName.forest_secret_exit_1, + [LocationName.forest_secret_exit_1]) + + forest_fortress_tile = create_region(world, player, active_locations, LocationName.forest_fortress_tile, None) + forest_fortress_region = create_region(world, player, active_locations, LocationName.forest_fortress_region, None) + forest_fortress = create_region(world, player, active_locations, LocationName.forest_fortress, + [LocationName.forest_fortress, LocationName.forest_reznor]) + + forest_castle_tile = create_region(world, player, active_locations, LocationName.forest_castle_tile, None) + forest_castle_region = create_region(world, player, active_locations, LocationName.forest_castle_region, None) + forest_castle = create_region(world, player, active_locations, LocationName.forest_castle, + [LocationName.forest_castle, LocationName.forest_koopaling]) + + blue_switch_palace_tile = create_region(world, player, active_locations, LocationName.blue_switch_palace_tile, None) + blue_switch_palace = create_region(world, player, active_locations, LocationName.blue_switch_palace, + [LocationName.blue_switch_palace]) + + + chocolate_island_1_tile = create_region(world, player, active_locations, LocationName.chocolate_island_1_tile, None) + chocolate_island_1_region = create_region(world, player, active_locations, LocationName.chocolate_island_1_region, None) + chocolate_island_1_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_1_exit_1, + [LocationName.chocolate_island_1_exit_1]) + + chocolate_island_2_tile = create_region(world, player, active_locations, LocationName.chocolate_island_2_tile, None) + chocolate_island_2_region = create_region(world, player, active_locations, LocationName.chocolate_island_2_region, None) + chocolate_island_2_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_2_exit_1, + [LocationName.chocolate_island_2_exit_1]) + chocolate_island_2_exit_2 = create_region(world, player, active_locations, LocationName.chocolate_island_2_exit_2, + [LocationName.chocolate_island_2_exit_2]) + + chocolate_island_3_tile = create_region(world, player, active_locations, LocationName.chocolate_island_3_tile, None) + chocolate_island_3_region = create_region(world, player, active_locations, LocationName.chocolate_island_3_region, None) + chocolate_island_3_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_3_exit_1, + [LocationName.chocolate_island_3_exit_1]) + chocolate_island_3_exit_2 = create_region(world, player, active_locations, LocationName.chocolate_island_3_exit_2, + [LocationName.chocolate_island_3_exit_2]) + + chocolate_island_4_tile = create_region(world, player, active_locations, LocationName.chocolate_island_4_tile, None) + chocolate_island_4_region = create_region(world, player, active_locations, LocationName.chocolate_island_4_region, None) + chocolate_island_4_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_4_exit_1, + [LocationName.chocolate_island_4_exit_1]) + + chocolate_island_5_tile = create_region(world, player, active_locations, LocationName.chocolate_island_5_tile, None) + chocolate_island_5_region = create_region(world, player, active_locations, LocationName.chocolate_island_5_region, None) + chocolate_island_5_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_island_5_exit_1, + [LocationName.chocolate_island_5_exit_1]) + + chocolate_ghost_house_tile = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_tile, None) + chocolate_ghost_house_region = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_region, None) + chocolate_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_ghost_house_exit_1, + [LocationName.chocolate_ghost_house_exit_1]) + + chocolate_secret_tile = create_region(world, player, active_locations, LocationName.chocolate_secret_tile, None) + chocolate_secret_region = create_region(world, player, active_locations, LocationName.chocolate_secret_region, None) + chocolate_secret_exit_1 = create_region(world, player, active_locations, LocationName.chocolate_secret_exit_1, + [LocationName.chocolate_secret_exit_1]) + + chocolate_fortress_tile = create_region(world, player, active_locations, LocationName.chocolate_fortress_tile, None) + chocolate_fortress_region = create_region(world, player, active_locations, LocationName.chocolate_fortress_region, None) + chocolate_fortress = create_region(world, player, active_locations, LocationName.chocolate_fortress, + [LocationName.chocolate_fortress, LocationName.chocolate_reznor]) + + chocolate_castle_tile = create_region(world, player, active_locations, LocationName.chocolate_castle_tile, None) + chocolate_castle_region = create_region(world, player, active_locations, LocationName.chocolate_castle_region, None) + chocolate_castle = create_region(world, player, active_locations, LocationName.chocolate_castle, + [LocationName.chocolate_castle, LocationName.chocolate_koopaling]) + + sunken_ghost_ship_tile = create_region(world, player, active_locations, LocationName.sunken_ghost_ship_tile, None) + sunken_ghost_ship_region = create_region(world, player, active_locations, LocationName.sunken_ghost_ship_region, None) + sunken_ghost_ship = create_region(world, player, active_locations, LocationName.sunken_ghost_ship, + [LocationName.sunken_ghost_ship]) + + + valley_of_bowser_1_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_tile, None) + valley_of_bowser_1_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_region, None) + valley_of_bowser_1_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_1_exit_1, + [LocationName.valley_of_bowser_1_exit_1]) + + valley_of_bowser_2_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_tile, None) + valley_of_bowser_2_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_region, None) + valley_of_bowser_2_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_exit_1, + [LocationName.valley_of_bowser_2_exit_1]) + valley_of_bowser_2_exit_2 = create_region(world, player, active_locations, LocationName.valley_of_bowser_2_exit_2, + [LocationName.valley_of_bowser_2_exit_2]) + + valley_of_bowser_3_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_tile, None) + valley_of_bowser_3_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_region, None) + valley_of_bowser_3_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_3_exit_1, + [LocationName.valley_of_bowser_3_exit_1]) + + valley_of_bowser_4_tile = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_tile, None) + valley_of_bowser_4_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_region, None) + valley_of_bowser_4_exit_1 = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_exit_1, + [LocationName.valley_of_bowser_4_exit_1]) + valley_of_bowser_4_exit_2 = create_region(world, player, active_locations, LocationName.valley_of_bowser_4_exit_2, + [LocationName.valley_of_bowser_4_exit_2]) + + valley_ghost_house_tile = create_region(world, player, active_locations, LocationName.valley_ghost_house_tile, None) + valley_ghost_house_region = create_region(world, player, active_locations, LocationName.valley_ghost_house_region, None) + valley_ghost_house_exit_1 = create_region(world, player, active_locations, LocationName.valley_ghost_house_exit_1, + [LocationName.valley_ghost_house_exit_1]) + valley_ghost_house_exit_2 = create_region(world, player, active_locations, LocationName.valley_ghost_house_exit_2, + [LocationName.valley_ghost_house_exit_2]) + + valley_fortress_tile = create_region(world, player, active_locations, LocationName.valley_fortress_tile, None) + valley_fortress_region = create_region(world, player, active_locations, LocationName.valley_fortress_region, None) + valley_fortress = create_region(world, player, active_locations, LocationName.valley_fortress, + [LocationName.valley_fortress, LocationName.valley_reznor]) + + valley_castle_tile = create_region(world, player, active_locations, LocationName.valley_castle_tile, None) + valley_castle_region = create_region(world, player, active_locations, LocationName.valley_castle_region, None) + valley_castle = create_region(world, player, active_locations, LocationName.valley_castle, + [LocationName.valley_castle, LocationName.valley_koopaling]) + + front_door_tile = create_region(world, player, active_locations, LocationName.front_door_tile, None) + front_door_region = create_region(world, player, active_locations, LocationName.front_door, None) + back_door_tile = create_region(world, player, active_locations, LocationName.back_door_tile, None) + back_door_region = create_region(world, player, active_locations, LocationName.back_door, None) + bowser_region_locations = [] + if world.goal[player] == "bowser": + bowser_region_locations += [LocationName.bowser] + bowser_region = create_region(world, player, active_locations, LocationName.bowser_region, bowser_region_locations) + + + donut_plains_star_road = create_region(world, player, active_locations, LocationName.donut_plains_star_road, None) + vanilla_dome_star_road = create_region(world, player, active_locations, LocationName.vanilla_dome_star_road, None) + twin_bridges_star_road = create_region(world, player, active_locations, LocationName.twin_bridges_star_road, None) + forest_star_road = create_region(world, player, active_locations, LocationName.forest_star_road, None) + valley_star_road = create_region(world, player, active_locations, LocationName.valley_star_road, None) + star_road_donut = create_region(world, player, active_locations, LocationName.star_road_donut, None) + star_road_vanilla = create_region(world, player, active_locations, LocationName.star_road_vanilla, None) + star_road_twin_bridges = create_region(world, player, active_locations, LocationName.star_road_twin_bridges, None) + star_road_forest = create_region(world, player, active_locations, LocationName.star_road_forest, None) + star_road_valley = create_region(world, player, active_locations, LocationName.star_road_valley, None) + star_road_special = create_region(world, player, active_locations, LocationName.star_road_special, None) + special_star_road = create_region(world, player, active_locations, LocationName.special_star_road, None) + + star_road_1_tile = create_region(world, player, active_locations, LocationName.star_road_1_tile, None) + star_road_1_region = create_region(world, player, active_locations, LocationName.star_road_1_region, None) + star_road_1_exit_1 = create_region(world, player, active_locations, LocationName.star_road_1_exit_1, + [LocationName.star_road_1_exit_1]) + star_road_1_exit_2 = create_region(world, player, active_locations, LocationName.star_road_1_exit_2, + [LocationName.star_road_1_exit_2]) + + star_road_2_tile = create_region(world, player, active_locations, LocationName.star_road_2_tile, None) + star_road_2_region = create_region(world, player, active_locations, LocationName.star_road_2_region, None) + star_road_2_exit_1 = create_region(world, player, active_locations, LocationName.star_road_2_exit_1, + [LocationName.star_road_2_exit_1]) + star_road_2_exit_2 = create_region(world, player, active_locations, LocationName.star_road_2_exit_2, + [LocationName.star_road_2_exit_2]) + + star_road_3_tile = create_region(world, player, active_locations, LocationName.star_road_3_tile, None) + star_road_3_region = create_region(world, player, active_locations, LocationName.star_road_3_region, None) + star_road_3_exit_1 = create_region(world, player, active_locations, LocationName.star_road_3_exit_1, + [LocationName.star_road_3_exit_1]) + star_road_3_exit_2 = create_region(world, player, active_locations, LocationName.star_road_3_exit_2, + [LocationName.star_road_3_exit_2]) + + star_road_4_tile = create_region(world, player, active_locations, LocationName.star_road_4_tile, None) + star_road_4_region = create_region(world, player, active_locations, LocationName.star_road_4_region, None) + star_road_4_exit_1 = create_region(world, player, active_locations, LocationName.star_road_4_exit_1, + [LocationName.star_road_4_exit_1]) + star_road_4_exit_2 = create_region(world, player, active_locations, LocationName.star_road_4_exit_2, + [LocationName.star_road_4_exit_2]) + + star_road_5_tile = create_region(world, player, active_locations, LocationName.star_road_5_tile, None) + star_road_5_region = create_region(world, player, active_locations, LocationName.star_road_5_region, None) + star_road_5_exit_1 = create_region(world, player, active_locations, LocationName.star_road_5_exit_1, + [LocationName.star_road_5_exit_1]) + star_road_5_exit_2 = create_region(world, player, active_locations, LocationName.star_road_5_exit_2, + [LocationName.star_road_5_exit_2]) + + + special_zone_1_tile = create_region(world, player, active_locations, LocationName.special_zone_1_tile, None) + special_zone_1_region = create_region(world, player, active_locations, LocationName.special_zone_1_region, None) + special_zone_1_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_1_exit_1, + [LocationName.special_zone_1_exit_1]) + + special_zone_2_tile = create_region(world, player, active_locations, LocationName.special_zone_2_tile, None) + special_zone_2_region = create_region(world, player, active_locations, LocationName.special_zone_2_region, None) + special_zone_2_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_2_exit_1, + [LocationName.special_zone_2_exit_1]) + + special_zone_3_tile = create_region(world, player, active_locations, LocationName.special_zone_3_tile, None) + special_zone_3_region = create_region(world, player, active_locations, LocationName.special_zone_3_region, None) + special_zone_3_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_3_exit_1, + [LocationName.special_zone_3_exit_1]) + + special_zone_4_tile = create_region(world, player, active_locations, LocationName.special_zone_4_tile, None) + special_zone_4_region = create_region(world, player, active_locations, LocationName.special_zone_4_region, None) + special_zone_4_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_4_exit_1, + [LocationName.special_zone_4_exit_1]) + + special_zone_5_tile = create_region(world, player, active_locations, LocationName.special_zone_5_tile, None) + special_zone_5_region = create_region(world, player, active_locations, LocationName.special_zone_5_region, None) + special_zone_5_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_5_exit_1, + [LocationName.special_zone_5_exit_1]) + + special_zone_6_tile = create_region(world, player, active_locations, LocationName.special_zone_6_tile, None) + special_zone_6_region = create_region(world, player, active_locations, LocationName.special_zone_6_region, None) + special_zone_6_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_6_exit_1, + [LocationName.special_zone_6_exit_1]) + + special_zone_7_tile = create_region(world, player, active_locations, LocationName.special_zone_7_tile, None) + special_zone_7_region = create_region(world, player, active_locations, LocationName.special_zone_7_region, None) + special_zone_7_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_7_exit_1, + [LocationName.special_zone_7_exit_1]) + + special_zone_8_tile = create_region(world, player, active_locations, LocationName.special_zone_8_tile, None) + special_zone_8_region = create_region(world, player, active_locations, LocationName.special_zone_8_region, None) + special_zone_8_exit_1 = create_region(world, player, active_locations, LocationName.special_zone_8_exit_1, + [LocationName.special_zone_8_exit_1]) + special_complete = create_region(world, player, active_locations, LocationName.special_complete, None) + + + # Set up the regions correctly. + world.regions += [ + menu_region, + yoshis_island_region, + donut_plains_region, + vanilla_dome_region, + twin_bridges_region, + forest_of_illusion_region, + chocolate_island_region, + valley_of_bowser_region, + star_road_region, + special_zone_region, + yoshis_house_tile, + yoshis_house_region, + yoshis_island_1_tile, + yoshis_island_1_region, + yoshis_island_1_exit_1, + yoshis_island_2_tile, + yoshis_island_2_region, + yoshis_island_2_exit_1, + yoshis_island_3_tile, + yoshis_island_3_region, + yoshis_island_3_exit_1, + yoshis_island_4_tile, + yoshis_island_4_region, + yoshis_island_4_exit_1, + yoshis_island_castle_tile, + yoshis_island_castle_region, + yoshis_island_castle, + yellow_switch_palace_tile, + yellow_switch_palace, + donut_plains_1_tile, + donut_plains_1_region, + donut_plains_1_exit_1, + donut_plains_1_exit_2, + donut_plains_2_tile, + donut_plains_2_region, + donut_plains_2_exit_1, + donut_plains_2_exit_2, + donut_plains_3_tile, + donut_plains_3_region, + donut_plains_3_exit_1, + donut_plains_4_tile, + donut_plains_4_region, + donut_plains_4_exit_1, + donut_secret_1_tile, + donut_secret_1_region, + donut_secret_1_exit_1, + donut_secret_1_exit_2, + donut_secret_2_tile, + donut_secret_2_region, + donut_secret_2_exit_1, + donut_ghost_house_tile, + donut_ghost_house_region, + donut_ghost_house_exit_1, + donut_ghost_house_exit_2, + donut_secret_house_tile, + donut_secret_house_region, + donut_secret_house_exit_1, + donut_secret_house_exit_2, + donut_plains_castle_tile, + donut_plains_castle_region, + donut_plains_castle, + green_switch_palace_tile, + green_switch_palace, + donut_plains_top_secret_tile, + donut_plains_top_secret, + vanilla_dome_1_tile, + vanilla_dome_1_region, + vanilla_dome_1_exit_1, + vanilla_dome_1_exit_2, + vanilla_dome_2_tile, + vanilla_dome_2_region, + vanilla_dome_2_exit_1, + vanilla_dome_2_exit_2, + vanilla_dome_3_tile, + vanilla_dome_3_region, + vanilla_dome_3_exit_1, + vanilla_dome_4_tile, + vanilla_dome_4_region, + vanilla_dome_4_exit_1, + vanilla_secret_1_tile, + vanilla_secret_1_region, + vanilla_secret_1_exit_1, + vanilla_secret_1_exit_2, + vanilla_secret_2_tile, + vanilla_secret_2_region, + vanilla_secret_2_exit_1, + vanilla_secret_3_tile, + vanilla_secret_3_region, + vanilla_secret_3_exit_1, + vanilla_ghost_house_tile, + vanilla_ghost_house_region, + vanilla_ghost_house_exit_1, + vanilla_fortress_tile, + vanilla_fortress_region, + vanilla_fortress, + vanilla_dome_castle_tile, + vanilla_dome_castle_region, + vanilla_dome_castle, + red_switch_palace_tile, + red_switch_palace, + butter_bridge_1_tile, + butter_bridge_1_region, + butter_bridge_1_exit_1, + butter_bridge_2_tile, + butter_bridge_2_region, + butter_bridge_2_exit_1, + cheese_bridge_tile, + cheese_bridge_region, + cheese_bridge_exit_1, + cheese_bridge_exit_2, + cookie_mountain_tile, + cookie_mountain_region, + cookie_mountain_exit_1, + soda_lake_tile, + soda_lake_region, + soda_lake_exit_1, + twin_bridges_castle_tile, + twin_bridges_castle_region, + twin_bridges_castle, + forest_of_illusion_1_tile, + forest_of_illusion_1_region, + forest_of_illusion_1_exit_1, + forest_of_illusion_1_exit_2, + forest_of_illusion_2_tile, + forest_of_illusion_2_region, + forest_of_illusion_2_exit_1, + forest_of_illusion_2_exit_2, + forest_of_illusion_3_tile, + forest_of_illusion_3_region, + forest_of_illusion_3_exit_1, + forest_of_illusion_3_exit_2, + forest_of_illusion_4_tile, + forest_of_illusion_4_region, + forest_of_illusion_4_exit_1, + forest_of_illusion_4_exit_2, + forest_ghost_house_tile, + forest_ghost_house_region, + forest_ghost_house_exit_1, + forest_ghost_house_exit_2, + forest_secret_tile, + forest_secret_region, + forest_secret_exit_1, + forest_fortress_tile, + forest_fortress_region, + forest_fortress, + forest_castle_tile, + forest_castle_region, + forest_castle, + blue_switch_palace_tile, + blue_switch_palace, + chocolate_island_1_tile, + chocolate_island_1_region, + chocolate_island_1_exit_1, + chocolate_island_2_tile, + chocolate_island_2_region, + chocolate_island_2_exit_1, + chocolate_island_2_exit_2, + chocolate_island_3_tile, + chocolate_island_3_region, + chocolate_island_3_exit_1, + chocolate_island_3_exit_2, + chocolate_island_4_tile, + chocolate_island_4_region, + chocolate_island_4_exit_1, + chocolate_island_5_tile, + chocolate_island_5_region, + chocolate_island_5_exit_1, + chocolate_ghost_house_tile, + chocolate_ghost_house_region, + chocolate_ghost_house_exit_1, + chocolate_secret_tile, + chocolate_secret_region, + chocolate_secret_exit_1, + chocolate_fortress_tile, + chocolate_fortress_region, + chocolate_fortress, + chocolate_castle_tile, + chocolate_castle_region, + chocolate_castle, + sunken_ghost_ship_tile, + sunken_ghost_ship_region, + sunken_ghost_ship, + valley_of_bowser_1_tile, + valley_of_bowser_1_region, + valley_of_bowser_1_exit_1, + valley_of_bowser_2_tile, + valley_of_bowser_2_region, + valley_of_bowser_2_exit_1, + valley_of_bowser_2_exit_2, + valley_of_bowser_3_tile, + valley_of_bowser_3_region, + valley_of_bowser_3_exit_1, + valley_of_bowser_4_tile, + valley_of_bowser_4_region, + valley_of_bowser_4_exit_1, + valley_of_bowser_4_exit_2, + valley_ghost_house_tile, + valley_ghost_house_region, + valley_ghost_house_exit_1, + valley_ghost_house_exit_2, + valley_fortress_tile, + valley_fortress_region, + valley_fortress, + valley_castle_tile, + valley_castle_region, + valley_castle, + front_door_tile, + front_door_region, + back_door_tile, + back_door_region, + bowser_region, + donut_plains_star_road, + vanilla_dome_star_road, + twin_bridges_star_road, + forest_star_road, + valley_star_road, + star_road_donut, + star_road_vanilla, + star_road_twin_bridges, + star_road_forest, + star_road_valley, + star_road_special, + special_star_road, + star_road_1_tile, + star_road_1_region, + star_road_1_exit_1, + star_road_1_exit_2, + star_road_2_tile, + star_road_2_region, + star_road_2_exit_1, + star_road_2_exit_2, + star_road_3_tile, + star_road_3_region, + star_road_3_exit_1, + star_road_3_exit_2, + star_road_4_tile, + star_road_4_region, + star_road_4_exit_1, + star_road_4_exit_2, + star_road_5_tile, + star_road_5_region, + star_road_5_exit_1, + star_road_5_exit_2, + special_zone_1_tile, + special_zone_1_region, + special_zone_1_exit_1, + special_zone_2_tile, + special_zone_2_region, + special_zone_2_exit_1, + special_zone_3_tile, + special_zone_3_region, + special_zone_3_exit_1, + special_zone_4_tile, + special_zone_4_region, + special_zone_4_exit_1, + special_zone_5_tile, + special_zone_5_region, + special_zone_5_exit_1, + special_zone_6_tile, + special_zone_6_region, + special_zone_6_exit_1, + special_zone_7_tile, + special_zone_7_region, + special_zone_7_exit_1, + special_zone_8_tile, + special_zone_8_region, + special_zone_8_exit_1, + special_complete, + ] + + + if world.dragon_coin_checks[player]: + add_location_to_region(world, player, active_locations, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_dragon, + lambda state: (state.has(ItemName.mario_spin_jump, player) and + state.has(ItemName.progressive_powerup, player, 1))) + add_location_to_region(world, player, active_locations, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_climb, player))) + add_location_to_region(world, player, active_locations, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_dragon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(world, player, active_locations, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_swim, player) or + (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player)))) + add_location_to_region(world, player, active_locations, LocationName.donut_plains_1_region, LocationName.donut_plains_1_dragon, + lambda state: (state.has(ItemName.mario_climb, player) or + state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.progressive_powerup, player, 3) and state.has(ItemName.mario_run, player)))) + add_location_to_region(world, player, active_locations, LocationName.donut_plains_2_region, LocationName.donut_plains_2_dragon) + add_location_to_region(world, player, active_locations, LocationName.donut_plains_3_region, LocationName.donut_plains_3_dragon, + lambda state: ((state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.progressive_powerup, player, 1) and state.has(ItemName.mario_climb, player) or + state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + add_location_to_region(world, player, active_locations, LocationName.donut_plains_4_region, LocationName.donut_plains_4_dragon) + add_location_to_region(world, player, active_locations, LocationName.donut_secret_1_region, LocationName.donut_secret_1_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.donut_secret_2_region, LocationName.donut_secret_2_dragon, + lambda state: (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_dragon, + lambda state: (state.has(ItemName.mario_carry, player) and + state.has(ItemName.mario_run, player) and + (state.has(ItemName.super_star_active, player) or + state.has(ItemName.progressive_powerup, player, 1)))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_dragon, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.p_switch, player) and + (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_dragon) + add_location_to_region(world, player, active_locations, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_dragon) + add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_dragon, + lambda state: (state.has(ItemName.mario_climb, player) and + state.has(ItemName.mario_carry, player))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_dragon, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(world, player, active_locations, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_dragon, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(world, player, active_locations, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_dragon) + add_location_to_region(world, player, active_locations, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(world, player, active_locations, LocationName.cheese_bridge_region, LocationName.cheese_bridge_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_climb, player))) + add_location_to_region(world, player, active_locations, LocationName.cookie_mountain_region, LocationName.cookie_mountain_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_climb, player))) + add_location_to_region(world, player, active_locations, LocationName.soda_lake_region, LocationName.soda_lake_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_carry, player))) + add_location_to_region(world, player, active_locations, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_dragon, + lambda state: (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.mario_carry, player) or + state.has(ItemName.p_switch, player) or + state.has(ItemName.progressive_powerup, player, 2))) + add_location_to_region(world, player, active_locations, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_dragon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(world, player, active_locations, LocationName.forest_secret_region, LocationName.forest_secret_dragon) + add_location_to_region(world, player, active_locations, LocationName.forest_castle_region, LocationName.forest_castle_dragon) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_dragon, + lambda state: (state.has(ItemName.blue_switch_palace, player) and + (state.has(ItemName.p_switch, player) or + state.has(ItemName.green_switch_palace, player) or + (state.has(ItemName.yellow_switch_palace, player) or state.has(ItemName.red_switch_palace, player))))) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_dragon) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_dragon, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(world, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_dragon, + lambda state: (state.has(ItemName.mario_swim, player) or + (state.has(ItemName.mario_carry, player) and state.has(ItemName.p_switch, player)))) + add_location_to_region(world, player, active_locations, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship_dragon, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.super_star_active, player) and + state.has(ItemName.progressive_powerup, player, 3))) + add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_dragon) + add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_dragon) + add_location_to_region(world, player, active_locations, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_dragon) + add_location_to_region(world, player, active_locations, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_dragon, + lambda state: state.has(ItemName.p_switch, player)) + add_location_to_region(world, player, active_locations, LocationName.valley_castle_region, LocationName.valley_castle_dragon) + add_location_to_region(world, player, active_locations, LocationName.star_road_1_region, LocationName.star_road_1_dragon, + lambda state: (state.has(ItemName.mario_spin_jump, player) and + state.has(ItemName.progressive_powerup, player, 1))) + add_location_to_region(world, player, active_locations, LocationName.special_zone_1_region, LocationName.special_zone_1_dragon, + lambda state: state.has(ItemName.mario_climb, player)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_2_region, LocationName.special_zone_2_dragon, + lambda state: state.has(ItemName.p_balloon, player)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_3_region, LocationName.special_zone_3_dragon, + lambda state: state.has(ItemName.yoshi_activate, player)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_4_region, LocationName.special_zone_4_dragon, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_5_region, LocationName.special_zone_5_dragon, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_6_region, LocationName.special_zone_6_dragon, + lambda state: state.has(ItemName.mario_swim, player)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_7_region, LocationName.special_zone_7_dragon, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + add_location_to_region(world, player, active_locations, LocationName.special_zone_8_region, LocationName.special_zone_8_dragon, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + + + +def connect_regions(world, player, level_to_tile_dict): + names: typing.Dict[str, int] = {} + + connect(world, player, names, "Menu", LocationName.yoshis_island_region) + connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_1_tile) + connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_2_tile) + + # Connect regions within levels using rules + connect(world, player, names, LocationName.yoshis_island_1_region, LocationName.yoshis_island_1_exit_1) + connect(world, player, names, LocationName.yoshis_island_2_region, LocationName.yoshis_island_2_exit_1) + connect(world, player, names, LocationName.yoshis_island_3_region, LocationName.yoshis_island_3_exit_1) + connect(world, player, names, LocationName.yoshis_island_4_region, LocationName.yoshis_island_4_exit_1) + connect(world, player, names, LocationName.yoshis_island_castle_region, LocationName.yoshis_island_castle, + lambda state: (state.has(ItemName.mario_climb, player))) + + connect(world, player, names, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_1) + connect(world, player, names, LocationName.donut_plains_1_region, LocationName.donut_plains_1_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + (state.has(ItemName.yoshi_activate, player) or + state.has(ItemName.green_switch_palace, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + connect(world, player, names, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_1) + connect(world, player, names, LocationName.donut_plains_2_region, LocationName.donut_plains_2_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + (state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.mario_spin_jump, player) and state.has(ItemName.mario_climb, player) and state.has(ItemName.progressive_powerup, player, 1))))) + connect(world, player, names, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.donut_secret_1_region, LocationName.donut_secret_1_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + state.has(ItemName.mario_swim, player) and + state.has(ItemName.p_switch, player))) + connect(world, player, names, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_1, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + connect(world, player, names, LocationName.donut_ghost_house_region, LocationName.donut_ghost_house_exit_2, + lambda state: (state.has(ItemName.mario_climb, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) + connect(world, player, names, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.donut_secret_house_region, LocationName.donut_secret_house_exit_2, + lambda state: (state.has(ItemName.p_switch, player) and state.has(ItemName.mario_carry, player) and + (state.has(ItemName.mario_climb, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + connect(world, player, names, LocationName.donut_plains_3_region, LocationName.donut_plains_3_exit_1) + connect(world, player, names, LocationName.donut_plains_4_region, LocationName.donut_plains_4_exit_1) + connect(world, player, names, LocationName.donut_secret_2_region, LocationName.donut_secret_2_exit_1) + connect(world, player, names, LocationName.donut_plains_castle_region, LocationName.donut_plains_castle) + + connect(world, player, names, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_1, + lambda state: (state.has(ItemName.mario_run, player) and + (state.has(ItemName.super_star_active, player) or + state.has(ItemName.progressive_powerup, player, 1)))) + connect(world, player, names, LocationName.vanilla_dome_1_region, LocationName.vanilla_dome_1_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + ((state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_climb, player)) or + (state.has(ItemName.yoshi_activate, player) and state.has(ItemName.red_switch_palace, player)) or + (state.has(ItemName.red_switch_palace, player) and state.has(ItemName.mario_climb, player))))) + connect(world, player, names, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_1, + lambda state: (state.has(ItemName.mario_swim, player) and + (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) + connect(world, player, names, LocationName.vanilla_dome_2_region, LocationName.vanilla_dome_2_exit_2, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.p_switch, player) and + state.has(ItemName.mario_carry, player) and + (state.has(ItemName.mario_climb, player) or state.has(ItemName.yoshi_activate, player)))) + connect(world, player, names, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_1, + lambda state: state.has(ItemName.mario_climb, player)) + connect(world, player, names, LocationName.vanilla_secret_1_region, LocationName.vanilla_secret_1_exit_2, + lambda state: (state.has(ItemName.mario_climb, player) and + (state.has(ItemName.mario_carry, player) and state.has(ItemName.blue_switch_palace, player)))) + connect(world, player, names, LocationName.vanilla_ghost_house_region, LocationName.vanilla_ghost_house_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.vanilla_dome_3_region, LocationName.vanilla_dome_3_exit_1) + connect(world, player, names, LocationName.vanilla_dome_4_region, LocationName.vanilla_dome_4_exit_1) + connect(world, player, names, LocationName.vanilla_secret_2_region, LocationName.vanilla_secret_2_exit_1) + connect(world, player, names, LocationName.vanilla_secret_3_region, LocationName.vanilla_secret_3_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.vanilla_fortress_region, LocationName.vanilla_fortress, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.vanilla_dome_castle_region, LocationName.vanilla_dome_castle) + + connect(world, player, names, LocationName.butter_bridge_1_region, LocationName.butter_bridge_1_exit_1) + connect(world, player, names, LocationName.butter_bridge_2_region, LocationName.butter_bridge_2_exit_1) + connect(world, player, names, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_1, + lambda state: state.has(ItemName.mario_climb, player)) + connect(world, player, names, LocationName.cheese_bridge_region, LocationName.cheese_bridge_exit_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + connect(world, player, names, LocationName.soda_lake_region, LocationName.soda_lake_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.cookie_mountain_region, LocationName.cookie_mountain_exit_1) + connect(world, player, names, LocationName.twin_bridges_castle_region, LocationName.twin_bridges_castle, + lambda state: (state.has(ItemName.mario_run, player) and + state.has(ItemName.mario_climb, player))) + + connect(world, player, names, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_1) + connect(world, player, names, LocationName.forest_of_illusion_1_region, LocationName.forest_of_illusion_1_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + state.has(ItemName.p_balloon, player))) + connect(world, player, names, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.forest_of_illusion_2_region, LocationName.forest_of_illusion_2_exit_2, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.mario_carry, player))) + connect(world, player, names, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_1, + lambda state: (state.has(ItemName.mario_carry, player) or + state.has(ItemName.yoshi_activate, player))) + connect(world, player, names, LocationName.forest_of_illusion_3_region, LocationName.forest_of_illusion_3_exit_2, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.mario_carry, player) and + state.has(ItemName.progressive_powerup, player, 1))) + connect(world, player, names, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_1) + connect(world, player, names, LocationName.forest_of_illusion_4_region, LocationName.forest_of_illusion_4_exit_2, + lambda state: state.has(ItemName.mario_carry, player)) + connect(world, player, names, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.forest_ghost_house_region, LocationName.forest_ghost_house_exit_2, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.forest_secret_region, LocationName.forest_secret_exit_1) + connect(world, player, names, LocationName.forest_fortress_region, LocationName.forest_fortress) + connect(world, player, names, LocationName.forest_castle_region, LocationName.forest_castle) + + connect(world, player, names, LocationName.chocolate_island_1_region, LocationName.chocolate_island_1_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_1) + connect(world, player, names, LocationName.chocolate_island_2_region, LocationName.chocolate_island_2_exit_2, + lambda state: state.has(ItemName.mario_carry, player)) + connect(world, player, names, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_1, + lambda state: (state.has(ItemName.mario_climb, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) + connect(world, player, names, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_exit_2, + lambda state: (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))) + connect(world, player, names, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_exit_1) + connect(world, player, names, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_exit_1) + connect(world, player, names, LocationName.chocolate_ghost_house_region, LocationName.chocolate_ghost_house_exit_1) + connect(world, player, names, LocationName.chocolate_fortress_region, LocationName.chocolate_fortress) + connect(world, player, names, LocationName.chocolate_secret_region, LocationName.chocolate_secret_exit_1, + lambda state: state.has(ItemName.mario_run, player)) + connect(world, player, names, LocationName.chocolate_castle_region, LocationName.chocolate_castle, + lambda state: (state.has(ItemName.progressive_powerup, player, 1))) + + connect(world, player, names, LocationName.sunken_ghost_ship_region, LocationName.sunken_ghost_ship, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.valley_of_bowser_1_region, LocationName.valley_of_bowser_1_exit_1) + connect(world, player, names, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_1) + connect(world, player, names, LocationName.valley_of_bowser_2_region, LocationName.valley_of_bowser_2_exit_2, + lambda state: state.has(ItemName.mario_carry, player)) + connect(world, player, names, LocationName.valley_of_bowser_3_region, LocationName.valley_of_bowser_3_exit_1) + connect(world, player, names, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_1, + lambda state: state.has(ItemName.mario_climb, player)) + connect(world, player, names, LocationName.valley_of_bowser_4_region, LocationName.valley_of_bowser_4_exit_2, + lambda state: (state.has(ItemName.mario_climb, player) and + state.has(ItemName.mario_carry, player) and + state.has(ItemName.yoshi_activate, player))) + connect(world, player, names, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.valley_ghost_house_region, LocationName.valley_ghost_house_exit_2, + lambda state: (state.has(ItemName.p_switch, player) and + state.has(ItemName.mario_carry, player) and + state.has(ItemName.mario_run, player))) + connect(world, player, names, LocationName.valley_fortress_region, LocationName.valley_fortress, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, player, names, LocationName.valley_castle_region, LocationName.valley_castle) + connect(world, player, names, LocationName.front_door, LocationName.bowser_region, + lambda state: (state.has(ItemName.mario_climb, player) and + state.has(ItemName.mario_run, player) and + state.has(ItemName.mario_swim, player) and + state.has(ItemName.progressive_powerup, player, 1) and + state.has(ItemName.koopaling, player, world.bosses_required[player].value))) + connect(world, player, names, LocationName.back_door, LocationName.bowser_region, + lambda state: state.has(ItemName.koopaling, player, world.bosses_required[player].value)) + + connect(world, player, names, LocationName.star_road_1_region, LocationName.star_road_1_exit_1, + lambda state: (state.has(ItemName.mario_spin_jump, player) and + state.has(ItemName.progressive_powerup, player, 1))) + connect(world, player, names, LocationName.star_road_1_region, LocationName.star_road_1_exit_2, + lambda state: (state.has(ItemName.mario_spin_jump, player) and + state.has(ItemName.mario_carry, player) and + state.has(ItemName.progressive_powerup, player, 1))) + connect(world, player, names, LocationName.star_road_2_region, LocationName.star_road_2_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.star_road_2_region, LocationName.star_road_2_exit_2, + lambda state: (state.has(ItemName.mario_swim, player) and + state.has(ItemName.mario_carry, player))) + connect(world, player, names, LocationName.star_road_3_region, LocationName.star_road_3_exit_1) + connect(world, player, names, LocationName.star_road_3_region, LocationName.star_road_3_exit_2, + lambda state: state.has(ItemName.mario_carry, player)) + connect(world, player, names, LocationName.star_road_4_region, LocationName.star_road_4_exit_1) + connect(world, player, names, LocationName.star_road_4_region, LocationName.star_road_4_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + (state.has(ItemName.yoshi_activate, player) or + (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.red_switch_palace, player))))) + connect(world, player, names, LocationName.star_road_5_region, LocationName.star_road_5_exit_1, + lambda state: state.has(ItemName.p_switch, player)) + connect(world, player, names, LocationName.star_road_5_region, LocationName.star_road_5_exit_2, + lambda state: (state.has(ItemName.mario_carry, player) and + state.has(ItemName.mario_climb, player) and + state.has(ItemName.p_switch, player) and + state.has(ItemName.yellow_switch_palace, player) and + state.has(ItemName.green_switch_palace, player) and + state.has(ItemName.red_switch_palace, player) and + state.has(ItemName.blue_switch_palace, player))) + + connect(world, player, names, LocationName.special_zone_1_region, LocationName.special_zone_1_exit_1, + lambda state: (state.has(ItemName.mario_climb, player) and + (state.has(ItemName.p_switch, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3))))) + connect(world, player, names, LocationName.special_zone_2_region, LocationName.special_zone_2_exit_1, + lambda state: state.has(ItemName.p_balloon, player)) + connect(world, player, names, LocationName.special_zone_3_region, LocationName.special_zone_3_exit_1, + lambda state: (state.has(ItemName.mario_climb, player) or + state.has(ItemName.p_switch, player) or + (state.has(ItemName.mario_run, player) and state.has(ItemName.progressive_powerup, player, 3)))) + connect(world, player, names, LocationName.special_zone_4_region, LocationName.special_zone_4_exit_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, player, names, LocationName.special_zone_5_region, LocationName.special_zone_5_exit_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, player, names, LocationName.special_zone_6_region, LocationName.special_zone_6_exit_1, + lambda state: state.has(ItemName.mario_swim, player)) + connect(world, player, names, LocationName.special_zone_7_region, LocationName.special_zone_7_exit_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + connect(world, player, names, LocationName.special_zone_8_region, LocationName.special_zone_8_exit_1, + lambda state: state.has(ItemName.progressive_powerup, player, 1)) + + + + # Connect levels to each other + for current_level_id, current_level_data in level_info_dict.items(): + # Connect tile regions to correct level regions + + if current_level_id not in level_to_tile_dict.keys(): + continue + + current_tile_id = level_to_tile_dict[current_level_id] + current_tile_data = level_info_dict[current_tile_id] + current_tile_name = current_tile_data.levelName + if ("Star Road - " not in current_tile_name) and (" - Star Road" not in current_tile_name): + current_tile_name += " - Tile" + connect(world, player, names, current_tile_name, current_level_data.levelName) + # Connect Exit regions to next tile regions + if current_tile_data.exit1Path: + next_tile_id = current_tile_data.exit1Path.otherLevelID + if world.swap_donut_gh_exits[player] and current_tile_id == 0x04: + next_tile_id = current_tile_data.exit2Path.otherLevelID + next_tile_name = level_info_dict[next_tile_id].levelName + if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): + next_tile_name += " - Tile" + current_exit_name = (current_level_data.levelName + " - Normal Exit") + connect(world, player, names, current_exit_name, next_tile_name) + if current_tile_data.exit2Path: + next_tile_id = current_tile_data.exit2Path.otherLevelID + if world.swap_donut_gh_exits[player] and current_tile_id == 0x04: + next_tile_id = current_tile_data.exit1Path.otherLevelID + next_tile_name = level_info_dict[next_tile_id].levelName + if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): + next_tile_name += " - Tile" + current_exit_name = (current_level_data.levelName + " - Secret Exit") + connect(world, player, names, current_exit_name, next_tile_name) + + connect(world, player, names, LocationName.donut_plains_star_road, LocationName.star_road_donut) + connect(world, player, names, LocationName.star_road_donut, LocationName.donut_plains_star_road) + connect(world, player, names, LocationName.star_road_donut, LocationName.star_road_1_tile) + connect(world, player, names, LocationName.vanilla_dome_star_road, LocationName.star_road_vanilla) + connect(world, player, names, LocationName.star_road_vanilla, LocationName.vanilla_dome_star_road) + connect(world, player, names, LocationName.star_road_vanilla, LocationName.star_road_2_tile) + connect(world, player, names, LocationName.twin_bridges_star_road, LocationName.star_road_twin_bridges) + connect(world, player, names, LocationName.star_road_twin_bridges, LocationName.twin_bridges_star_road) + connect(world, player, names, LocationName.star_road_twin_bridges, LocationName.star_road_3_tile) + connect(world, player, names, LocationName.forest_star_road, LocationName.star_road_forest) + connect(world, player, names, LocationName.star_road_forest, LocationName.forest_star_road) + connect(world, player, names, LocationName.star_road_forest, LocationName.star_road_4_tile) + connect(world, player, names, LocationName.valley_star_road, LocationName.star_road_valley) + connect(world, player, names, LocationName.star_road_valley, LocationName.valley_star_road) + connect(world, player, names, LocationName.star_road_valley, LocationName.star_road_5_tile) + connect(world, player, names, LocationName.star_road_special, LocationName.special_star_road) + connect(world, player, names, LocationName.special_star_road, LocationName.star_road_special) + connect(world, player, names, LocationName.special_star_road, LocationName.special_zone_1_tile) + + connect(world, player, names, LocationName.star_road_valley, LocationName.front_door_tile) + + + +def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None): + ret = Region(name, RegionType.Generic, name, player) + ret.world = world + if locations: + for locationName in locations: + loc_id = active_locations.get(locationName, 0) + if loc_id: + location = SMWLocation(player, locationName, loc_id, ret) + ret.locations.append(location) + + return ret + +def add_location_to_region(world: MultiWorld, player: int, active_locations, region_name: str, location_name: str, + rule: typing.Optional[typing.Callable] = None): + region = world.get_region(region_name, player) + loc_id = active_locations.get(location_name, 0) + if loc_id: + location = SMWLocation(player, location_name, loc_id, region) + region.locations.append(location) + if rule: + add_rule(location, rule) + + + +def connect(world: MultiWorld, player: int, used_names: typing.Dict[str, int], source: str, target: str, + rule: typing.Optional[typing.Callable] = None): + source_region = world.get_region(source, player) + target_region = world.get_region(target, player) + + if target not in used_names: + used_names[target] = 1 + name = target + else: + used_names[target] += 1 + name = target + (' ' * used_names[target]) + + connection = Entrance(player, name, source_region) + + if rule: + connection.access_rule = rule + + source_region.exits.append(connection) + connection.connect(target_region) diff --git a/worlds/smw/Rom.py b/worlds/smw/Rom.py new file mode 100644 index 0000000000..34bdd2f0ea --- /dev/null +++ b/worlds/smw/Rom.py @@ -0,0 +1,846 @@ +import Utils +from worlds.Files import APDeltaPatch +from .Aesthetics import generate_shuffled_header_data +from .Levels import level_info_dict +from .Names.TextBox import generate_goal_text, title_text_mapping, generate_text_box + +USHASH = 'cdd3c8c37322978ca8669b34bc89c804' +ROM_PLAYER_LIMIT = 65535 + +import hashlib +import os +import math + + +ability_rom_data = { + 0xBC0003: [[0x1F2C, 0x7]], # Run 0x80 + 0xBC0004: [[0x1F2C, 0x6]], # Carry 0x40 + 0xBC0005: [[0x1F2C, 0x2]], # Swim 0x04 + 0xBC0006: [[0x1F2C, 0x3]], # Spin Jump 0x08 + 0xBC0007: [[0x1F2C, 0x5]], # Climb 0x20 + 0xBC0008: [[0x1F2C, 0x1]], # Yoshi 0x02 + 0xBC0009: [[0x1F2C, 0x4]], # P-Switch 0x10 + #0xBC000A: [[]] + 0xBC000B: [[0x1F2D, 0x3]], # P-Balloon 0x08 + 0xBC000D: [[0x1F2D, 0x4]], # Super Star 0x10 +} + + +item_rom_data = { + 0xBC0001: [0x18E4, 0x1], # 1-Up Mushroom + + 0xBC0002: [0x1F24, 0x1, 0x1F], # Yoshi Egg + 0xBC0012: [0x1F26, 0x1, 0x09], # Boss Token + + 0xBC000E: [0x1F28, 0x1, 0x1C], # Yellow Switch Palace + 0xBC000F: [0x1F27, 0x1, 0x1C], # Green Switch Palace + 0xBC0010: [0x1F2A, 0x1, 0x1C], # Red Switch Palace + 0xBC0011: [0x1F29, 0x1, 0x1C], # Blue Switch Palace + + 0xBC0013: [0x0086, 0x1, 0x0E], # Ice Trap + 0xBC0014: [0x18BD, 0x7F, 0x18], # Stun Trap +} + +music_rom_data = [ + +] + +level_music_ids = [ + +] + + +class SMWDeltaPatch(APDeltaPatch): + hash = USHASH + game = "Super Mario World" + patch_file_ending = ".apsmw" + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + +class LocalRom: + + def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None): + self.name = name + self.hash = hash + self.orig_buffer = None + + with open(file, 'rb') as stream: + self.buffer = Utils.read_snes_rom(stream) + + def read_bit(self, address: int, bit_number: int) -> bool: + bitflag = (1 << bit_number) + return ((self.buffer[address] & bitflag) != 0) + + def read_byte(self, address: int) -> int: + return self.buffer[address] + + def read_bytes(self, startaddress: int, length: int) -> bytes: + return self.buffer[startaddress:startaddress + length] + + def write_byte(self, address: int, value: int): + self.buffer[address] = value + + def write_bytes(self, startaddress: int, values): + self.buffer[startaddress:startaddress + len(values)] = values + + def write_to_file(self, file): + with open(file, 'wb') as outfile: + outfile.write(self.buffer) + + def read_from_file(self, file): + with open(file, 'rb') as stream: + self.buffer = bytearray(stream.read()) + + +def handle_ability_code(rom): + # Lock Abilities + + #rom.write_byte(0xC581, 0x01) # No Stars + #rom.write_byte(0x62E6, 0x01) # No Star Music + #rom.write_byte(0xC300, 0x01) # No P-Balloons + #rom.write_byte(0xC305, 0x01) # No P-Balloons + + # Run + rom.write_bytes(0x5977, bytearray([0x22, 0x10, 0xBA, 0x03])) # JSL $03BA10 + rom.write_bytes(0x597B, bytearray([0xEA] * 0x04)) + + RUN_SUB_ADDR = 0x01BA10 + rom.write_bytes(RUN_SUB_ADDR + 0x00, bytearray([0xDA])) # PHX + rom.write_bytes(RUN_SUB_ADDR + 0x01, bytearray([0x08])) # PHP + rom.write_bytes(RUN_SUB_ADDR + 0x02, bytearray([0x90, 0x03])) # BCC +0x03 + rom.write_bytes(RUN_SUB_ADDR + 0x04, bytearray([0xC8])) # INY + rom.write_bytes(RUN_SUB_ADDR + 0x05, bytearray([0xA9, 0x70])) # LDA #70 + rom.write_bytes(RUN_SUB_ADDR + 0x07, bytearray([0xAA])) # TAX + rom.write_bytes(RUN_SUB_ADDR + 0x08, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(RUN_SUB_ADDR + 0x0B, bytearray([0x89, 0x80])) # BIT #80 + rom.write_bytes(RUN_SUB_ADDR + 0x0D, bytearray([0xF0, 0x04])) # BEQ +0x04 + rom.write_bytes(RUN_SUB_ADDR + 0x0F, bytearray([0x8A])) # TXA + rom.write_bytes(RUN_SUB_ADDR + 0x10, bytearray([0x8D, 0xE4, 0x13])) # STA $13E4 + rom.write_bytes(RUN_SUB_ADDR + 0x13, bytearray([0x8A])) # TXA + rom.write_bytes(RUN_SUB_ADDR + 0x14, bytearray([0x28])) # PLP + rom.write_bytes(RUN_SUB_ADDR + 0x15, bytearray([0xFA])) # PLX + rom.write_bytes(RUN_SUB_ADDR + 0x16, bytearray([0x6B])) # RTL + # End Run + + # Purple Block Carry + rom.write_bytes(0x726F, bytearray([0x22, 0x28, 0xBA, 0x03])) # JSL $03BA28 + rom.write_bytes(0x7273, bytearray([0xEA] * 0x02)) + + PURPLE_BLOCK_CARRY_SUB_ADDR = 0x01BA28 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x04, bytearray([0x89, 0x40])) # BIT #40 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x09, bytearray([0xAD, 0x8F, 0x14])) # LDA $148F + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x0C, bytearray([0x0D, 0x7A, 0x18])) # ORA $187A + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x0F, bytearray([0x80, 0x03])) # BRA +0x03 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x12, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(PURPLE_BLOCK_CARRY_SUB_ADDR + 0x14, bytearray([0x6B])) # RTL + # End Purple Block Carry + + # Springboard Carry + rom.write_bytes(0xE6DA, bytearray([0x22, 0x40, 0xBA, 0x03])) # JSL $03BA40 + rom.write_bytes(0xE6DE, bytearray([0xEA] * 0x04)) + + SPRINGBOARD_CARRY_SUB_ADDR = 0x01BA40 + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x00, bytearray([0x48])) # PHA + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x01, bytearray([0x08])) # PHP + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x05, bytearray([0x89, 0x40])) # BIT #40 + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x07, bytearray([0xF0, 0x08])) # BEQ +0x08 + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x09, bytearray([0xA9, 0x0B])) # LDA #0B + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x0B, bytearray([0x9D, 0xC8, 0x14])) # STA $14C8, X + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x0E, bytearray([0x9E, 0x02, 0x16])) # STZ $1602, X + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x12, bytearray([0x68])) # PLA + rom.write_bytes(SPRINGBOARD_CARRY_SUB_ADDR + 0x13, bytearray([0x6B])) # RTL + # End Springboard Carry + + # Shell Carry + rom.write_bytes(0xAA66, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(0xAA69, bytearray([0x89, 0x40])) # BIT #40 + rom.write_bytes(0xAA6B, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(0xAA6D, bytearray([0x22, 0x60, 0xBA, 0x03])) # JSL $03BA60 + rom.write_bytes(0xAA71, bytearray([0xEA] * 0x02)) + + SHELL_CARRY_SUB_ADDR = 0x01BA60 + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x01, bytearray([0xA9, 0x0B])) # LDA #0B + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x03, bytearray([0x9D, 0xC8, 0x14])) # STA $14C8, X + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x06, bytearray([0xEE, 0x70, 0x14])) # INC $1470 + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x09, bytearray([0xA9, 0x0B])) # LDA #08 + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x0B, bytearray([0x8D, 0x98, 0x14])) # STA $1498 + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x0E, bytearray([0x28])) # PLP + rom.write_bytes(SHELL_CARRY_SUB_ADDR + 0x0F, bytearray([0x6B])) # RTL + # End Shell Carry + + # Yoshi Carry + rom.write_bytes(0xF309, bytearray([0x22, 0x70, 0xBA, 0x03])) # JSL $03BA70 + rom.write_bytes(0xF30D, bytearray([0xEA] * 0x06)) + + YOSHI_CARRY_SUB_ADDR = 0x01BA70 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x04, bytearray([0x89, 0x40])) # BIT #40 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x06, bytearray([0xF0, 0x0A])) # BEQ +0x0A + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x08, bytearray([0xA9, 0x12])) # LDA #12 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x0A, bytearray([0x8D, 0xA3, 0x14])) # STA $14A3 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x0D, bytearray([0xA9, 0x21])) # LDA #21 + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x0F, bytearray([0x8D, 0xFC, 0x1D])) # STA $1DFC + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x12, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_CARRY_SUB_ADDR + 0x13, bytearray([0x6B])) # RTL + # End Yoshi Carry + + # Climb + rom.write_bytes(0x4D72, bytearray([0x5C, 0x88, 0xBA, 0x03])) # JML $03BA88 + rom.write_bytes(0x4D76, bytearray([0xEA] * 0x03)) + + CLIMB_SUB_ADDR = 0x01BA88 + rom.write_bytes(CLIMB_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(CLIMB_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(CLIMB_SUB_ADDR + 0x04, bytearray([0x89, 0x20])) # BIT #20 + rom.write_bytes(CLIMB_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 + rom.write_bytes(CLIMB_SUB_ADDR + 0x08, bytearray([0xA5, 0x8B])) # LDA $8B + rom.write_bytes(CLIMB_SUB_ADDR + 0x0A, bytearray([0x85, 0x74])) # STA $74 + rom.write_bytes(CLIMB_SUB_ADDR + 0x0C, bytearray([0x28])) # PLP + rom.write_bytes(CLIMB_SUB_ADDR + 0x0D, bytearray([0x5C, 0x17, 0xDB, 0x00])) # JML $00DB17 + rom.write_bytes(CLIMB_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(CLIMB_SUB_ADDR + 0x12, bytearray([0x5C, 0x76, 0xCD, 0x00])) # JML $00CD76 + # End Climb + + # P-Switch + rom.write_bytes(0xAB1A, bytearray([0x22, 0xA0, 0xBA, 0x03])) # JSL $03BAA0 + rom.write_bytes(0xAB1E, bytearray([0xEA] * 0x01)) + + P_SWITCH_SUB_ADDR = 0x01BAA0 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x04, bytearray([0x89, 0x10])) # BIT #10 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x06, bytearray([0xF0, 0x04])) # BEQ +0x04 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x08, bytearray([0xA9, 0xB0])) # LDA #B0 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x0A, bytearray([0x80, 0x02])) # BRA +0x02 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x0C, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x0E, bytearray([0x99, 0xAD, 0x14])) # STA $14AD + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(P_SWITCH_SUB_ADDR + 0x12, bytearray([0x6B])) # RTL + # End P-Switch + + # Spin Jump + rom.write_bytes(0x5645, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(0x5648, bytearray([0x89, 0x08])) # BIT #08 + rom.write_bytes(0x564A, bytearray([0xF0, 0x12])) # BEQ +0x12 + rom.write_bytes(0x564C, bytearray([0x22, 0xB8, 0xBA, 0x03])) # JSL $03BAB8 + + SPIN_JUMP_SUB_ADDR = 0x01BAB8 + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x01, bytearray([0x1A])) # INC + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x02, bytearray([0x8D, 0x0D, 0x14])) # STA $140D + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x05, bytearray([0xA9, 0x04])) # LDA #04 + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x07, bytearray([0x8D, 0xFC, 0x1D])) # STA $1DFC + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x0A, bytearray([0xA4, 0x76])) # LDY #76 + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x0C, bytearray([0x28])) # PLP + rom.write_bytes(SPIN_JUMP_SUB_ADDR + 0x0D, bytearray([0x6B])) # RTL + # End Spin Jump + + # Spin Jump from Water + rom.write_bytes(0x6A89, bytearray([0x22, 0xF8, 0xBB, 0x03])) # JSL $03BBF8 + rom.write_bytes(0x6A8D, bytearray([0xEA] * 0x05)) + + SPIN_JUMP_WATER_SUB_ADDR = 0x01BBF8 + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x06, bytearray([0xF0, 0x09])) # BEQ +0x09 + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x08, bytearray([0x1A])) # INC + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x09, bytearray([0x8D, 0x0D, 0x14])) # STA $140D + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x0C, bytearray([0xA9, 0x04])) # LDA #04 + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x0E, bytearray([0x8D, 0xFC, 0x1D])) # STA $1DFC + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x11, bytearray([0x28])) # PLP + rom.write_bytes(SPIN_JUMP_WATER_SUB_ADDR + 0x12, bytearray([0x6B])) # RTL + # End Spin Jump from Water + + # Spin Jump from Springboard + rom.write_bytes(0xE693, bytearray([0x22, 0x0C, 0xBC, 0x03])) # JSL $03BC0C + rom.write_bytes(0xE697, bytearray([0xEA] * 0x01)) + + SPIN_JUMP_SPRING_SUB_ADDR = 0x01BC0C + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x06, bytearray([0xF0, 0x05])) # BEQ +0x05 + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x08, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x0A, bytearray([0x8D, 0x0D, 0x14])) # STA $140D + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x0D, bytearray([0x28])) # PLP + rom.write_bytes(SPIN_JUMP_SPRING_SUB_ADDR + 0x0E, bytearray([0x6B])) # RTL + # End Spin Jump from Springboard + + # Swim + rom.write_bytes(0x5A25, bytearray([0x22, 0xC8, 0xBA, 0x03])) # JSL $03BAC8 + rom.write_bytes(0x5A29, bytearray([0xEA] * 0x04)) + + SWIM_SUB_ADDR = 0x01BAC8 + rom.write_bytes(SWIM_SUB_ADDR + 0x00, bytearray([0x48])) # PHA + rom.write_bytes(SWIM_SUB_ADDR + 0x01, bytearray([0x08])) # PHP + rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SWIM_SUB_ADDR + 0x05, bytearray([0x89, 0x04])) # BIT #04 + rom.write_bytes(SWIM_SUB_ADDR + 0x07, bytearray([0xF0, 0x0C])) # BEQ +0x0C + rom.write_bytes(SWIM_SUB_ADDR + 0x09, bytearray([0x28])) # PLP + rom.write_bytes(SWIM_SUB_ADDR + 0x0A, bytearray([0x68])) # PLA + rom.write_bytes(SWIM_SUB_ADDR + 0x0B, bytearray([0xDD, 0x84, 0xD9])) # CMP $D489, X + rom.write_bytes(SWIM_SUB_ADDR + 0x0E, bytearray([0xB0, 0x03])) # BCS +0x03 + rom.write_bytes(SWIM_SUB_ADDR + 0x10, bytearray([0xBD, 0x84, 0xD9])) # LDA $D489, X + rom.write_bytes(SWIM_SUB_ADDR + 0x13, bytearray([0x80, 0x0A])) # BRA +0x0A + rom.write_bytes(SWIM_SUB_ADDR + 0x15, bytearray([0x28])) # PLP + rom.write_bytes(SWIM_SUB_ADDR + 0x16, bytearray([0x68])) # PLA + rom.write_bytes(SWIM_SUB_ADDR + 0x17, bytearray([0xDD, 0xBE, 0xDE])) # CMP $DEBE, X + rom.write_bytes(SWIM_SUB_ADDR + 0x1A, bytearray([0xB0, 0x03])) # BCS +0x03 + rom.write_bytes(SWIM_SUB_ADDR + 0x1C, bytearray([0xBD, 0xBE, 0xDE])) # LDA $DEBE, X + rom.write_bytes(SWIM_SUB_ADDR + 0x1F, bytearray([0x6B])) # RTL + # End Swim + + # Item Swim + rom.write_bytes(0x59D7, bytearray([0x22, 0xE8, 0xBA, 0x03])) # JSL $03BAE8 + rom.write_bytes(0x59DB, bytearray([0xEA] * 0x02)) + + SWIM_SUB_ADDR = 0x01BAE8 + rom.write_bytes(SWIM_SUB_ADDR + 0x00, bytearray([0x48])) # PHA + rom.write_bytes(SWIM_SUB_ADDR + 0x01, bytearray([0x08])) # PHP + rom.write_bytes(SWIM_SUB_ADDR + 0x02, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(SWIM_SUB_ADDR + 0x05, bytearray([0x89, 0x04])) # BIT #04 + rom.write_bytes(SWIM_SUB_ADDR + 0x07, bytearray([0xF0, 0x0A])) # BEQ +0x0A + rom.write_bytes(SWIM_SUB_ADDR + 0x09, bytearray([0x28])) # PLP + rom.write_bytes(SWIM_SUB_ADDR + 0x0A, bytearray([0x68])) # PLA + rom.write_bytes(SWIM_SUB_ADDR + 0x0B, bytearray([0xC9, 0xF0])) # CMP #F0 + rom.write_bytes(SWIM_SUB_ADDR + 0x0D, bytearray([0xB0, 0x02])) # BCS +0x02 + rom.write_bytes(SWIM_SUB_ADDR + 0x0F, bytearray([0xA9, 0xF0])) # LDA #F0 + rom.write_bytes(SWIM_SUB_ADDR + 0x11, bytearray([0x80, 0x08])) # BRA +0x08 + rom.write_bytes(SWIM_SUB_ADDR + 0x13, bytearray([0x28])) # PLP + rom.write_bytes(SWIM_SUB_ADDR + 0x14, bytearray([0x68])) # PLA + rom.write_bytes(SWIM_SUB_ADDR + 0x15, bytearray([0xC9, 0xFF])) # CMP #FF + rom.write_bytes(SWIM_SUB_ADDR + 0x17, bytearray([0xB0, 0x02])) # BCS +0x02 + rom.write_bytes(SWIM_SUB_ADDR + 0x19, bytearray([0xA9, 0x00])) # LDA #00 + rom.write_bytes(SWIM_SUB_ADDR + 0x1B, bytearray([0x6B])) # RTL + # End Item Swim + + # Yoshi + rom.write_bytes(0x109FB, bytearray([0x22, 0x08, 0xBB, 0x03])) # JSL $03BB08 + rom.write_bytes(0x109FF, bytearray([0xEA] * 0x02)) + + YOSHI_SUB_ADDR = 0x01BB08 + rom.write_bytes(YOSHI_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0x89, 0x02])) # BIT #02 + rom.write_bytes(YOSHI_SUB_ADDR + 0x06, bytearray([0xF0, 0x06])) # BEQ +0x06 + rom.write_bytes(YOSHI_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_SUB_ADDR + 0x09, bytearray([0xB9, 0xA1, 0x88])) # LDA $88A1, Y + rom.write_bytes(YOSHI_SUB_ADDR + 0x0C, bytearray([0x80, 0x04])) # BRA +0x04 + rom.write_bytes(YOSHI_SUB_ADDR + 0x0E, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_SUB_ADDR + 0x0F, bytearray([0xB9, 0xA2, 0x88])) # LDA $88A2, Y + rom.write_bytes(YOSHI_SUB_ADDR + 0x12, bytearray([0x9D, 0x1C, 0x15])) # STA $151C, X + rom.write_bytes(YOSHI_SUB_ADDR + 0x15, bytearray([0x6B])) # RTL + # End Yoshi + + # Baby Yoshi + rom.write_bytes(0xA2B8, bytearray([0x22, 0x20, 0xBB, 0x03])) # JSL $03BB20 + rom.write_bytes(0xA2BC, bytearray([0xEA] * 0x01)) + + YOSHI_SUB_ADDR = 0x01BB20 + rom.write_bytes(YOSHI_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(YOSHI_SUB_ADDR + 0x01, bytearray([0x9C, 0x1E, 0x14])) # STZ $141E + rom.write_bytes(YOSHI_SUB_ADDR + 0x04, bytearray([0xAD, 0x2C, 0x1F])) # LDA $1F2C + rom.write_bytes(YOSHI_SUB_ADDR + 0x07, bytearray([0x89, 0x02])) # BIT #02 + rom.write_bytes(YOSHI_SUB_ADDR + 0x09, bytearray([0xF0, 0x05])) # BEQ +0x05 + rom.write_bytes(YOSHI_SUB_ADDR + 0x0B, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_SUB_ADDR + 0x0C, bytearray([0xA9, 0x35])) # LDA #35 + rom.write_bytes(YOSHI_SUB_ADDR + 0x0E, bytearray([0x80, 0x03])) # BRA +0x03 + rom.write_bytes(YOSHI_SUB_ADDR + 0x10, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_SUB_ADDR + 0x11, bytearray([0xA9, 0x70])) # LDA #70 + rom.write_bytes(YOSHI_SUB_ADDR + 0x13, bytearray([0x6B])) # RTL + # End Baby Yoshi + + # Midway Gate + rom.write_bytes(0x72E4, bytearray([0x22, 0x38, 0xBB, 0x03])) # JSL $03BB38 + + MIDWAY_GATE_SUB_ADDR = 0x01BB38 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x04, bytearray([0x89, 0x01])) # BIT #01 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x09, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x0B, bytearray([0x85, 0x19])) # STA $19 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x0D, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x0F, bytearray([0x28])) # PLP + rom.write_bytes(MIDWAY_GATE_SUB_ADDR + 0x10, bytearray([0x6B])) # RTL + # End Midway Gate + + # Mushroom + rom.write_bytes(0x5156, bytearray([0x22, 0x50, 0xBB, 0x03])) # JSL $03BB50 + rom.write_bytes(0x515A, bytearray([0xEA] * 0x04)) + + MUSHROOM_SUB_ADDR = 0x01BB50 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x04, bytearray([0x89, 0x01])) # BIT #01 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x06, bytearray([0xF0, 0x05])) # BEQ +0x05 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x09, bytearray([0xE6, 0x19])) # INC $19 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x0B, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x0D, bytearray([0x28])) # PLP + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x0E, bytearray([0xA9, 0x00])) # LDA #00 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x10, bytearray([0x85, 0x71])) # STA $72 + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x12, bytearray([0x64, 0x9D])) # STZ $9D + rom.write_bytes(MUSHROOM_SUB_ADDR + 0x14, bytearray([0x6B])) # RTL + # End Mushroom + + # Take Damage + rom.write_bytes(0x5142, bytearray([0x22, 0x65, 0xBB, 0x03])) # JSL $03BB65 + rom.write_bytes(0x5146, bytearray([0x60] * 0x01)) # RTS + + DAMAGE_SUB_ADDR = 0x01BB65 + rom.write_bytes(DAMAGE_SUB_ADDR + 0x00, bytearray([0x8D, 0x97, 0x14])) # STA $1497 + rom.write_bytes(DAMAGE_SUB_ADDR + 0x03, bytearray([0x80, 0xF4])) # BRA -0x0C + # End Take Damage + + # Fire Flower Cycle + rom.write_bytes(0x5187, bytearray([0x22, 0x6A, 0xBB, 0x03])) # JSL $03BB6A + rom.write_bytes(0x518B, bytearray([0x60] * 0x01)) # RTS + + PALETTE_CYCLE_SUB_ADDR = 0x01BB6A + rom.write_bytes(PALETTE_CYCLE_SUB_ADDR + 0x00, bytearray([0xCE, 0x9B, 0x14])) # DEC $149B + rom.write_bytes(PALETTE_CYCLE_SUB_ADDR + 0x03, bytearray([0xF0, 0xEF])) # BEQ -0x11 + rom.write_bytes(PALETTE_CYCLE_SUB_ADDR + 0x05, bytearray([0x6B])) # RTL + # End Fire Flower Cycle + + # Pipe Exit + rom.write_bytes(0x526D, bytearray([0x22, 0x70, 0xBB, 0x03])) # JSL $03BB70 + rom.write_bytes(0x5271, bytearray([0x60, 0xEA] * 0x01)) # RTS, NOP + + PIPE_EXIT_SUB_ADDR = 0x01BB70 + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x00, bytearray([0x9C, 0x19, 0x14])) # STZ $1419 + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x03, bytearray([0xA9, 0x00])) # LDA #00 + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x05, bytearray([0x85, 0x71])) # STA $72 + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x07, bytearray([0x64, 0x9D])) # STZ $9D + rom.write_bytes(PIPE_EXIT_SUB_ADDR + 0x09, bytearray([0x6B])) # RTL + # End Pipe Exit + + # Cape Transform + rom.write_bytes(0x5168, bytearray([0x22, 0x7A, 0xBB, 0x03])) # JSL $03BB7A + rom.write_bytes(0x516C, bytearray([0xEA] * 0x01)) # RTS, NOP + rom.write_bytes(0x516D, bytearray([0xF0, 0xD1])) # BEQ -0x2F + + CAPE_TRANSFORM_SUB_ADDR = 0x01BB7A + rom.write_bytes(CAPE_TRANSFORM_SUB_ADDR + 0x00, bytearray([0xA5, 0x19])) # LDA $19 + rom.write_bytes(CAPE_TRANSFORM_SUB_ADDR + 0x02, bytearray([0x4A])) # LSR + rom.write_bytes(CAPE_TRANSFORM_SUB_ADDR + 0x03, bytearray([0xD0, 0xDF])) # BNE -0x21 + rom.write_bytes(CAPE_TRANSFORM_SUB_ADDR + 0x05, bytearray([0x6B])) # RTL + # End Cape Transform + + # Fire Flower + rom.write_bytes(0xC5F7, bytearray([0x22, 0x80, 0xBB, 0x03])) # JSL $03BB80 + + FIRE_FLOWER_SUB_ADDR = 0x01BB80 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x04, bytearray([0x89, 0x02])) # BIT #02 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x09, bytearray([0xA9, 0x03])) # LDA #03 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x0B, bytearray([0x85, 0x19])) # STA $19 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x0D, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x0F, bytearray([0x28])) # PLP + rom.write_bytes(FIRE_FLOWER_SUB_ADDR + 0x10, bytearray([0x6B])) # RTL + # End Fire Flower + + # Cape + rom.write_bytes(0xC598, bytearray([0x22, 0x91, 0xBB, 0x03])) # JSL $03BB91 + + CAPE_SUB_ADDR = 0x01BB91 + rom.write_bytes(CAPE_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(CAPE_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(CAPE_SUB_ADDR + 0x04, bytearray([0x89, 0x04])) # BIT #04 + rom.write_bytes(CAPE_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(CAPE_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(CAPE_SUB_ADDR + 0x09, bytearray([0xA9, 0x02])) # LDA #02 + rom.write_bytes(CAPE_SUB_ADDR + 0x0B, bytearray([0x85, 0x19])) # STA $19 + rom.write_bytes(CAPE_SUB_ADDR + 0x0D, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(CAPE_SUB_ADDR + 0x0F, bytearray([0x28])) # PLP + rom.write_bytes(CAPE_SUB_ADDR + 0x10, bytearray([0x6B])) # RTL + # End Cape + + # P-Balloon + rom.write_bytes(0xC2FF, bytearray([0x22, 0xA2, 0xBB, 0x03])) # JSL $03BBA2 + rom.write_bytes(0xC303, bytearray([0xEA] * 0x06)) + + P_BALLOON_SUB_ADDR = 0x01BBA2 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x04, bytearray([0x89, 0x08])) # BIT #08 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x06, bytearray([0xF0, 0x0D])) # BEQ +0x0D + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x09, bytearray([0xA9, 0x09])) # LDA #09 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x0B, bytearray([0x8D, 0xF3, 0x13])) # STA $13F3 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x0E, bytearray([0xA9, 0xFF])) # LDA #FF + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x10, bytearray([0x8D, 0x91, 0x18])) # STA $1891 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x13, bytearray([0x80, 0x0B])) # BRA +0x0B + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x15, bytearray([0x28])) # PLP + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x16, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x18, bytearray([0x8D, 0xF3, 0x13])) # STA $13F3 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x1B, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x1D, bytearray([0x8D, 0x91, 0x18])) # STA $1891 + rom.write_bytes(P_BALLOON_SUB_ADDR + 0x20, bytearray([0x6B])) # RTL + # End P-Balloon + + # Star + rom.write_bytes(0xC580, bytearray([0x22, 0xC8, 0xBB, 0x03])) # JSL $03BBC8 + rom.write_bytes(0xC584, bytearray([0xEA] * 0x01)) + + STAR_SUB_ADDR = 0x01BBC8 + rom.write_bytes(STAR_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(STAR_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(STAR_SUB_ADDR + 0x04, bytearray([0x89, 0x10])) # BIT #10 + rom.write_bytes(STAR_SUB_ADDR + 0x06, bytearray([0xF0, 0x08])) # BEQ +0x08 + rom.write_bytes(STAR_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(STAR_SUB_ADDR + 0x09, bytearray([0xA9, 0xFF])) # LDA #FF + rom.write_bytes(STAR_SUB_ADDR + 0x0B, bytearray([0x8D, 0x90, 0x14])) # STA $1490 + rom.write_bytes(STAR_SUB_ADDR + 0x0E, bytearray([0x80, 0x06])) # BRA +0x06 + rom.write_bytes(STAR_SUB_ADDR + 0x10, bytearray([0x28])) # PLP + rom.write_bytes(STAR_SUB_ADDR + 0x11, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(STAR_SUB_ADDR + 0x13, bytearray([0x8D, 0x90, 0x14])) # STA $1490 + rom.write_bytes(STAR_SUB_ADDR + 0x16, bytearray([0x6B])) # RTL + # End Star + + # Star Timer + rom.write_bytes(0x62E3, bytearray([0x22, 0xE0, 0xBB, 0x03])) # JSL $03BBE0 + + STAR_TIMER_SUB_ADDR = 0x01BBE0 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x01, bytearray([0xAD, 0x2D, 0x1F])) # LDA $1F2D + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x04, bytearray([0x89, 0x10])) # BIT #10 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x06, bytearray([0xF0, 0x07])) # BEQ +0x07 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x09, bytearray([0xA5, 0x13])) # LDA $13 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x0B, bytearray([0xC0, 0x1E])) # CPY #1E + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x0D, bytearray([0x80, 0x05])) # BRA +0x05 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x0F, bytearray([0x28])) # PLP + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x10, bytearray([0xA5, 0x13])) # LDA $13 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x12, bytearray([0xC0, 0x01])) # CPY #01 + rom.write_bytes(STAR_TIMER_SUB_ADDR + 0x14, bytearray([0x6B])) # RTL + # End Star Timer + + return + + +def handle_yoshi_box(rom): + + rom.write_bytes(0xEC3D, bytearray([0xEA] * 0x03)) # NOP Lines that cause Yoshi Rescue Box normally + + rom.write_bytes(0x2B20F, bytearray([0x20, 0x60, 0xDC])) # JSR $05DC60 + + YOSHI_BOX_SUB_ADDR = 0x02DC60 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x01, bytearray([0xAD, 0x26, 0x14])) # LDA $1426 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x04, bytearray([0xC9, 0x03])) # CMP #03 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x06, bytearray([0xF0, 0x06])) # BEQ +0x06 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x09, bytearray([0xB9, 0xD9, 0xA5])) # LDA $A5B9, Y + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x0C, bytearray([0x80, 0x08])) # BRA +0x08 + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x0E, bytearray([0x28])) # PLP + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x0F, bytearray([0xDA])) # PHX + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x10, bytearray([0xBB])) # TYX + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x11, bytearray([0xBF, 0x00, 0xC2, 0x7E])) # LDA $7EC200, X + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x15, bytearray([0xFA])) # PLX + rom.write_bytes(YOSHI_BOX_SUB_ADDR + 0x16, bytearray([0x60])) # RTS + + return + + +def handle_bowser_damage(rom): + + rom.write_bytes(0x1A509, bytearray([0x20, 0x50, 0xBC])) # JSR $03BC50 + + BOWSER_BALLS_SUB_ADDR = 0x01BC50 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x01, bytearray([0xAD, 0x48, 0x0F])) # LDA $F48 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x04, bytearray([0xCF, 0xA1, 0xBF, 0x03])) # CMP $03BFA1 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x08, bytearray([0x90, 0x06])) # BCC +0x06 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0A, bytearray([0x28])) # PLP + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0B, bytearray([0xEE, 0xB8, 0x14])) # INC $14B8 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x0E, bytearray([0x80, 0x01])) # BRA +0x01 + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x10, bytearray([0x28])) # PLP + rom.write_bytes(BOWSER_BALLS_SUB_ADDR + 0x11, bytearray([0x60])) # RTS + + return + + +def handle_level_shuffle(rom, active_level_dict): + rom.write_bytes(0x37600, bytearray([0x00] * 0x800)) # Duplicate Level Table + + rom.write_bytes(0x2D89C, bytearray([0x00, 0xF6, 0x06])) # Level Load Pointer + rom.write_bytes(0x20F46, bytearray([0x00, 0xF6, 0x06])) # Mid Gate Pointer + rom.write_bytes(0x20E7B, bytearray([0x00, 0xF6, 0x06])) # Level Name Pointer + rom.write_bytes(0x21543, bytearray([0x00, 0xF6, 0x06])) # Also Level Name Pointer? + rom.write_bytes(0x20F64, bytearray([0x00, 0xF6, 0x06])) # Level Beaten Pointer + + ### Fix Translevel Check + rom.write_bytes(0x2D8AE, bytearray([0x20, 0x00, 0xDD])) # JSR $DD00 + rom.write_bytes(0x2D8B1, bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) # NOP NOP NOP NOP NOP + + rom.write_bytes(0x2D7CB, bytearray([0x20, 0x00, 0xDD])) # JSR $DD00 + rom.write_bytes(0x2D7CE, bytearray([0xEA, 0xEA, 0xEA, 0xEA, 0xEA, 0xEA])) # NOP NOP NOP NOP NOP + + rom.write_bytes(0x2DD00, bytearray([0xDA])) # PHX + rom.write_bytes(0x2DD01, bytearray([0x08])) # PHP + rom.write_bytes(0x2DD02, bytearray([0xE2, 0x30])) # SEP #30 + rom.write_bytes(0x2DD04, bytearray([0xAE, 0xBF, 0x13])) # LDX $13BF + rom.write_bytes(0x2DD07, bytearray([0xE0, 0x25])) # CPX #25 + rom.write_bytes(0x2DD09, bytearray([0x90, 0x04])) # BCC $DD0F + rom.write_bytes(0x2DD0B, bytearray([0xA2, 0x01])) # LDX #01 + rom.write_bytes(0x2DD0D, bytearray([0x80, 0x02])) # BRA $DD11 + rom.write_bytes(0x2DD0F, bytearray([0xA2, 0x00])) # LDX #00 + rom.write_bytes(0x2DD11, bytearray([0x86, 0x0F])) # STX $0F + rom.write_bytes(0x2DD13, bytearray([0x28])) # PLP + rom.write_bytes(0x2DD14, bytearray([0xFA])) # PLX + rom.write_bytes(0x2DD15, bytearray([0x60])) # RTS + ### End Fix Translevel Check + + ### Fix Snake Blocks + rom.write_bytes(0x192FB, bytearray([0x20, 0x1D, 0xBC])) # JSR $03BC1D + + SNAKE_BLOCKS_SUB_ADDR = 0x01BC1D + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x01, bytearray([0xAD, 0xBF, 0x13])) # LDA $13BF + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x04, bytearray([0xC9, 0x20])) # CMP #20 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x06, bytearray([0xF0, 0x05])) # BEQ +0x05 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x08, bytearray([0x28])) # PLP + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x09, bytearray([0xA9, 0x01])) # LDA #01 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x0B, bytearray([0x80, 0x03])) # BRA +0x03 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x0D, bytearray([0x28])) # PLP + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x0E, bytearray([0xA9, 0x00])) # LDA #00 + rom.write_bytes(SNAKE_BLOCKS_SUB_ADDR + 0x10, bytearray([0x60])) # RTS + ### End Fix Snake Blocks + + for level_id, level_data in level_info_dict.items(): + if level_id not in active_level_dict.keys(): + continue + + tile_id = active_level_dict[level_id] + tile_data = level_info_dict[tile_id] + + if level_id > 0x80: + level_id = level_id - 0x50 + + rom.write_byte(tile_data.levelIDAddress, level_id) + rom.write_byte(0x2D608 + level_id, tile_data.eventIDValue) + + for level_id, tile_id in active_level_dict.items(): + rom.write_byte(0x37F70 + level_id, tile_id) + + +def handle_collected_paths(rom): + rom.write_bytes(0x1F5B, bytearray([0x22, 0x30, 0xBC, 0x03])) # JSL $03BC30 + rom.write_bytes(0x1F5F, bytearray([0xEA] * 0x02)) + + COLLECTED_PATHS_SUB_ADDR = 0x01BC30 + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x00, bytearray([0x08])) # PHP + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x01, bytearray([0xAD, 0x00, 0x01])) # LDA $0100 + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x04, bytearray([0xC9, 0x0B])) # CMP #0B + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x06, bytearray([0xD0, 0x04])) # BNE +0x04 + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x08, bytearray([0x22, 0xAD, 0xDA, 0x04])) # JSL $04DAAD + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x0C, bytearray([0x28])) # PLP + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x0D, bytearray([0xEE, 0x00, 0x01])) # INC $0100 + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x10, bytearray([0xAD, 0xAF, 0x0D])) # LDA $0DAF + rom.write_bytes(COLLECTED_PATHS_SUB_ADDR + 0x13, bytearray([0x6B])) # RTL + + +def handle_music_shuffle(rom, world, player): + from .Aesthetics import generate_shuffled_level_music, generate_shuffled_ow_music, level_music_address_data, ow_music_address_data + + shuffled_level_music = generate_shuffled_level_music(world, player) + for i in range(len(shuffled_level_music)): + rom.write_byte(level_music_address_data[i], shuffled_level_music[i]) + + shuffled_ow_music = generate_shuffled_ow_music(world, player) + for i in range(len(shuffled_ow_music)): + for addr in ow_music_address_data[i]: + rom.write_byte(addr, shuffled_ow_music[i]) + + +def handle_mario_palette(rom, world, player): + from .Aesthetics import mario_palettes, fire_mario_palettes, ow_mario_palettes + + chosen_palette = world.mario_palette[player].value + + rom.write_bytes(0x32C8, bytes(mario_palettes[chosen_palette])) + rom.write_bytes(0x32F0, bytes(fire_mario_palettes[chosen_palette])) + rom.write_bytes(0x359C, bytes(ow_mario_palettes[chosen_palette])) + + +def handle_swap_donut_gh_exits(rom): + rom.write_bytes(0x2567C, bytes([0xC0])) + rom.write_bytes(0x25873, bytes([0xA9])) + rom.write_bytes(0x25875, bytes([0x85])) + rom.write_bytes(0x25954, bytes([0x92])) + rom.write_bytes(0x25956, bytes([0x0A])) + rom.write_bytes(0x25E31, bytes([0x00, 0x00, 0xD8, 0x04, 0x24, 0x00, 0x98, 0x04, 0x48, 0x00, 0xD8, 0x03, 0x6C, 0x00, 0x56, 0x03, + 0x90, 0x00, 0x56, 0x03, 0xB4, 0x00, 0x56, 0x03, 0x10, 0x05, 0x18, 0x05, 0x28, 0x09, 0x24, 0x05, + 0x38, 0x0B, 0x14, 0x07, 0xEC, 0x09, 0x12, 0x05, 0xF0, 0x09, 0xD2, 0x04, 0xF4, 0x09, 0x92, 0x04])) + rom.write_bytes(0x26371, bytes([0x32])) + + +def patch_rom(world, rom, player, active_level_dict): + local_random = world.slot_seeds[player] + + goal_text = generate_goal_text(world, player) + + rom.write_bytes(0x2A6E2, goal_text) + rom.write_byte(0x2B1D8, 0x80) + + intro_text = generate_text_box("Bowser has stolen all of Mario's abilities. Can you help Mario travel across Dinosaur land to get them back and save the Princess from him?") + rom.write_bytes(0x2A5D9, intro_text) + + # Force all 8 Bowser's Castle Rooms + rom.write_byte(0x3A680, 0xD4) + rom.write_byte(0x3A684, 0xD4) + rom.write_byte(0x3A688, 0xD4) + rom.write_byte(0x3A68C, 0xD4) + rom.write_byte(0x3A705, 0xD3) + rom.write_byte(0x3A763, 0xD2) + rom.write_byte(0x3A800, 0xD1) + rom.write_byte(0x3A83D, 0xCF) + rom.write_byte(0x3A932, 0xCE) + rom.write_byte(0x3A9E1, 0xCD) + rom.write_byte(0x3AA75, 0xCC) + + # Prevent Title Screen Deaths + rom.write_byte(0x1C6A, 0x80) + + # Title Screen Text + player_name_bytes = bytearray() + player_name = world.get_player_name(player) + for i in range(16): + char = " " + if i < len(player_name): + char = world.get_player_name(player)[i] + upper_char = char.upper() + if upper_char not in title_text_mapping: + for byte in title_text_mapping["."]: + player_name_bytes.append(byte) + else: + for byte in title_text_mapping[upper_char]: + player_name_bytes.append(byte) + + rom.write_bytes(0x2B7F1, player_name_bytes) # MARIO A + rom.write_bytes(0x2B726, player_name_bytes) # MARIO A + + rom.write_bytes(0x2B815, bytearray([0xFC, 0x38] * 0x10)) # MARIO B + rom.write_bytes(0x2B74A, bytearray([0xFC, 0x38] * 0x10)) # MARIO B + rom.write_bytes(0x2B839, bytearray([0x71, 0x31, 0x74, 0x31, 0x2D, 0x31, 0x84, 0x30, + 0x82, 0x30, 0x6F, 0x31, 0x73, 0x31, 0x70, 0x31, + 0x71, 0x31, 0x75, 0x31, 0x83, 0x30, 0xFC, 0x38, + 0xFC, 0x38, 0xFC, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # MARIO C + rom.write_bytes(0x2B76E, bytearray([0xFC, 0x38] * 0x10)) # MARIO C + rom.write_bytes(0x2B79E, bytearray([0xFC, 0x38] * 0x05)) # EMPTY + rom.write_bytes(0x2B7AE, bytearray([0xFC, 0x38] * 0x05)) # EMPTY + rom.write_bytes(0x2B8A8, bytearray([0xFC, 0x38] * 0x0D)) # 2 PLAYER GAME + + rom.write_bytes(0x2B85D, bytearray([0xFC, 0x38] * 0x0A)) # ERASE + + rom.write_bytes(0x2B88E, bytearray([0x2C, 0x31, 0x73, 0x31, 0x75, 0x31, 0x82, 0x30, 0x30, 0x31, 0xFC, 0x38, 0x31, 0x31, 0x73, 0x31, + 0x73, 0x31, 0x7C, 0x30, 0xFC, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # 1 Player Game + + rom.write_bytes(0x2B6D7, bytearray([0xFC, 0x38, 0xFC, 0x38, 0x16, 0x38, 0x18, 0x38, 0x0D, 0x38, 0xFC, 0x38, 0x0B, 0x38, 0x22, 0x38, + 0xFC, 0x38, 0x19, 0x38, 0x18, 0x38, 0x1B, 0x38, 0x22, 0x38, 0x10, 0x38, 0x18, 0x38, 0x17, 0x38, + 0x0E, 0x38, 0xFC, 0x38, 0xFC, 0x38])) # Mod by PoryGone + + # Title Options + rom.write_bytes(0x1E6A, bytearray([0x01])) + rom.write_bytes(0x1E6C, bytearray([0x01])) + rom.write_bytes(0x1E6E, bytearray([0x01])) + + # Always allow Start+Select + rom.write_bytes(0x2267, bytearray([0xEA, 0xEA])) + + # Always bring up save prompt on beating a level + if world.autosave[player]: + rom.write_bytes(0x20F93, bytearray([0x00])) + + # Starting Life Count + rom.write_bytes(0x1E25, bytearray([world.starting_life_count[player].value - 1])) + + # Repurpose Bonus Stars counter for Boss Token or Yoshi Eggs + rom.write_bytes(0x3F1AA, bytearray([0x00] * 0x20)) + rom.write_bytes(0x20F9F, bytearray([0xEA] * 0x3B)) + + # Prevent Switch Palaces setting the Switch Palace flags + rom.write_bytes(0x6EC9A, bytearray([0xEA, 0xEA])) + rom.write_bytes(0x6EB1, bytearray([0xEA, 0xEA])) + rom.write_bytes(0x6EB4, bytearray([0xEA, 0xEA, 0xEA])) + + handle_ability_code(rom) + + handle_yoshi_box(rom) + handle_bowser_damage(rom) + + handle_collected_paths(rom) + + # Handle Level Shuffle + handle_level_shuffle(rom, active_level_dict) + + # Handle Music Shuffle + if world.music_shuffle[player] != "none": + handle_music_shuffle(rom, world, player) + + generate_shuffled_header_data(rom, world, player) + + if world.swap_donut_gh_exits[player]: + handle_swap_donut_gh_exits(rom) + + handle_mario_palette(rom, world, player) + + # Store all relevant option results in ROM + rom.write_byte(0x01BFA0, world.goal[player].value) + rom.write_byte(0x01BFA1, world.bosses_required[player].value) + required_yoshi_eggs = max(math.floor( + world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + rom.write_byte(0x01BFA2, required_yoshi_eggs) + #rom.write_byte(0x01BFA3, world.display_sent_item_popups[player].value) + rom.write_byte(0x01BFA4, world.display_received_item_popups[player].value) + rom.write_byte(0x01BFA5, world.death_link[player].value) + rom.write_byte(0x01BFA6, world.dragon_coin_checks[player].value) + rom.write_byte(0x01BFA7, world.swap_donut_gh_exits[player].value) + + + from Main import __version__ + rom.name = bytearray(f'SMW{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21] + rom.name.extend([0] * (21 - len(rom.name))) + rom.write_bytes(0x7FC0, rom.name) + + +def get_base_rom_bytes(file_name: str = "") -> bytes: + base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + file_name = get_base_rom_path(file_name) + base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if USHASH != basemd5.hexdigest(): + raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. ' + 'Get the correct game and version, then dump it') + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + + +def get_base_rom_path(file_name: str = "") -> str: + options = Utils.get_options() + if not file_name: + file_name = options["smw_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.local_path(file_name) + return file_name diff --git a/worlds/smw/Rules.py b/worlds/smw/Rules.py new file mode 100644 index 0000000000..bf9fedb805 --- /dev/null +++ b/worlds/smw/Rules.py @@ -0,0 +1,20 @@ +import math + +from BaseClasses import MultiWorld +from .Names import LocationName, ItemName +from ..AutoWorld import LogicMixin +from ..generic.Rules import add_rule, set_rule + + +def set_rules(world: MultiWorld, player: int): + + if world.goal[player] == "yoshi_egg_hunt": + required_yoshi_eggs = max(math.floor( + world.number_of_yoshi_eggs[player].value * (world.percentage_of_yoshi_eggs[player].value / 100.0)), 1) + + add_rule(world.get_location(LocationName.yoshis_house, player), + lambda state: state.has(ItemName.yoshi_egg, player, required_yoshi_eggs)) + else: + add_rule(world.get_location(LocationName.bowser, player), lambda state: state.has(ItemName.mario_carry, player)) + + world.completion_condition[player] = lambda state: state.has(ItemName.victory, player) diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py new file mode 100644 index 0000000000..77931b7c72 --- /dev/null +++ b/worlds/smw/__init__.py @@ -0,0 +1,249 @@ +import os +import typing +import math +import threading + +from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from .Items import SMWItem, ItemData, item_table +from .Locations import SMWLocation, all_locations, setup_locations +from .Options import smw_options +from .Regions import create_regions, connect_regions +from .Levels import full_level_list, generate_level_list, location_id_to_level_id +from .Rules import set_rules +from ..generic.Rules import add_rule +from .Names import ItemName, LocationName +from ..AutoWorld import WebWorld, World +from .Rom import LocalRom, patch_rom, get_base_rom_path, SMWDeltaPatch + + +class SMWWeb(WebWorld): + theme = "grass" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Super Mario World randomizer connected to an Archipelago Multiworld.", + "English", + "setup_en.md", + "setup/en", + ["PoryGone"] + ) + + tutorials = [setup_en] + + +class SMWWorld(World): + """ + Super Mario World is an action platforming game. + The Princess has been kidnapped by Bowser again, but Mario has somehow + lost all of his abilities. Can he get them back in time to save the Princess? + """ + game: str = "Super Mario World" + option_definitions = smw_options + topology_present = False + data_version = 1 + required_client_version = (0, 3, 5) + + item_name_to_id = {name: data.code for name, data in item_table.items()} + location_name_to_id = all_locations + + active_level_dict: typing.Dict[int,int] + web = SMWWeb() + + def __init__(self, world: MultiWorld, player: int): + self.rom_name_available_event = threading.Event() + super().__init__(world, player) + + @classmethod + def stage_assert_generate(cls, world): + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + + def _get_slot_data(self): + return { + #"death_link": self.world.death_link[self.player].value, + "active_levels": self.active_level_dict, + } + + def _create_items(self, name: str): + data = item_table[name] + return [self.create_item(name)] * data.quantity + + def fill_slot_data(self) -> dict: + slot_data = self._get_slot_data() + for option_name in smw_options: + option = getattr(self.world, option_name)[self.player] + slot_data[option_name] = option.value + + return slot_data + + def generate_basic(self): + itempool: typing.List[SMWItem] = [] + + self.active_level_dict = dict(zip(generate_level_list(self.world, self.player), full_level_list)) + self.topology_present = self.world.level_shuffle[self.player] + + connect_regions(self.world, self.player, self.active_level_dict) + + # Add Boss Token amount requirements for Worlds + add_rule(self.world.get_region(LocationName.donut_plains_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1)) + add_rule(self.world.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2)) + add_rule(self.world.get_region(LocationName.forest_of_illusion_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 4)) + add_rule(self.world.get_region(LocationName.chocolate_island_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 5)) + add_rule(self.world.get_region(LocationName.valley_of_bowser_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 6)) + + total_required_locations = 96 + if self.world.dragon_coin_checks[self.player]: + total_required_locations += 49 + + itempool += [self.create_item(ItemName.mario_run)] + itempool += [self.create_item(ItemName.mario_carry)] + itempool += [self.create_item(ItemName.mario_swim)] + itempool += [self.create_item(ItemName.mario_spin_jump)] + itempool += [self.create_item(ItemName.mario_climb)] + itempool += [self.create_item(ItemName.yoshi_activate)] + itempool += [self.create_item(ItemName.p_switch)] + itempool += [self.create_item(ItemName.p_balloon)] + itempool += [self.create_item(ItemName.super_star_active)] + itempool += [self.create_item(ItemName.progressive_powerup)] * 3 + itempool += [self.create_item(ItemName.yellow_switch_palace)] + itempool += [self.create_item(ItemName.green_switch_palace)] + itempool += [self.create_item(ItemName.red_switch_palace)] + itempool += [self.create_item(ItemName.blue_switch_palace)] + + if self.world.goal[self.player] == "yoshi_egg_hunt": + itempool += [self.create_item(ItemName.yoshi_egg)] * self.world.number_of_yoshi_eggs[self.player] + self.world.get_location(LocationName.yoshis_house, self.player).place_locked_item(self.create_item(ItemName.victory)) + else: + self.world.get_location(LocationName.bowser, self.player).place_locked_item(self.create_item(ItemName.victory)) + + junk_count = total_required_locations - len(itempool) + trap_weights = [] + trap_weights += ([ItemName.ice_trap] * self.world.ice_trap_weight[self.player].value) + trap_weights += ([ItemName.stun_trap] * self.world.stun_trap_weight[self.player].value) + trap_weights += ([ItemName.literature_trap] * self.world.literature_trap_weight[self.player].value) + trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.world.trap_fill_percentage[self.player].value / 100.0)) + junk_count -= trap_count + + trap_pool = [] + for i in range(trap_count): + trap_item = self.world.random.choice(trap_weights) + trap_pool += [self.create_item(trap_item)] + + itempool += trap_pool + + itempool += [self.create_item(ItemName.one_up_mushroom)] * junk_count + + boss_location_names = [LocationName.yoshis_island_koopaling, LocationName.donut_plains_koopaling, LocationName.vanilla_dome_koopaling, + LocationName.twin_bridges_koopaling, LocationName.forest_koopaling, LocationName.chocolate_koopaling, + LocationName.valley_koopaling, LocationName.vanilla_reznor, LocationName.forest_reznor, LocationName.chocolate_reznor, LocationName.valley_reznor] + + for location_name in boss_location_names: + self.world.get_location(location_name, self.player).place_locked_item(self.create_item(ItemName.koopaling)) + + self.world.itempool += itempool + + + def generate_output(self, output_directory: str): + rompath = "" # if variable is not declared finally clause may fail + try: + world = self.world + player = self.player + + rom = LocalRom(get_base_rom_path()) + patch_rom(self.world, rom, self.player, self.active_level_dict) + + rompath = os.path.join(output_directory, f"{self.world.get_out_file_name_base(self.player)}.sfc") + rom.write_to_file(rompath) + self.rom_name = rom.name + + patch = SMWDeltaPatch(os.path.splitext(rompath)[0]+SMWDeltaPatch.patch_file_ending, player=player, + player_name=world.player_name[player], patched_path=rompath) + patch.write() + except: + raise + finally: + self.rom_name_available_event.set() # make sure threading continues and errors are collected + if os.path.exists(rompath): + os.unlink(rompath) + + def modify_multidata(self, multidata: dict): + import base64 + # wait for self.rom_name to be available. + self.rom_name_available_event.wait() + rom_name = getattr(self, "rom_name", None) + # we skip in case of error, so that the original error in the output thread is the one that gets raised + if rom_name: + new_name = base64.b64encode(bytes(self.rom_name)).decode() + multidata["connect_names"][new_name] = multidata["connect_names"][self.world.player_name[self.player]] + + def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]): + if self.topology_present: + world_names = [ + LocationName.yoshis_island_region, + LocationName.donut_plains_region, + LocationName.vanilla_dome_region, + LocationName.twin_bridges_region, + LocationName.forest_of_illusion_region, + LocationName.chocolate_island_region, + LocationName.valley_of_bowser_region, + LocationName.star_road_region, + LocationName.special_zone_region, + ] + world_cutoffs = [ + 0x07, + 0x13, + 0x1F, + 0x26, + 0x30, + 0x39, + 0x44, + 0x4F, + 0x59 + ] + er_hint_data = {} + for loc_name, level_data in location_id_to_level_id.items(): + level_id = level_data[0] + + if level_id not in self.active_level_dict: + continue + + keys_list = list(self.active_level_dict.keys()) + level_index = keys_list.index(level_id) + for i in range(len(world_cutoffs)): + if level_index >= world_cutoffs[i]: + continue + + if self.world.dragon_coin_checks[self.player].value == 0 and "Dragon Coins" in loc_name: + continue + + location = self.world.get_location(loc_name, self.player) + er_hint_data[location.address] = world_names[i] + break + + hint_data[self.player] = er_hint_data + + def create_regions(self): + location_table = setup_locations(self.world, self.player) + create_regions(self.world, self.player, location_table) + + def create_item(self, name: str, force_non_progression=False) -> Item: + data = item_table[name] + + if force_non_progression: + classification = ItemClassification.filler + elif name == ItemName.yoshi_egg: + classification = ItemClassification.progression_skip_balancing + elif data.progression: + classification = ItemClassification.progression + elif data.trap: + classification = ItemClassification.trap + else: + classification = ItemClassification.filler + + created_item = SMWItem(name, classification, data.code, self.player) + + return created_item + + def set_rules(self): + set_rules(self.world, self.player) diff --git a/worlds/smw/docs/en_Super Mario World.md b/worlds/smw/docs/en_Super Mario World.md new file mode 100644 index 0000000000..87a96e558b --- /dev/null +++ b/worlds/smw/docs/en_Super Mario World.md @@ -0,0 +1,43 @@ +# Super Mario World + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file. + +## What does randomization do to this game? + +Mario's basic abilities are removed, and placed into the item pool as items that any player can find. This includes: +- Carry +- Climb +- Run +- P-Switch +- Swim +- Spin Jump +- Yoshi + +Additionally, the ability to use powerups (Mushrooms, Fire Flowers, Capes, Stars, and P-Balloons) are shuffled into the item pool, as are the Four Switch Palaces. + +## What is the goal of Super Mario World when randomized? + +There are two goals which can be chosen: +- `Bowser`: Reach Bowser's Castle and defeat Bowser, after defeating a certain number of bosses. +- `Yoshi Egg Hunt`: Collect a certain number of Yoshi Eggs, then return to Yoshi's House + +## What items and locations get shuffled? + +Each unique level exit awards a location check. Optionally, collecting five Dragon Coins in each level can also award a location check. +Mario's various abilities and powerups as described above are placed into the item pool. +If the player is playing Yoshi Egg Hunt, a certain number of Yoshi Eggs will be placed into the item pool. +Any additional items that are needed to fill out the item pool with be 1-Up Mushrooms. + +## Which items can be in another player's world? + +Any shuffled item can be in other players' worlds. + +## What does another world's item look like in Super Mario World + +Items do not have an appearance in Super Mario World. + +## When the player receives an item, what happens? + +The player can choose to receive a text box in-game when they receive an item. Regardless of that choice, items will be queued, and granted when the player next enters a level. diff --git a/worlds/smw/docs/setup_en.md b/worlds/smw/docs/setup_en.md new file mode 100644 index 0000000000..178b7392b7 --- /dev/null +++ b/worlds/smw/docs/setup_en.md @@ -0,0 +1,149 @@ +# Super Mario World Randomizer Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Make sure to check the box for `SNI Client - Super Mario World Patch Setup` + + +- Hardware or software capable of loading and playing SNES ROM files + - An emulator capable of connecting to SNI such as: + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), + - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) + - RetroArch 1.10.3 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, + - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other + compatible hardware +- Your legally obtained Super Mario World ROM file, probably named `Super Mario World (USA).sfc` + +## Installation Procedures + +### Windows Setup + +1. During the installation of Archipelago, you will have been asked to install the SNI Client. If you did not do this, + or you are on an older version, you may run the installer again to install the SNI Client. +2. During setup, you will be asked to locate your base ROM file. This is your Super Mario World ROM file. +3. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM + files. + 1. Extract your emulator's folder to your Desktop, or somewhere you will remember. + 2. Right-click on a ROM file and select **Open with...** + 3. Check the box next to **Always use this app to open .sfc files** + 4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC** + 5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you + extracted in step one. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +See the guide on setting up a basic YAML at the Archipelago setup +guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) + +### Where do I get a config file? + +The Player Settings page on the website allows you to configure your personal settings and export a config file from +them. Player settings page: [Super Mario World Player Settings Page](/games/Super%20Mario%20World/player-settings) + +### Verifying your config file + +If you would like to validate your config file to make sure it works, you may do so on the YAML Validator page. YAML +validator page: [YAML Validation page](/mysterycheck) + +## Joining a MultiWorld Game + +### Obtain your patch file and create your ROM + +When you join a multiworld game, you will be asked to provide your config file to whomever is hosting. Once that is done, +the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch +files. Your patch file should have a `.apsmw` extension. + +Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically launch the +client, and will also create your ROM in the same place as your patch file. + +### Connect to the client + +#### With an emulator + +When the client launched automatically, SNI should have also automatically launched in the background. If this is its +first time launching, you may be prompted to allow it to communicate through the Windows Firewall. + +##### snes9x-rr + +1. Load your ROM file if it hasn't already been loaded. +2. Click on the File menu and hover on **Lua Scripting** +3. Click on **New Lua Script Window...** +4. In the new window, click **Browse...** +5. Select the connector lua file included with your client + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. +6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of +the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. + +##### BizHawk + +1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following these + menu options: + `Config --> Cores --> SNES --> BSNES` + Once you have changed the loaded core, you must restart BizHawk. +2. Load your ROM file if it hasn't already been loaded. +3. Click on the Tools menu and click on **Lua Console** +4. Click the button to open a new Lua script. +5. Select the `Connector.lua` file included with your client + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. Please note the most recent versions of BizHawk are 64-bit only. + +##### RetroArch 1.10.3 or newer + +You only have to do these steps once. Note, RetroArch 1.9.x will not work as it is older than 1.10.3. + +1. Enter the RetroArch main menu screen. +2. Go to Settings --> User Interface. Set "Show Advanced Settings" to ON. +3. Go to Settings --> Network. Set "Network Commands" to ON. (It is found below Request Device 16.) Leave the default + Network Command Port at 55355. + +![Screenshot of Network Commands setting](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-en.png) +4. Go to Main Menu --> Online Updater --> Core Downloader. Scroll down and select "Nintendo - SNES / SFC (bsnes-mercury + Performance)". + +When loading a ROM, be sure to select a **bsnes-mercury** core. These are the only cores that allow external tools to +read ROM data. + +#### With hardware + +This guide assumes you have downloaded the correct firmware for your device. If you have not done so already, please do +this now. SD2SNES and FXPak Pro users may download the appropriate firmware on the SD2SNES releases page. SD2SNES +releases page: [SD2SNES Releases Page](https://github.com/RedGuyyyy/sd2snes/releases) + +Other hardware may find helpful information on the usb2snes platforms +page: [usb2snes Supported Platforms Page](http://usb2snes.com/#supported-platforms) + +1. Close your emulator, which may have auto-launched. +2. Power on your device and load the ROM. + +### Connect to the Archipelago Server + +The patch file which launched your client should have automatically connected you to the AP Server. There are a few +reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the +client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it +into the "Server" input field then press enter. + +The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". + +### Play the game + +When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on +successfully joining a multiworld game! + +## Hosting a MultiWorld game + +The recommended way to host a game is to use our hosting service. The process is relatively simple: + +1. Collect config files from your players. +2. Create a zip file containing your players' config files. +3. Upload that zip file to the Generate page above. + - Generate page: [WebHost Seed Generation Page](/generate) +4. Wait a moment while the seed is generated. +5. When the seed is generated, you will be redirected to a "Seed Info" page. +6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, so + they may download their patch files from there. +7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all + players in the game. Any observers may also be given the link to this page. +8. Once all players have joined, you may begin playing. diff --git a/worlds/smz3/Rom.py b/worlds/smz3/Rom.py index a355636fed..3fec151dc6 100644 --- a/worlds/smz3/Rom.py +++ b/worlds/smz3/Rom.py @@ -2,7 +2,8 @@ import hashlib import os import Utils -from Patch import read_rom, APDeltaPatch +from Utils import read_snes_rom +from worlds.Files import APDeltaPatch SMJUHASH = '21f3e98df4780ee1c667b84e57d88675' LTTPJPN10HASH = '03a63945398191337e896e5771f77173' @@ -23,7 +24,7 @@ def get_base_rom_bytes() -> bytes: base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) if not base_rom_bytes: sm_file_name = get_sm_base_rom_path() - sm_base_rom_bytes = bytes(read_rom(open(sm_file_name, "rb"))) + sm_base_rom_bytes = bytes(read_snes_rom(open(sm_file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(sm_base_rom_bytes) @@ -31,7 +32,7 @@ def get_base_rom_bytes() -> bytes: raise Exception('Supplied Base Rom does not match known MD5 for SM Japan+US release. ' 'Get the correct game and version, then dump it') lttp_file_name = get_lttp_base_rom_path() - lttp_base_rom_bytes = bytes(read_rom(open(lttp_file_name, "rb"))) + lttp_base_rom_bytes = bytes(read_snes_rom(open(lttp_file_name, "rb"))) basemd5 = hashlib.md5() basemd5.update(lttp_base_rom_bytes) diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py index b796c2a43c..753fb556ae 100644 --- a/worlds/smz3/__init__.py +++ b/worlds/smz3/__init__.py @@ -426,11 +426,9 @@ class SMZ3World(World): base_combined_rom[addr + offset] = byte offset += 1 - outfilebase = 'AP_' + self.world.seed_name - outfilepname = f'_P{self.player}' - outfilepname += f"_{self.world.get_file_safe_player_name(self.player).replace(' ', '_')}" \ + outfilebase = self.world.get_out_file_name_base(self.player) - filename = os.path.join(output_directory, f'{outfilebase}{outfilepname}.sfc') + filename = os.path.join(output_directory, f"{outfilebase}.sfc") with open(filename, "wb") as binary_file: binary_file.write(base_combined_rom) patch = SMZ3DeltaPatch(os.path.splitext(filename)[0]+SMZ3DeltaPatch.patch_file_ending, player=self.player, diff --git a/worlds/smz3/docs/multiworld_en.md b/worlds/smz3/docs/multiworld_en.md index 7457d6d0a7..735be9d519 100644 --- a/worlds/smz3/docs/multiworld_en.md +++ b/worlds/smz3/docs/multiworld_en.md @@ -8,8 +8,7 @@ `SNI Client - A Link to the Past Patch Setup` - Hardware or software capable of loading and playing SNES ROM files - An emulator capable of connecting to SNI such as: - - snes9x Multitroid - from: [snes9x Multitroid Download](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + - snes9x-rr from: [snes9x rr](https://github.com/gocha/snes9x-rr/releases), - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html), or - RetroArch 1.10.3 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other @@ -79,7 +78,7 @@ client, and will also create your ROM in the same place as your patch file. When the client launched automatically, SNI should have also automatically launched in the background. If this is its first time launching, you may be prompted to allow it to communicate through the Windows Firewall. -##### snes9x Multitroid +##### snes9x-rr 1. Load your ROM file if it hasn't already been loaded. 2. Click on the File menu and hover on **Lua Scripting** diff --git a/worlds/soe/Patch.py b/worlds/soe/Patch.py index 21bdd94220..f6a0a69f55 100644 --- a/worlds/soe/Patch.py +++ b/worlds/soe/Patch.py @@ -2,7 +2,7 @@ import bsdiff4 import yaml from typing import Optional import Utils -from Patch import APDeltaPatch +from worlds.Files import APDeltaPatch import os diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py index a0dc41c3ce..4885fd3179 100644 --- a/worlds/soe/__init__.py +++ b/worlds/soe/__init__.py @@ -333,8 +333,7 @@ class SoEWorld(World): switches.extend(('--available-fragments', str(self.available_fragments), '--required-fragments', str(self.required_fragments))) rom_file = get_base_rom_path() - out_base = output_path(output_directory, f'AP_{self.world.seed_name}_P{self.player}_' - f'{self.world.get_file_safe_player_name(self.player)}') + out_base = output_path(output_directory, self.world.get_out_file_name_base(self.player)) out_file = out_base + '.sfc' placement_file = out_base + '.txt' patch_file = out_base + '.apsoe' diff --git a/worlds/subnautica/docs/setup_en.md b/worlds/subnautica/docs/setup_en.md index 665cb8b336..bd4a92cc7c 100644 --- a/worlds/subnautica/docs/setup_en.md +++ b/worlds/subnautica/docs/setup_en.md @@ -7,37 +7,50 @@ - Archipelago Mod for Subnautica from: [Subnautica Archipelago Mod Releases Page](https://github.com/Berserker66/ArchipelagoSubnauticaModSrc/releases) -## Installation Procedures +## Installation Procedure 1. Install QModManager4 as per its instructions. -2. The folder you installed QModManager4 into will now have a /QMods directory. It might appear after a start of - Subnautica. You can also create this folder yourself. +2. The Subnautica game directory should now contain a `QMods` folder. Unpack the Archipelago Mod into this folder, so that `Subnautica/QMods/Archipelago/` is a valid path. -3. Unpack the Archipelago Mod into this folder, so that Subnautica/QMods/Archipelago/ is a valid path. - -4. Start Subnautica. You should see a Connect Menu in the topleft of your main Menu. +3. Start Subnautica. You should see a connect form with three text boxes in the top left of your main menu. ## Connecting -Using the Connect Menu in Subnautica's Main Menu you enter your connection info to connect to an Archipelago Multiworld. -Menu points: +Use the connect form in Subnautica's main menu to enter your connection information to connect to an Archipelago multiworld. +Connection information consists of: - Host: the full url that you're trying to connect to, such as `archipelago.gg:38281`. - - PlayerName: your name in the multiworld. Can also be called Slot Name and is the name you entered when creating your settings. + - PlayerName: your name in the multiworld. Can also be called "slot name" and is the name you entered when creating your settings. - Password: optional password, leave blank if no password was set. After the connection is made, start a new game. You should start to see Archipelago chat messages to appear, such as a message announcing that you joined the multiworld. ## Resuming -When loading a savegame it will automatically attempt to resume the connection that was active when the savegame was made. -If that connection information is no longer valid, such as if the server's IP and/or port has changed, the Connect Menu will reappear after loading. Use the Connect Menu before or after loading the savegame to connect to the new instance. +Savegames store their connection information and automatically attempt to reestablish the connection upon loading. +If the connection information is no longer valid, such as if the server's IP and/or port have changed, +you need to use the connect form on the main menu beforehand. -Warning: Currently it is not checked if this is the correct multiworld belonging to that savegame, please ensure that yourself beforehand. +Warning: Currently it is not checked whether a loaded savegame belongs to the multiworld you are connecting to. Please ensure that yourself beforehand. + +## Console Commands + +The mod adds the following console commands: + - `silent` toggles Archipelago chat messages appearing. + - `deathlink` toggles death link. + +To enable the console in Subnautica, press `F3` and `F8`, then uncheck "Disable Console" in the top left. Press `F3` and `F8` again to close the menus. +To enter a console command, press `Enter`. + +## Known Issues + +- Do not attempt playing vanilla saves while the mod is installed, as the mod will override the scan information of the savegame. +- When exiting to the main menu the mod's state is not properly reset. Loading a savegame from here will break various things. + If you want to reload a save it is recommended you restart the game entirely. +- Attempting to load a savegame containing no longer valid connection information without entering valid information on the main menu will hang on the loading screen. ## Troubleshooting -If you don't see the Connect Menu within the Main Menu, check that you see a file named `qmodmanager_log-Subnautica.txt` in the Subnautica game directory. If not, -QModManager4 is not correctly installed, otherwise open it and look -for `[Info : BepInEx] Loading [Archipelago`. If it doesn't show this, then +If you don't see the connect form on the main menu screen, check whether you see a file named `qmodmanager_log-Subnautica.txt` in the Subnautica game directory. If not, +QModManager4 is not correctly installed, otherwise open it and look for `Loading [Archipelago`. If the file doesn't contain this text, then QModManager4 didn't find the Archipelago mod, so check your paths. diff --git a/worlds/v6/__init__.py b/worlds/v6/__init__.py index 38690e5a00..9c5fdbc8a1 100644 --- a/worlds/v6/__init__.py +++ b/worlds/v6/__init__.py @@ -91,6 +91,6 @@ class V6World(World): } } } - filename = f"AP_{self.world.seed_name}_P{self.player}_{self.world.get_file_safe_player_name(self.player)}.apv6" + filename = f"{self.world.get_out_file_name_base(self.player)}.apv6" with open(os.path.join(output_directory, filename), 'w') as f: json.dump(data, f)