From 325299286b738d30b3bb9f2083568e8c21e5e762 Mon Sep 17 00:00:00 2001 From: digiholic Date: Thu, 29 Jun 2023 12:36:01 -0600 Subject: [PATCH] Mega Man Battle Network 3: Implement New Game (#1198) * Initializes MMBN3 world with empty files * Adds item names to item dict * Adds locations and names * Adds skeleton of MMBN3Client. Mostly copy pasta from OOT * Fixed some style and formatting * More incremental Lua tests * Adds all locations and checking to Lua connector * Made class definitions for TextPet Parser * Begun connecting item delivery system through lua and textpet * Lua Connection can now send test items * Item Delivery is now parameterized. Test command can send any chip * Adds the ability to send non-chip items * Fixes name errors in python client * Fixes count for zenny, attempts to fix bugfrags * Fixes an issue where you always received 255 bugfrags * Converts zenny and bugfrag amounts to little endian bytecode * Checks game state before sending chips Adds debug option to display information overlayed on rom Fixes chip indexing issue for chips with ids over 255 Minor text fixes * Adds in some animation reset instructions during item get message * Stores previously collected item index in save, re-sends missing items * Adds title screen check before sending locations Loading items from save could not be done via RAM. Had to be added in assembly * Adds progressive undernet check * Added library for lzss decoding bits of rom * More progress on parsing text events from ROM * Adds a way to inject messages into ScriptArchive data structure and generate bytecode * Adds Item definitions, passes to client * Adds regions and item collection rules * Touched up a few names and values that have changed in preparation for the final patching * Modifying messages via item is now successful * Added generate_output hook to generate ROM data * Generates ROM successfully * Fixes navi cust give index * Whoops forgot to wrap this in brackets * Injects extra scripts for undernet rankings * Programs had ammount and color swapped * Prompts the user for their username when connecting * Adds flagClear to the list of commands to avoid overwriting * Fixes message box crashes and several other multiworld issues * Fixes IDs and names of several items and locations * Added .gba to gitignore * Fixes compatibility after recent rebase * Fixes some locations and items that are otherwise unobtainable * Attempts to make a working launcher in the installer * Creates installer and fixes several inaccessible locations * Many minor changes to items, locations, and requirements made during testing * Adds an info page for MMBN3 * Fixes failing tests by removing duplicate IDs and properly marking progression items * Accidentally forgot to un-remove the thing * Whoops, changed this by accident * Updates self.world references to self.multiworld * Fixes imports to use from imports instead of using the namespace * Removed some leftover merge artifacts from inno setup * Puts back that darned signtool line again * Adds Overworld Metro keys as items * Adds TamaCode and puts shortcuts behind cyber passes * Fixes Numberman code 16 check * Fixes metro access logic and adds text to metro * Reworks Lua to fix crashing when many items are queued * Items for other BN3 games for different players are no longer given in the main player's ROM as well * Fixes incorrect Item ID for ACDC Metro * Fixes multi-box text messages * Adds timer before sending an item * Forgot to remove the second box of SubMems * Updates patch and lua to prevent softlocks and crashes * Adds options for extra undernet ranks, exclude jobs * Extra GigFreez now gives 20 bugfrags * Additional Progressive Undernets can no longer appear on the WWW Base * Moves item signal byte to empty area of flags instead of end of RAM * Adds Chocolate Shop locations and navi chips to fill them * Fixes save crash, and added chocolates to lua * Fixes chocolate stand selling out text, removes DrillMan cube in Undernet * Replaces old messaging system with direct memory manipulation for receiving items * Removes NDSPY requirements from MMBN3 by manually adapting the GBA's lz10 algorithm * Fixes the names of Hospital-1 Locations * Adds Canary Bit to avoid sending checks when title screen check fails * Gaining a cybermetro pass will now open the shortcut immediately * Randomizes the two accessible areas of Undernet 7, adds Hammer as item * Adds new locations to connector lua * Injects the name of the item into trade quests * Fixes copy-paste error in docs * Fixes merge artifacts and depracated code * Nut-wafer stand now faces Lan the right way after buying * Removes unused Goal Option and updates the readme to include most recent changes * Touch-ups and formatting changes * The Great Fillerization update. Dozens of items changed to Filler * Replaces instances of Mega Man with MegaMan * Update worlds/mmbn3/docs/en_MegaMan Battle Network 3.md Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> * Update worlds/mmbn3/__init__.py Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com> * Changes code ordering to suit base class's * assert_generate now checks for roms. Minor text fixes * Makes player specific frequency and excluded location options * Apply suggestions from code review Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> * Addresses suggested changes from PR review * Replaces ndspy lz10 with MIT-compliant nlzss lz10 * apworld compatibility fix for mmbn3_options from utils * Addressing more comments by el-u * APworld will now pull patch from zip folder * Apply suggestions from code review Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> * Cleaned up comments for progressive undernet ROM function, moved index list to field to avoid re-initializing * Removes improper player-indexed location/item dicts, replaces with world member variables * Avoids redefining list in progressive undernet ROM function * Filler items can no longer be generated beyond their specified amounts * Fixes list copying issue with item frequencies * Adds BN3 Client Generation back into Launcher settings * Fixes typos causing huge problems * Fixed non-relative import for apworld * Removes custom enum implementation that broke pickle * Displays message when attempting to load an incorrect ROM, will not attempt to patch it * Filler items can now only be placed once * Changes path in setup doc to match Lua path changes * Fixes file extension for MMBN3 file * Replaces magic number with reference to value in NetUtils * Moves victory rules to set_rules. Removes commented out code * Rewrites Lua script to send block of memory * Fixes off-by-one error in sending bytes for locations * Fixes issue with invalid characters in text parsing, and WWW monitor text box parsing * Moves trade text injection to init so it has access to options * Attempts to split the text boxes for hinted items * Trade checks now provide hints if the option is set for them * Fixes escape character issue for BizHawk 2.9.1 Something in Bizhawk lua parsing changed to dislike the escaped tilde. I'm not even entirely sure why it was escaped in the first place, but this should fix the compatibility of it. * Re-adds desk check that it turns out actually does exist * Updates requirements to mention bizhawk 2.7 instead of 2.3.1 * Fixes off-by-one error in command byte counts * Fixes program color indices * Fixes newline PEP violations * Reverts an accidental whitespace change made to launcher.py * Fixes URL formatting on link to settings from setup guide Co-authored-by: Zach Parks * Splits several lines in the readme to avoid excessive length * Fixes formatting and (hopefully) reduces cringe of joke in setup doc * Removes unnecessary constructor * Changes item frequency generation to avoid reusing the same references Co-authored-by: Zach Parks --------- Co-authored-by: el-u <109771707+el-u@users.noreply.github.com> Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com> Co-authored-by: Zach Parks --- MMBN3Client.py | 372 +++++++++ Utils.py | 4 + data/lua/connector_mmbn3.lua | 723 ++++++++++++++++++ host.yaml | 9 +- inno_setup.iss | 53 +- worlds/LauncherComponents.py | 3 + worlds/mmbn3/BN3RomUtils.py | 194 +++++ worlds/mmbn3/Items.py | 345 +++++++++ worlds/mmbn3/Locations.py | 359 +++++++++ worlds/mmbn3/Names/ItemName.py | 238 ++++++ worlds/mmbn3/Names/LocationName.py | 313 ++++++++ worlds/mmbn3/Options.py | 48 ++ worlds/mmbn3/Regions.py | 354 +++++++++ worlds/mmbn3/Rom.py | 347 +++++++++ worlds/mmbn3/__init__.py | 483 ++++++++++++ worlds/mmbn3/data/bn3-ap-patch.bsdiff | Bin 0 -> 59914 bytes .../mmbn3/docs/en_MegaMan Battle Network 3.md | 74 ++ worlds/mmbn3/docs/setup_en.md | 79 ++ worlds/mmbn3/lz10.py | 257 +++++++ 19 files changed, 4249 insertions(+), 6 deletions(-) create mode 100644 MMBN3Client.py create mode 100644 data/lua/connector_mmbn3.lua create mode 100644 worlds/mmbn3/BN3RomUtils.py create mode 100644 worlds/mmbn3/Items.py create mode 100644 worlds/mmbn3/Locations.py create mode 100644 worlds/mmbn3/Names/ItemName.py create mode 100644 worlds/mmbn3/Names/LocationName.py create mode 100644 worlds/mmbn3/Options.py create mode 100644 worlds/mmbn3/Regions.py create mode 100644 worlds/mmbn3/Rom.py create mode 100644 worlds/mmbn3/__init__.py create mode 100644 worlds/mmbn3/data/bn3-ap-patch.bsdiff create mode 100644 worlds/mmbn3/docs/en_MegaMan Battle Network 3.md create mode 100644 worlds/mmbn3/docs/setup_en.md create mode 100644 worlds/mmbn3/lz10.py diff --git a/MMBN3Client.py b/MMBN3Client.py new file mode 100644 index 000000000000..d8ee581bd453 --- /dev/null +++ b/MMBN3Client.py @@ -0,0 +1,372 @@ +import asyncio +import hashlib +import json +import os +import multiprocessing +import subprocess +import zipfile + +from asyncio import StreamReader, StreamWriter + +import bsdiff4 + +from CommonClient import CommonContext, server_loop, gui_enabled, \ + ClientCommandProcessor, logger, get_base_parser +import Utils +from NetUtils import ClientStatus +from worlds.mmbn3.Items import items_by_id +from worlds.mmbn3.Rom import get_base_rom_path +from worlds.mmbn3.Locations import all_locations, scoutable_locations + +SYSTEM_MESSAGE_ID = 0 + +CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart connector_mmbn3.lua" +CONNECTION_REFUSED_STATUS = \ + "Connection refused. Please start your emulator and make sure connector_mmbn3.lua is running" +CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart connector_mmbn3.lua" +CONNECTION_TENTATIVE_STATUS = "Initial Connection Made" +CONNECTION_CONNECTED_STATUS = "Connected" +CONNECTION_INITIAL_STATUS = "Connection has not been initiated" +CONNECTION_INCORRECT_ROM = "Supplied Base Rom does not match US GBA Blue Version. Please provide the correct ROM version" + +script_version: int = 2 + +debugEnabled = False +locations_checked = [] +items_sent = [] +itemIndex = 1 + +CHECKSUM_BLUE = "6fe31df0144759b34ad666badaacc442" + + +class MMBN3CommandProcessor(ClientCommandProcessor): + def __init__(self, ctx): + super().__init__(ctx) + + def _cmd_gba(self): + """Check GBA Connection State""" + if isinstance(self.ctx, MMBN3Context): + logger.info(f"GBA Status: {self.ctx.gba_status}") + + def _cmd_debug(self): + """Toggle the Debug Text overlay in ROM""" + global debugEnabled + debugEnabled = not debugEnabled + logger.info("Debug Overlay Enabled" if debugEnabled else "Debug Overlay Disabled") + + +class MMBN3Context(CommonContext): + command_processor = MMBN3CommandProcessor + game = "MegaMan Battle Network 3" + items_handling = 0b001 # full local + + def __init__(self, server_address, password): + super().__init__(server_address, password) + self.gba_streams: (StreamReader, StreamWriter) = None + self.gba_sync_task = None + self.gba_status = CONNECTION_INITIAL_STATUS + self.awaiting_rom = False + self.location_table = {} + self.version_warning = False + self.auth_name = None + self.slot_data = dict() + self.patching_error = False + + async def server_auth(self, password_requested: bool = False): + if password_requested and not self.password: + await super(MMBN3Context, self).server_auth(password_requested) + + if self.auth_name is None: + self.awaiting_rom = True + logger.info("No ROM detected, awaiting conection to Bizhawk to authenticate to the multiworld server") + return + + logger.info("Attempting to decode from ROM... ") + self.awaiting_rom = False + self.auth = self.auth_name.decode("utf8").replace('\x00', '') + logger.info("Connecting as "+self.auth) + await self.send_connect(name=self.auth) + + def run_gui(self): + from kvui import GameManager + + class MMBN3Manager(GameManager): + logging_pairs = [ + ("Client", "Archipelago") + ] + base_title = "Archipelago MegaMan Battle Network 3 Client" + + self.ui = MMBN3Manager(self) + self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") + + def on_package(self, cmd: str, args: dict): + if cmd == 'Connected': + self.slot_data = args.get("slot_data", {}) + print(self.slot_data) + +class ItemInfo: + id = 0x00 + sender = "" + type = "" + count = 1 + itemName = "Unknown" + itemID = 0x00 # Item ID, Chip ID, etc. + subItemID = 0x00 # Code for chips, color for programs + itemIndex = 1 + + def __init__(self, id, sender, type): + self.id = id + self.sender = sender + self.type = type + + def get_json(self): + json_data = { + "id": self.id, + "sender": self.sender, + "type": self.type, + "itemName": self.itemName, + "itemID": self.itemID, + "subItemID": self.subItemID, + "count": self.count, + "itemIndex": self.itemIndex + } + return json_data + + +def get_payload(ctx: MMBN3Context): + global debugEnabled + + items_sent = [] + for i, item in enumerate(ctx.items_received): + item_data = items_by_id[item.item] + new_item = ItemInfo(i, ctx.player_names[item.player], item_data.type) + new_item.itemIndex = i+1 + new_item.itemName = item_data.itemName + new_item.type = item_data.type + new_item.itemID = item_data.itemID + new_item.subItemID = item_data.subItemID + new_item.count = item_data.count + items_sent.append(new_item) + + return json.dumps({ + "items": [item.get_json() for item in items_sent], + "debug": debugEnabled + }) + + +async def parse_payload(payload: dict, ctx: MMBN3Context, force: bool): + # Game completion handling + if payload["gameComplete"] and not ctx.finished_game: + await ctx.send_msgs([{ + "cmd": "StatusUpdate", + "status": ClientStatus.CLIENT_GOAL + }]) + ctx.finished_game = True + + # Locations handling + if ctx.location_table != payload["locations"]: + ctx.location_table = payload["locations"] + locs = [loc.id for loc in all_locations + if check_location_packet(loc, ctx.location_table)] + await ctx.send_msgs([{ + "cmd": "LocationChecks", + "locations": locs + }]) + + # If trade hinting is enabled, send scout checks + if ctx.slot_data.get("trade_quest_hinting", 0) == 2: + scouted_locs = [loc.id for loc in scoutable_locations + if check_location_scouted(loc, payload["locations"])] + await ctx.send_msgs([{ + "cmd": "LocationScouts", + "locations": scouted_locs, + "create_as_hint": 2 + }]) + + +def check_location_packet(location, memory): + if len(memory) == 0: + return False + # Our keys have to be strings to come through the JSON lua plugin so we have to turn our memory address into a string as well + location_key = hex(location.flag_byte)[2:] + byte = memory.get(location_key) + if byte is not None: + return byte & location.flag_mask + + +def check_location_scouted(location, memory): + if len(memory) == 0: + return False + location_key = hex(location.hint_flag)[2:] + byte = memory.get(location_key) + if byte is not None: + return byte & location.hint_flag_mask + + +async def gba_sync_task(ctx: MMBN3Context): + logger.info("Starting GBA connector. Use /gba for status information.") + if ctx.patching_error: + logger.error('Unable to Patch ROM. No ROM provided or ROM does not match US GBA Blue Version.') + while not ctx.exit_event.is_set(): + error_status = None + if ctx.gba_streams: + (reader, writer) = ctx.gba_streams + msg = get_payload(ctx).encode() + writer.write(msg) + writer.write(b'\n') + try: + await asyncio.wait_for(writer.drain(), timeout=1.5) + try: + # Data will return a dict with up to four fields + # 1. str: player name (always) + # 2. int: script version (always) + # 3. dict[str, byte]: value of location's memory byte + # 4. bool: whether the game currently registers as complete + data = await asyncio.wait_for(reader.readline(), timeout=10) + data_decoded = json.loads(data.decode()) + reported_version = data_decoded.get("scriptVersion", 0) + if reported_version >= script_version: + if ctx.game is not None and "locations" in data_decoded: + # Not just a keep alive ping, parse + asyncio.create_task((parse_payload(data_decoded, ctx, False))) + if not ctx.auth: + ctx.auth_name = bytes(data_decoded["playerName"]) + + if ctx.awaiting_rom: + logger.info("Awaiting data from ROM...") + await ctx.server_auth(False) + else: + if not ctx.version_warning: + logger.warning(f"Your Lua script is version {reported_version}, expected {script_version}." + "Please update to the latest version." + "Your connection to the Archipelago server will not be accepted.") + ctx.version_warning = True + except asyncio.TimeoutError: + logger.debug("Read Timed Out, Reconnecting") + error_status = CONNECTION_TIMING_OUT_STATUS + writer.close() + ctx.gba_streams = None + except ConnectionResetError: + logger.debug("Read failed due to Connection Lost, Reconnecting") + error_status = CONNECTION_RESET_STATUS + writer.close() + ctx.gba_streams = None + except TimeoutError: + logger.debug("Connection Timed Out, Reconnecting") + error_status = CONNECTION_TIMING_OUT_STATUS + writer.close() + ctx.gba_streams = None + except ConnectionResetError: + logger.debug("Connection Lost, Reconnecting") + error_status = CONNECTION_RESET_STATUS + writer.close() + ctx.gba_streams = None + if ctx.gba_status == CONNECTION_TENTATIVE_STATUS: + if not error_status: + logger.info("Successfully Connected to GBA") + ctx.gba_status = CONNECTION_CONNECTED_STATUS + else: + ctx.gba_status = f"Was tentatively connected but error occurred: {error_status}" + elif error_status: + ctx.gba_status = error_status + logger.info("Lost connection to GBA and attempting to reconnect. Use /gba for status updates") + else: + try: + logger.debug("Attempting to connect to GBA") + ctx.gba_streams = await asyncio.wait_for(asyncio.open_connection("localhost", 28922), timeout=10) + ctx.gba_status = CONNECTION_TENTATIVE_STATUS + except TimeoutError: + logger.debug("Connection Timed Out, Trying Again") + ctx.gba_status = CONNECTION_TIMING_OUT_STATUS + continue + except ConnectionRefusedError: + logger.debug("Connection Refused, Trying Again") + ctx.gba_status = CONNECTION_REFUSED_STATUS + continue + + +async def run_game(romfile): + options = Utils.get_options().get("mmbn3_options", None) + if options is None: + auto_start = True + else: + auto_start = options.get("rom_start", True) + if auto_start: + import webbrowser + webbrowser.open(romfile) + elif os.path.isfile(auto_start): + subprocess.Popen([auto_start, romfile], + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + +async def patch_and_run_game(apmmbn3_file): + base_name = os.path.splitext(apmmbn3_file)[0] + + with zipfile.ZipFile(apmmbn3_file, 'r') as patch_archive: + try: + with patch_archive.open("delta.bsdiff4", 'r') as stream: + patch_data = stream.read() + except KeyError: + raise FileNotFoundError("Patch file missing from archive.") + rom_file = get_base_rom_path() + + with open(rom_file, 'rb') as rom: + rom_bytes = rom.read() + + patched_bytes = bsdiff4.patch(rom_bytes, patch_data) + patched_rom_file = base_name+".gba" + with open(patched_rom_file, 'wb') as patched_rom: + patched_rom.write(patched_bytes) + + asyncio.create_task(run_game(patched_rom_file)) + + +def confirm_checksum(): + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + return False + + with open(rom_file, 'rb') as rom: + rom_bytes = rom.read() + + basemd5 = hashlib.md5() + basemd5.update(rom_bytes) + return CHECKSUM_BLUE == basemd5.hexdigest() + + +if __name__ == "__main__": + Utils.init_logging("MMBN3Client") + + async def main(): + multiprocessing.freeze_support() + parser = get_base_parser() + parser.add_argument("patch_file", default="", type=str, nargs="?", + help="Path to an APMMBN3 file") + args = parser.parse_args() + checksum_matches = confirm_checksum() + if checksum_matches: + if args.patch_file: + asyncio.create_task(patch_and_run_game(args.patch_file)) + + ctx = MMBN3Context(args.connect, args.password) + if not checksum_matches: + ctx.patching_error = True + ctx.server_task = asyncio.create_task(server_loop(ctx), name="Server Loop") + if gui_enabled: + ctx.run_gui() + ctx.run_cli() + + ctx.gba_sync_task = asyncio.create_task(gba_sync_task(ctx), name="GBA Sync") + await ctx.exit_event.wait() + ctx.server_address = None + await ctx.shutdown() + + if ctx.gba_sync_task: + await ctx.gba_sync_task + + import colorama + + colorama.init() + + asyncio.run(main()) + colorama.deinit() diff --git a/Utils.py b/Utils.py index 44715bd4255a..1acd56514f86 100644 --- a/Utils.py +++ b/Utils.py @@ -339,6 +339,10 @@ def get_default_options() -> OptionsType: "wargroove_options": { "root_directory": "C:/Program Files (x86)/Steam/steamapps/common/Wargroove" }, + "mmbn3_options": { + "rom_file": "Mega Man Battle Network 3 - Blue Version (USA).gba", + "rom_start": True + }, "adventure_options": { "rom_file": "ADVNTURE.BIN", "display_msgs": True, diff --git a/data/lua/connector_mmbn3.lua b/data/lua/connector_mmbn3.lua new file mode 100644 index 000000000000..e584121a604c --- /dev/null +++ b/data/lua/connector_mmbn3.lua @@ -0,0 +1,723 @@ +local socket = require("socket") +local json = require('json') +local math = require('math') +require('common') + +local last_modified_date = '2023-31-05' -- Should be the last modified date +local script_version = 4 + +local bizhawk_version = client.getversion() +local bizhawk_major, bizhawk_minor, bizhawk_patch = bizhawk_version:match("(%d+)%.(%d+)%.?(%d*)") +bizhawk_major = tonumber(bizhawk_major) +bizhawk_minor = tonumber(bizhawk_minor) +if bizhawk_patch == "" then + bizhawk_patch = 0 +else + bizhawk_patch = tonumber(bizhawk_patch) +end + +local STATE_OK = "Ok" +local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected" +local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made" +local STATE_UNINITIALIZED = "Uninitialized" + +local prevstate = "" +local curstate = STATE_UNINITIALIZED +local mmbn3Socket = nil +local frame = 0 + +-- States +local ITEMSTATE_NONINITIALIZED = "Game Not Yet Started" -- Game has not yet started +local ITEMSTATE_NONITEM = "Non-Itemable State" -- Do not send item now. RAM is not capable of holding +local ITEMSTATE_IDLE = "Item State Ready" -- Ready for the next item if there are any +local ITEMSTATE_SENT = "Item Sent Not Claimed" -- The ItemBit is set, but the dialog has not been closed yet +local itemState = ITEMSTATE_NONINITIALIZED + +local itemQueued = nil +local itemQueueCounter = 120 + +local debugEnabled = false +local game_complete = false + +local backup_bytes = nil + +local itemsReceived = {} +local previousMessageBit = 0x00 + +local key_item_start_address = 0x20019C0 + +-- The Canary Byte is a flag byte that is intentionally left unused. If this byte is FF, then we know the flag +-- data cannot be trusted, so we don't send checks. +local canary_byte = 0x20001A9 + +local charDict = { + [' ']=0x00,['0']=0x01,['1']=0x02,['2']=0x03,['3']=0x04,['4']=0x05,['5']=0x06,['6']=0x07,['7']=0x08,['8']=0x09,['9']=0x0A, + ['A']=0x0B,['B']=0x0C,['C']=0x0D,['D']=0x0E,['E']=0x0F,['F']=0x10,['G']=0x11,['H']=0x12,['I']=0x13,['J']=0x14,['K']=0x15, + ['L']=0x16,['M']=0x17,['N']=0x18,['O']=0x19,['P']=0x1A,['Q']=0x1B,['R']=0x1C,['S']=0x1D,['T']=0x1E,['U']=0x1F,['V']=0x20, + ['W']=0x21,['X']=0x22,['Y']=0x23,['Z']=0x24,['a']=0x25,['b']=0x26,['c']=0x27,['d']=0x28,['e']=0x29,['f']=0x2A,['g']=0x2B, + ['h']=0x2C,['i']=0x2D,['j']=0x2E,['k']=0x2F,['l']=0x30,['m']=0x31,['n']=0x32,['o']=0x33,['p']=0x34,['q']=0x35,['r']=0x36, + ['s']=0x37,['t']=0x38,['u']=0x39,['v']=0x3A,['w']=0x3B,['x']=0x3C,['y']=0x3D,['z']=0x3E,['-']=0x3F,['×']=0x40,[']=']=0x41, + [':']=0x42,['+']=0x43,['÷']=0x44,['※']=0x45,['*']=0x46,['!']=0x47,['?']=0x48,['%']=0x49,['&']=0x4A,[',']=0x4B,['⋯']=0x4C, + ['.']=0x4D,['・']=0x4E,[';']=0x4F,['\'']=0x50,['\"']=0x51,['~']=0x52,['/']=0x53,['(']=0x54,[')']=0x55,['「']=0x56,['」']=0x57, + ["[V2]"]=0x58,["[V3]"]=0x59,["[V4]"]=0x5A,["[V5]"]=0x5B,['@']=0x5C,['♥']=0x5D,['♪']=0x5E,["[MB]"]=0x5F,['■']=0x60,['_']=0x61, + ["[circle1]"]=0x62,["[circle2]"]=0x63,["[cross1]"]=0x64,["[cross2]"]=0x65,["[bracket1]"]=0x66,["[bracket2]"]=0x67,["[ModTools1]"]=0x68, + ["[ModTools2]"]=0x69,["[ModTools3]"]=0x6A,['Σ']=0x6B,['Ω']=0x6C,['α']=0x6D,['β']=0x6E,['#']=0x6F,['…']=0x70,['>']=0x71, + ['<']=0x72,['エ']=0x73,["[BowneGlobal1]"]=0x74,["[BowneGlobal2]"]=0x75,["[BowneGlobal3]"]=0x76,["[BowneGlobal4]"]=0x77, + ["[BowneGlobal5]"]=0x78,["[BowneGlobal6]"]=0x79,["[BowneGlobal7]"]=0x7A,["[BowneGlobal8]"]=0x7B,["[BowneGlobal9]"]=0x7C, + ["[BowneGlobal10]"]=0x7D,["[BowneGlobal11]"]=0x7E,['\n']=0xE8 +} + +local TableConcat = function(t1,t2) + for i=1,#t2 do + t1[#t1+1] = t2[i] + end + return t1 +end +local int32ToByteList_le = function(x) + bytes = {} + hexString = string.format("%08x", x) + for i=#hexString, 1, -2 do + hbyte = hexString:sub(i-1, i) + table.insert(bytes,tonumber(hbyte,16)) + end + return bytes +end +local int16ToByteList_le = function(x) + bytes = {} + hexString = string.format("%04x", x) + for i=#hexString, 1, -2 do + hbyte = hexString:sub(i-1, i) + table.insert(bytes,tonumber(hbyte,16)) + end + return bytes +end + +local IsInMenu = function() + return bit.band(memory.read_u8(0x0200027A),0x10) ~= 0 +end +local IsInTransition = function() + return bit.band(memory.read_u8(0x02001880), 0x10) ~= 0 +end +local IsInDialog = function() + return bit.band(memory.read_u8(0x02009480),0x01) ~= 0 +end +local IsInBattle = function() + return memory.read_u8(0x020097F8) == 0x08 +end +local IsItemQueued = function() + return memory.read_u8(0x2000224) == 0x01 +end + +-- This function actually determines when you're on ANY full-screen menu (navi cust, link battle, etc.) but we +-- don't want to check any locations there either so it's fine. +local IsOnTitle = function() + return bit.band(memory.read_u8(0x020097F8),0x04) == 0 +end +local IsItemable = function() + return not IsInMenu() and not IsInTransition() and not IsInDialog() and not IsInBattle() and not IsOnTitle() and not IsItemQueued() +end + +local is_game_complete = function() + if IsOnTitle() or itemState == ITEMSTATE_NONINITIALIZED then return game_complete end + + -- If the game is already marked complete, do not read memory + if game_complete then return true end + local is_alpha_defeated = bit.band(memory.read_u8(0x2000433), 0x01) ~= 0 + + if (is_alpha_defeated) then + game_complete = true + return true + end + + -- Game is still ongoing + return false +end + +local saveItemIndexToRAM = function(newIndex) + memory.write_s16_le(0x20000AE,newIndex) +end + +local loadItemIndexFromRAM = function() + last_index = memory.read_s16_le(0x20000AE) + if (last_index < 0) then + last_index = 0 + saveItemIndexToRAM(0) + end + return last_index +end + +local loadPlayerNameFromROM = function() + return memory.read_bytes_as_array(0x7FFFC0,63,"ROM") +end + +local check_all_locations = function() + local location_checks = {} + -- Title Screen should not check items + if itemState == ITEMSTATE_NONINITIALIZED or IsInTransition() then + return location_checks + end + if memory.read_u8(canary_byte) == 0xFF then + return location_checks + end + for k,v in pairs(memory.read_bytes_as_dict(0x02000000, 0x434)) do + str_k = string.format("%x", k) + location_checks[str_k] = v + end + return location_checks +end + +local Check_Progressive_Undernet_ID = function() + ordered_offsets = { 0x020019DB,0x020019DC,0x020019DD,0x020019DE,0x020019DF,0x020019E0,0x020019FA,0x020019E2 } + for i=1,#ordered_offsets do + offset=ordered_offsets[i] + + if memory.read_u8(offset) == 0 then + return i + end + end + return 9 +end +local GenerateTextBytes = function(message) + bytes = {} + for i = 1, #message do + local c = message:sub(i,i) + table.insert(bytes, charDict[c]) + end + return bytes +end + +-- Item Message Generation functions +local Next_Progressive_Undernet_ID = function(index) + ordered_IDs = { 27,28,29,30,31,32,58,34} + if index > #ordered_IDs then + --It shouldn't reach this point, but if it does, just give another GigFreez I guess + return 34 + end + item_index=ordered_IDs[index] + return item_index +end +local Extra_Progressive_Undernet = function() + fragBytes = int32ToByteList_le(20) + bytes = { + 0xF6, 0x50, fragBytes[1], fragBytes[2], fragBytes[3], fragBytes[4], 0xFF, 0xFF, 0xFF + } + bytes = TableConcat(bytes, GenerateTextBytes("The extra data\ndecompiles into:\n\"20 BugFrags\"!!")) + return bytes +end + +local GenerateChipGet = function(chip, code, amt) + chipBytes = int16ToByteList_le(chip) + bytes = { + 0xF6, 0x10, chipBytes[1], chipBytes[2], code, amt, + charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict[' '], charDict['c'], charDict['h'], charDict['i'], charDict['p'], charDict[' '], charDict['f'], charDict['o'], charDict['r'], charDict['\n'], + + } + if chip < 256 then + bytes = TableConcat(bytes, { + charDict['\"'], 0xF9,0x00,chipBytes[1],0x01,0x00,0xF9,0x00,code,0x03, charDict['\"'],charDict['!'],charDict['!'] + }) + else + bytes = TableConcat(bytes, { + charDict['\"'], 0xF9,0x00,chipBytes[1],0x02,0x00,0xF9,0x00,code,0x03, charDict['\"'],charDict['!'],charDict['!'] + }) + end + return bytes +end +local GenerateKeyItemGet = function(item, amt) + bytes = { + 0xF6, 0x00, item, amt, + charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'], + charDict['\"'], 0xF9, 0x00, item, 0x00, charDict['\"'],charDict['!'],charDict['!'] + } + return bytes +end +local GenerateSubChipGet = function(subchip, amt) + -- SubChips have an extra bit of trouble. If you have too many, they're supposed to skip to another text bank that doesn't give you the item + -- Instead, I'm going to just let it get eaten + bytes = { + 0xF6, 0x20, subchip, amt, 0xFF, 0xFF, 0xFF, + charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'], + charDict['S'], charDict['u'], charDict['b'], charDict['C'], charDict['h'], charDict['i'], charDict['p'], charDict[' '], charDict['f'], charDict['o'], charDict['r'], charDict['\n'], + charDict['\"'], 0xF9, 0x00, subchip, 0x00, charDict['\"'],charDict['!'],charDict['!'] + } + return bytes +end +local GenerateZennyGet = function(amt) + zennyBytes = int32ToByteList_le(amt) + bytes = { + 0xF6, 0x30, zennyBytes[1], zennyBytes[2], zennyBytes[3], zennyBytes[4], 0xFF, 0xFF, 0xFF, + charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'], charDict['\"'] + } + -- The text needs to be added one char at a time, so we need to convert the number to a string then iterate through it + zennyStr = tostring(amt) + for i = 1, #zennyStr do + local c = zennyStr:sub(i,i) + table.insert(bytes, charDict[c]) + end + bytes = TableConcat(bytes, { + charDict[' '], charDict['Z'], charDict['e'], charDict['n'], charDict['n'], charDict['y'], charDict['s'], charDict['\"'],charDict['!'],charDict['!'] + }) + return bytes +end +local GenerateProgramGet = function(program, color, amt) + bytes = { + 0xF6, 0x40, (program * 4), amt, color, + charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict[' '], charDict['N'], charDict['a'], charDict['v'], charDict['i'], charDict['\n'], + charDict['C'], charDict['u'], charDict['s'], charDict['t'], charDict['o'], charDict['m'], charDict['i'], charDict['z'], charDict['e'], charDict['r'], charDict[' '], charDict['P'], charDict['r'], charDict['o'], charDict['g'], charDict['r'], charDict['a'], charDict['m'], charDict[':'], charDict['\n'], + charDict['\"'], 0xF9, 0x00, program, 0x05, charDict['\"'],charDict['!'],charDict['!'] + } + + return bytes +end +local GenerateBugfragGet = function(amt) + fragBytes = int32ToByteList_le(amt) + bytes = { + 0xF6, 0x50, fragBytes[1], fragBytes[2], fragBytes[3], fragBytes[4], 0xFF, 0xFF, 0xFF, + charDict['G'], charDict['o'], charDict['t'], charDict[':'], charDict['\n'], charDict['\"'] + } + -- The text needs to be added one char at a time, so we need to convert the number to a string then iterate through it + bugFragStr = tostring(amt) + for i = 1, #bugFragStr do + local c = bugFragStr:sub(i,i) + table.insert(bytes, charDict[c]) + end + bytes = TableConcat(bytes, { + charDict[' '], charDict['B'], charDict['u'], charDict['g'], charDict['F'], charDict['r'], charDict['a'], charDict['g'], charDict['s'], charDict['\"'],charDict['!'],charDict['!'] + }) + return bytes +end +local GenerateGetMessageFromItem = function(item) + --Special case for progressive undernet + if item["type"] == "undernet" then + undernet_id = Check_Progressive_Undernet_ID() + if undernet_id > 8 then + return Extra_Progressive_Undernet() + end + return GenerateKeyItemGet(Next_Progressive_Undernet_ID(undernet_id),1) + elseif item["type"] == "chip" then + return GenerateChipGet(item["itemID"], item["subItemID"], item["count"]) + elseif item["type"] == "key" then + return GenerateKeyItemGet(item["itemID"], item["count"]) + elseif item["type"] == "subchip" then + return GenerateSubChipGet(item["itemID"], item["count"]) + elseif item["type"] == "zenny" then + return GenerateZennyGet(item["count"]) + elseif item["type"] == "program" then + return GenerateProgramGet(item["itemID"], item["subItemID"], item["count"]) + elseif item["type"] == "bugfrag" then + return GenerateBugfragGet(item["count"]) + end + + return GenerateTextBytes("Empty Message") +end + +local GetMessage = function(item) + startBytes = {0x02, 0x00} + playerLockBytes = {0xF8,0x00, 0xF8, 0x10} + msgOpenBytes = {0xF1, 0x02} + textBytes = GenerateTextBytes("Receiving\ndata from\n"..item["sender"]..".") + dotdotWaitBytes = {0xEA,0x00,0x0A,0x00,0x4D,0xEA,0x00,0x0A,0x00,0x4D} + continueBytes = {0xEB, 0xE9} + -- continueBytes = {0xE9} + playReceiveAnimationBytes = {0xF8,0x04,0x18} + chipGiveBytes = GenerateGetMessageFromItem(item) + playerFinishBytes = {0xF8, 0x0C} + playerUnlockBytes={0xEB, 0xF8, 0x08} + -- playerUnlockBytes={0xF8, 0x08} + endMessageBytes = {0xF8, 0x10, 0xE7} + + bytes = {} + bytes = TableConcat(bytes,startBytes) + bytes = TableConcat(bytes,playerLockBytes) + bytes = TableConcat(bytes,msgOpenBytes) + bytes = TableConcat(bytes,textBytes) + bytes = TableConcat(bytes,dotdotWaitBytes) + bytes = TableConcat(bytes,continueBytes) + bytes = TableConcat(bytes,playReceiveAnimationBytes) + bytes = TableConcat(bytes,chipGiveBytes) + bytes = TableConcat(bytes,playerFinishBytes) + bytes = TableConcat(bytes,playerUnlockBytes) + bytes = TableConcat(bytes,endMessageBytes) + return bytes +end + +local getChipCodeIndex = function(chip_id, chip_code) + chipCodeArrayStartAddress = 0x8011510 + (0x20 * chip_id) + for i=1,6 do + currentCode = memory.read_u8(chipCodeArrayStartAddress + (i-1)) + if currentCode == chip_code then + return i-1 + end + end + return 0 +end + +local getProgramColorIndex = function(program_id, program_color) + -- The general case, most programs use white pink or yellow. This is the values the enums already have + if program_id >= 23 and program_id <= 47 then + return program_color-1 + end + --The final three programs only have a color index 0, so just return those + if program_id > 47 then + return 0 + end + --BrakChrg as an AP item only comes in orange, index 0 + if program_id == 3 then + return 0 + end + -- every other AP obtainable program returns only color index 3 + return 3 +end + +local addChip = function(chip_id, chip_code, amount) + chipStartAddress = 0x02001F60 + chipOffset = 0x12 * chip_id + chip_code_index = getChipCodeIndex(chip_id, chip_code) + currentChipAddress = chipStartAddress + chipOffset + chip_code_index + currentChipCount = memory.read_u8(currentChipAddress) + memory.write_u8(currentChipAddress,currentChipCount+amount) +end + +local addProgram = function(program_id, program_color, amount) + programStartAddress = 0x02001A80 + programOffset = 0x04 * program_id + program_code_index = getProgramColorIndex(program_id, program_color) + currentProgramAddress = programStartAddress + programOffset + program_code_index + currentProgramCount = memory.read_u8(currentProgramAddress) + memory.write_u8(currentProgramAddress, currentProgramCount+amount) +end + +local addSubChip = function(subchip_id, amount) + subChipStartAddress = 0x02001A30 + --SubChip indices start after the key items, so subtract 112 from the index to get the actual subchip index + currentSubChipAddress = subChipStartAddress + (subchip_id - 112) + currentSubChipCount = memory.read_u8(currentSubChipAddress) + --TODO check submem, reject if number too big + memory.write_u8(currentSubChipAddress, currentSubChipCount+amount) +end + +local changeZenny = function(val) + if val == nil then + return 0 + end + if memory.read_u32_le(0x20018F4) <= math.abs(tonumber(val)) and tonumber(val) < 0 then + memory.write_u32_le(0x20018f4, 0) + val = 0 + return "empty" + end + memory.write_u32_le(0x20018f4, memory.read_u32_le(0x20018F4) + tonumber(val)) + if memory.read_u32_le(0x20018F4) > 999999 then + memory.write_u32_le(0x20018F4, 999999) + end + return val +end + +local changeFrags = function(val) + if val == nil then + return 0 + end + if memory.read_u16_le(0x20018F8) <= math.abs(tonumber(val)) and tonumber(val) < 0 then + memory.write_u16_le(0x20018f8, 0) + val = 0 + return "empty" + end + memory.write_u16_le(0x20018f8, memory.read_u16_le(0x20018F8) + tonumber(val)) + if memory.read_u16_le(0x20018F8) > 9999 then + memory.write_u16_le(0x20018F8, 9999) + end + return val +end + +-- Fix Health Pools +local fix_hp = function() + -- Current Health fix + if IsInBattle() and not (memory.read_u16_le(0x20018A0) == memory.read_u16_le(0x2037294)) then + memory.write_u16_le(0x20018A0, memory.read_u16_le(0x2037294)) + end + + -- Max Health Fix + if IsInBattle() and not (memory.read_u16_le(0x20018A2) == memory.read_u16_le(0x2037296)) then + memory.write_u16_le(0x20018A2, memory.read_u16_le(0x2037296)) + end +end + +local changeRegMemory = function(amt) + regMemoryAddress = 0x02001897 + currentRegMem = memory.read_u8(regMemoryAddress) + memory.write_u8(regMemoryAddress, currentRegMem + amt) +end + +local changeMaxHealth = function(val) + fix_hp() + if val == nil then + fix_hp() + return 0 + end + if math.abs(tonumber(val)) >= memory.read_u16_le(0x20018A2) and tonumber(val) < 0 then + memory.write_u16_le(0x20018A2, 0) + if IsInBattle() then + memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2)) + if memory.read_u16_le(0x2037296) >= memory.read_u16_le(0x20018A2) then + memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2)) + end + end + fix_hp() + return "lethal" + end + memory.write_u16_le(0x20018A2, memory.read_u16_le(0x20018A2) + tonumber(val)) + if memory.read_u16_le(0x20018A2) > 9999 then + memory.write_u16_le(0x20018A2, 9999) + end + if IsInBattle() then + memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2)) + end + fix_hp() + return val +end + +local SendItem = function(item) + if item["type"] == "undernet" then + undernet_id = Check_Progressive_Undernet_ID() + if undernet_id > 8 then + -- Generate Extra BugFrags + changeFrags(20) + gui.addmessage("Receiving extra Undernet Rank from "..item["sender"]..", +20 BugFrags") + -- print("Receiving extra Undernet Rank from "..item["sender"]..", +20 BugFrags") + else + itemAddress = key_item_start_address + Next_Progressive_Undernet_ID(undernet_id) + + itemCount = memory.read_u8(itemAddress) + itemCount = itemCount + item["count"] + memory.write_u8(itemAddress, itemCount) + gui.addmessage("Received Undernet Rank from player "..item["sender"]) + -- print("Received Undernet Rank from player "..item["sender"]) + end + elseif item["type"] == "chip" then + addChip(item["itemID"], item["subItemID"], item["count"]) + gui.addmessage("Received Chip "..item["itemName"].." from player "..item["sender"]) + -- print("Received Chip "..item["itemName"].." from player "..item["sender"]) + elseif item["type"] == "key" then + itemAddress = key_item_start_address + item["itemID"] + itemCount = memory.read_u8(itemAddress) + itemCount = itemCount + item["count"] + memory.write_u8(itemAddress, itemCount) + -- HPMemory will increase the internal counter but not actually increase the HP. If the item is one of those, do that + if item["itemID"] == 96 then + changeMaxHealth(20) + end + -- Same for the RegUps, but there's three of those + if item["itemID"] == 98 then + changeRegMemory(1) + end + if item["itemID"] == 99 then + changeRegMemory(2) + end + if item["itemID"] == 100 then + changeRegMemory(3) + end + gui.addmessage("Received Key Item "..item["itemName"].." from player "..item["sender"]) + -- print("Received Key Item "..item["itemName"].." from player "..item["sender"]) + elseif item["type"] == "subchip" then + addSubChip(item["itemID"], item["count"]) + gui.addmessage("Received SubChip "..item["itemName"].." from player "..item["sender"]) + -- print("Received SubChip "..item["itemName"].." from player "..item["sender"]) + elseif item["type"] == "zenny" then + changeZenny(item["count"]) + gui.addmessage("Received "..item["count"].."z from "..item["sender"]) + -- print("Received "..item["count"].."z from "..item["sender"]) + elseif item["type"] == "program" then + addProgram(item["itemID"], item["subItemID"], item["count"]) + gui.addmessage("Received Program "..item["itemName"].." from player "..item["sender"]) + -- print("Received Program "..item["itemName"].." from player "..item["sender"]) + elseif item["type"] == "bugfrag" then + changeFrags(item["count"]) + gui.addmessage("Received "..item["count"].." BugFrag(s) from "..item["sender"]) + -- print("Received "..item["count"].." BugFrag(s) from "..item["sender"]) + end +end + +-- Set the flags for opening the shortcuts as soon as the Cybermetro passes are received to save having to check email +local OpenShortcuts = function() + if (memory.read_u8(key_item_start_address + 92) > 0) then + memory.write_u8(0x2000032, bit.bor(memory.read_u8(0x2000032),0x10)) + end + -- if CSciPass + if (memory.read_u8(key_item_start_address + 93) > 0) then + memory.write_u8(0x2000032, bit.bor(memory.read_u8(0x2000032),0x08)) + end + if (memory.read_u8(key_item_start_address + 94) > 0) then + memory.write_u8(0x2000032, bit.bor(memory.read_u8(0x2000032),0x20)) + end + if (memory.read_u8(key_item_start_address + 95) > 0) then + memory.write_u8(0x2000032, bit.bor(memory.read_u8(0x2000032),0x40)) + end +end + +local RestoreItemRam = function() + if backup_bytes ~= nil then + memory.write_bytes_as_array(0x203fe10, backup_bytes) + end + backup_bytes = nil +end + +local process_block = function(block) + -- Sometimes the block is nothing, if this is the case then quietly stop processing + if block == nil then + return + end + debugEnabled = block['debug'] + -- Queue item for receiving, if one exists + if (itemsReceived ~= block['items']) then + itemsReceived = block['items'] + end + return +end + +local itemStateMachineProcess = function() + if itemState == ITEMSTATE_NONINITIALIZED then + itemQueueCounter = 120 + -- Only exit this state the first time a dialog window pops up. This way we know for sure that we're ready to receive + if not IsInMenu() and (IsInDialog() or IsInTransition()) then + itemState = ITEMSTATE_NONITEM + end + elseif itemState == ITEMSTATE_NONITEM then + itemQueueCounter = 120 + -- Always attempt to restore the previously stored memory in this state + -- Exit this state whenever the game is in an itemable status + if IsItemable() then + itemState = ITEMSTATE_IDLE + end + elseif itemState == ITEMSTATE_IDLE then + -- Remain Idle until an item is sent or we enter a non itemable status + if not IsItemable() then + itemState = ITEMSTATE_NONITEM + end + if itemQueueCounter == 0 then + if #itemsReceived > loadItemIndexFromRAM() and not IsItemQueued() then + itemQueued = itemsReceived[loadItemIndexFromRAM()+1] + SendItem(itemQueued) + itemState = ITEMSTATE_SENT + end + else + itemQueueCounter = itemQueueCounter - 1 + end + elseif itemState == ITEMSTATE_SENT then + -- Once the item is sent, wait for the dialog to close. Then clear the item bit and be ready for the next item. + if IsInTransition() or IsInMenu() or IsOnTitle() then + itemState = ITEMSTATE_NONITEM + itemQueued = nil + RestoreItemRam() + elseif not IsInDialog() then + itemState = ITEMSTATE_IDLE + saveItemIndexToRAM(itemQueued["itemIndex"]) + itemQueued = nil + RestoreItemRam() + end + end +end +local receive = function() + l, e = mmbn3Socket:receive() + + -- Handle incoming message + if e == 'closed' then + if curstate == STATE_OK then + print("Connection closed") + end + curstate = STATE_UNINITIALIZED + return + elseif e == 'timeout' then + print("timeout") + return + elseif e ~= nil then + print(e) + curstate = STATE_UNINITIALIZED + return + end + process_block(json.decode(l)) +end + +local send = function() + -- Determine message to send back + local retTable = {} + retTable["playerName"] = loadPlayerNameFromROM() + retTable["scriptVersion"] = script_version + retTable["locations"] = check_all_locations() + retTable["gameComplete"] = is_game_complete() + + -- Send the message + msg = json.encode(retTable).."\n" + local ret, error = mmbn3Socket:send(msg) + + if ret == nil then + print(error) + elseif curstate == STATE_INITIAL_CONNECTION_MADE then + curstate = STATE_TENTATIVELY_CONNECTED + elseif curstate == STATE_TENTATIVELY_CONNECTED then + print("Connected!") + curstate = STATE_OK + end +end + +function main() + if (bizhawk_major > 2 or (bizhawk_major == 2 and bizhawk_minor >= 7)==false) then + print("Must use a version of bizhawk 2.7.0 or higher") + return + end + server, error = socket.bind('localhost', 28922) + + while true do + frame = frame + 1 + + if not (curstate == prevstate) then + prevstate = curstate + end + + itemStateMachineProcess() + + if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then + -- If we're connected and everything's fine, receive and send data from the network + if (frame % 60 == 0) then + receive() + send() + -- Perform utility functions which read and write data but aren't directly related to checks + OpenShortcuts() + end + elseif (curstate == STATE_UNINITIALIZED) then + -- If we're uninitialized, attempt to make the connection. + if (frame % 120 == 0) then + server:settimeout(2) + local client, timeout = server:accept() + if timeout == nil then + print('Initial Connection Made') + curstate = STATE_INITIAL_CONNECTION_MADE + mmbn3Socket = client + mmbn3Socket:settimeout(0) + else + print('Connection failed, ensure MMBN3Client is running and rerun connector_mmbn3.lua') + return + end + end + end + + -- Handle the debug data display + gui.cleartext() + if debugEnabled then + -- gui.text(0,0,"Item Queued: "..tostring(IsItemQueued())) + -- gui.text(0,16,"In Battle: "..tostring(IsInBattle())) + -- gui.text(0,32,"In Dialog: "..tostring(IsInDialog())) + -- gui.text(0,48,"In Menu: "..tostring(IsInMenu())) + gui.text(0,48,"Item Wait Time: "..tostring(itemQueueCounter)) + gui.text(0,64,itemState) + if itemQueued == nil then + gui.text(0,80,"No item queued") + else + gui.text(0,80,itemQueued["type"].." "..itemQueued["itemID"]) + end + gui.text(0,96,"Item Index: "..loadItemIndexFromRAM()) + end + + emu.frameadvance() + end +end + +main() \ No newline at end of file diff --git a/host.yaml b/host.yaml index 26123954a7fd..c2647c44caae 100644 --- a/host.yaml +++ b/host.yaml @@ -167,7 +167,10 @@ zillion_options: # RetroArch doesn't make it easy to launch a game from the command line. # You have to know the path to the emulator core library on the user's computer. rom_start: "retroarch" - +mmbn3_options: + # File name of the MMBN3 Blue US rom + rom_file: "Mega Man Battle Network 3 - Blue Version (USA).gba" + rom_start: true adventure_options: # File name of the standard NTSC Adventure rom. # The licensed "The 80 Classic Games" CD-ROM contains this. @@ -185,7 +188,3 @@ adventure_options: rom_args: " " # Set this to true to display item received messages in Emuhawk display_msgs: true - - - - diff --git a/inno_setup.iss b/inno_setup.iss index bd4d10eae661..5e289187ffd2 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -63,6 +63,7 @@ Name: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full Name: "generator/zl"; Description: "Zillion ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 150000; Flags: disablenouninstallwarning Name: "generator/pkmn_r"; Description: "Pokemon Red ROM Setup"; Types: full hosting Name: "generator/pkmn_b"; Description: "Pokemon Blue ROM Setup"; Types: full hosting +Name: "generator/mmbn3"; Description: "MegaMan Battle Network 3"; Types: full hosting; ExtraDiskSpaceRequired: 8388608; Flags: disablenouninstallwarning Name: "generator/ladx"; Description: "Link's Awakening DX ROM Setup"; Types: full hosting Name: "generator/tloz"; Description: "The Legend of Zelda ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 135168; Flags: disablenouninstallwarning Name: "server"; Description: "Server"; Types: full hosting @@ -81,6 +82,7 @@ Name: "client/ff1"; Description: "Final Fantasy 1"; Types: full playing Name: "client/pkmn"; Description: "Pokemon Client" Name: "client/pkmn/red"; Description: "Pokemon Client - Pokemon Red Setup"; Types: full playing; ExtraDiskSpaceRequired: 1048576 Name: "client/pkmn/blue"; Description: "Pokemon Client - Pokemon Blue Setup"; Types: full playing; ExtraDiskSpaceRequired: 1048576 +Name: "client/mmbn3"; Description: "MegaMan Battle Network 3 Client"; Types: full playing; Name: "client/ladx"; Description: "Link's Awakening Client"; Types: full playing; ExtraDiskSpaceRequired: 1048576 Name: "client/cf"; Description: "ChecksFinder"; Types: full playing Name: "client/sc2"; Description: "Starcraft 2"; Types: full playing @@ -105,6 +107,7 @@ Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda Source: "{code:GetZlROMPath}"; DestDir: "{app}"; DestName: "Zillion (UE) [!].sms"; Flags: external; Components: client/zl or generator/zl Source: "{code:GetRedROMPath}"; DestDir: "{app}"; DestName: "Pokemon Red (UE) [S][!].gb"; Flags: external; Components: client/pkmn/red or generator/pkmn_r Source: "{code:GetBlueROMPath}"; DestDir: "{app}"; DestName: "Pokemon Blue (UE) [S][!].gb"; Flags: external; Components: client/pkmn/blue or generator/pkmn_b +Source: "{code:GetBN3ROMPath}"; DestDir: "{app}"; DestName: "Mega Man Battle Network 3 - Blue Version (USA).gba"; Flags: external; Components: client/mmbn3 Source: "{code:GetLADXROMPath}"; DestDir: "{app}"; DestName: "Legend of Zelda, The - Link's Awakening DX (USA, Europe) (SGB Enhanced).gbc"; Flags: external; Components: client/ladx or generator/ladx Source: "{code:GetTLoZROMPath}"; DestDir: "{app}"; DestName: "Legend of Zelda, The (U) (PRG0) [!].nes"; Flags: external; Components: client/tloz or generator/tloz Source: "{code:GetAdvnROMPath}"; DestDir: "{app}"; DestName: "ADVNTURE.BIN"; Flags: external; Components: client/advn @@ -128,6 +131,7 @@ Source: "{#source_path}\ArchipelagoFF1Client.exe"; DestDir: "{app}"; Flags: igno Source: "{#source_path}\ArchipelagoPokemonClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/pkmn Source: "{#source_path}\ArchipelagoChecksFinderClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/cf Source: "{#source_path}\ArchipelagoStarcraft2Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sc2 +Source: "{#source_path}\ArchipelagoMMBN3Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/mmbn3 Source: "{#source_path}\ArchipelagoZelda1Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/tloz Source: "{#source_path}\ArchipelagoWargrooveClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/wargroove Source: "{#source_path}\ArchipelagoKH2Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/kh2 @@ -148,6 +152,7 @@ Name: "{group}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\Archipelag Name: "{group}\{#MyAppName} Pokemon Client"; Filename: "{app}\ArchipelagoPokemonClient.exe"; Components: client/pkmn Name: "{group}\{#MyAppName} ChecksFinder Client"; Filename: "{app}\ArchipelagoChecksFinderClient.exe"; Components: client/cf Name: "{group}\{#MyAppName} Starcraft 2 Client"; Filename: "{app}\ArchipelagoStarcraft2Client.exe"; Components: client/sc2 +Name: "{group}\{#MyAppName} MegaMan Battle Network 3 Client"; Filename: "{app}\ArchipelagoMMBN3Client.exe"; Components: client/mmbn3 Name: "{group}\{#MyAppName} The Legend of Zelda Client"; Filename: "{app}\ArchipelagoZelda1Client.exe"; Components: client/tloz Name: "{group}\{#MyAppName} Kingdom Hearts 2 Client"; Filename: "{app}\ArchipelagoKH2Client.exe"; Components: client/kh2 Name: "{group}\{#MyAppName} Adventure Client"; Filename: "{app}\ArchipelagoAdventureClient.exe"; Components: client/advn @@ -165,6 +170,7 @@ Name: "{commondesktop}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\Ar Name: "{commondesktop}\{#MyAppName} Pokemon Client"; Filename: "{app}\ArchipelagoPokemonClient.exe"; Tasks: desktopicon; Components: client/pkmn Name: "{commondesktop}\{#MyAppName} ChecksFinder Client"; Filename: "{app}\ArchipelagoChecksFinderClient.exe"; Tasks: desktopicon; Components: client/cf Name: "{commondesktop}\{#MyAppName} Starcraft 2 Client"; Filename: "{app}\ArchipelagoStarcraft2Client.exe"; Tasks: desktopicon; Components: client/sc2 +Name: "{commondesktop}\{#MyAppName} MegaMan Battle Network 3 Client"; Filename: "{app}\ArchipelagoMMBN3Client.exe"; Tasks: desktopicon; Components: client/mmbn3 Name: "{commondesktop}\{#MyAppName} The Legend of Zelda Client"; Filename: "{app}\ArchipelagoZelda1Client.exe"; Tasks: desktopicon; Components: client/tloz Name: "{commondesktop}\{#MyAppName} Wargroove Client"; Filename: "{app}\ArchipelagoWargrooveClient.exe"; Tasks: desktopicon; Components: client/wargroove Name: "{commondesktop}\{#MyAppName} Kingdom Hearts 2 Client"; Filename: "{app}\ArchipelagoKH2Client.exe"; Tasks: desktopicon; Components: client/kh2 @@ -249,6 +255,11 @@ Root: HKCR; Subkey: "{#MyAppName}pkmnbpatch"; ValueData: "Ar Root: HKCR; Subkey: "{#MyAppName}pkmnbpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoPokemonClient.exe,0"; ValueType: string; ValueName: ""; Components: client/pkmn Root: HKCR; Subkey: "{#MyAppName}pkmnbpatch\shell\open\command"; ValueData: """{app}\ArchipelagoPokemonClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/pkmn +Root: HKCR; Subkey: ".apbn3"; ValueData: "{#MyAppName}bn3bpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/mmbn3 +Root: HKCR; Subkey: "{#MyAppName}bn3bpatch"; ValueData: "Archipelago MegaMan Battle Network 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/mmbn3 +Root: HKCR; Subkey: "{#MyAppName}bn3bpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoMMBN3Client.exe,0"; ValueType: string; ValueName: ""; Components: client/mmbn3 +Root: HKCR; Subkey: "{#MyAppName}bn3bpatch\shell\open\command"; ValueData: """{app}\ArchipelagoMMBN3Client.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/mmbn3 + Root: HKCR; Subkey: ".apladx"; ValueData: "{#MyAppName}ladxpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/ladx Root: HKCR; Subkey: "{#MyAppName}ladxpatch"; ValueData: "Archipelago Links Awakening DX Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/ladx Root: HKCR; Subkey: "{#MyAppName}ladxpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoLinksAwakeningClient.exe,0"; ValueType: string; ValueName: ""; Components: client/ladx @@ -331,6 +342,9 @@ var RedROMFilePage: TInputFileWizardPage; var bluerom: string; var BlueROMFilePage: TInputFileWizardPage; +var bn3rom: string; +var BN3ROMFilePage: TInputFileWizardPage; + var ladxrom: string; var LADXROMFilePage: TInputFileWizardPage; @@ -450,7 +464,7 @@ begin '.gb'); end; -function AddSMSRomPage(name: string): TInputFileWizardPage; +function AddGBARomPage(name: string): TInputFileWizardPage; begin Result := CreateInputFilePage( @@ -458,7 +472,20 @@ begin 'Select ROM File', 'Where is your ' + name + ' located?', 'Select the file, then click Next.'); + Result.Add( + 'Location of ROM file:', + 'GBA ROM files|*.gba|All files|*.*', + '.gba'); +end; +function AddSMSRomPage(name: string): TInputFileWizardPage; +begin + Result := + CreateInputFilePage( + wpSelectComponents, + 'Select ROM File', + 'Where is your ' + name + ' located?', + 'Select the file, then click Next.'); Result.Add( 'Location of ROM file:', 'SMS ROM files|*.sms|All files|*.*', @@ -541,6 +568,8 @@ begin Result := not (L2ACROMFilePage.Values[0] = '') else if (assigned(OoTROMFilePage)) and (CurPageID = OoTROMFilePage.ID) then Result := not (OoTROMFilePage.Values[0] = '') + else if (assigned(BN3ROMFilePage)) and (CurPageID = BN3ROMFilePage.ID) then + Result := not (BN3ROMFilePage.Values[0] = '') else if (assigned(ZlROMFilePage)) and (CurPageID = ZlROMFilePage.ID) then Result := not (ZlROMFilePage.Values[0] = '') else if (assigned(RedROMFilePage)) and (CurPageID = RedROMFilePage.ID) then @@ -765,6 +794,22 @@ begin Result := ''; end; +function GetBN3ROMPath(Param: string): string; +begin + if Length(bn3rom) > 0 then + Result := bn3rom + else if Assigned(BN3ROMFilePage) then + begin + R := CompareStr(GetMD5OfFile(BN3ROMFilePage.Values[0]), '6fe31df0144759b34ad666badaacc442') + if R <> 0 then + MsgBox('MegaMan Battle Network 3 Blue ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := BN3ROMFilePage.Values[0] + end + else + Result := ''; + end; + procedure InitializeWizard(); begin AddOoTRomPage(); @@ -801,6 +846,10 @@ begin if Length(bluerom) = 0 then BlueROMFilePage:= AddGBRomPage('Pokemon Blue (UE) [S][!].gb'); + bn3rom := CheckRom('Mega Man Battle Network 3 - Blue Version (USA).gba','6fe31df0144759b34ad666badaacc442'); + if Length(bn3rom) = 0 then + BN3ROMFilePage:= AddGBARomPage('Mega Man Battle Network 3 - Blue Version (USA).gba'); + ladxrom := CheckRom('Legend of Zelda, The - Link''s Awakening DX (USA, Europe) (SGB Enhanced).gbc','07c211479386825042efb4ad31bb525f'); if Length(ladxrom) = 0 then LADXROMFilePage:= AddGBRomPage('Legend of Zelda, The - Link''s Awakening DX (USA, Europe) (SGB Enhanced).gbc'); @@ -842,6 +891,8 @@ begin Result := not (WizardIsComponentSelected('generator/pkmn_r') or WizardIsComponentSelected('client/pkmn/red')); if (assigned(BlueROMFilePage)) and (PageID = BlueROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/pkmn_b') or WizardIsComponentSelected('client/pkmn/blue')); + if (assigned(BN3ROMFilePage)) and (PageID = BN3ROMFilePage.ID) then + Result := not (WizardIsComponentSelected('generator/mmbn3') or WizardIsComponentSelected('client/mmbn3')); if (assigned(LADXROMFilePage)) and (PageID = LADXROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/ladx') or WizardIsComponentSelected('client/ladx')); if (assigned(TLoZROMFilePage)) and (PageID = TLoZROMFilePage.ID) then diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py index b266bef1fa14..c3ae2b0495b0 100644 --- a/worlds/LauncherComponents.py +++ b/worlds/LauncherComponents.py @@ -116,6 +116,9 @@ def launch_textclient(): file_identifier=SuffixIdentifier('.apzl')), # Kingdom Hearts 2 Component('KH2 Client', "KH2Client"), + + #MegaMan Battle Network 3 + Component('MMBN3 Client', 'MMBN3Client', file_identifier=SuffixIdentifier('.apbn3')) ] diff --git a/worlds/mmbn3/BN3RomUtils.py b/worlds/mmbn3/BN3RomUtils.py new file mode 100644 index 000000000000..ede867823a8e --- /dev/null +++ b/worlds/mmbn3/BN3RomUtils.py @@ -0,0 +1,194 @@ +import re + +from .Items import ItemType + +ArchiveToSizeUncomp = {0x684F9C: 0xED, 0x7043EC: 0xA25, 0x704E14: 0x4DE, 0x7052F4: 0x236A, 0x707660: 0x13FB, 0x708A5C: 0x70, 0x708ACC: 0x264, 0x708D30: 0x8D0, 0x709600: 0xE96, 0x70A498: 0xEB5, 0x70B350: 0xE9B, 0x70C1EC: 0xE8A, 0x70D078: 0xEC6, 0x70DF40: 0xEE2, 0x70EE24: 0xEAA, 0x70FCD0: 0x9B1, 0x710684: 0x765, 0x710DEC: 0x4F6, 0x7112E4: 0x9C8, 0x711CAC: 0x66A, 0x713F50: 0x48, 0x713F98: 0xDF, 0x714078: 0x17C, 0x7141F4: 0x45F, 0x714654: 0x13F, 0x714794: 0x249B, 0x716C30: 0x2225, 0x718E58: 0x1DA3, 0x71ABFC: 0x1D51, 0x71C950: 0x1495, 0x71DDE8: 0x12B3, 0x71F09C: 0x15B3, 0x720650: 0x607, 0x720C58: 0x3E0, 0x721038: 0x208, 0x721240: 0x428, 0x721668: 0x3FF, 0x721A68: 0x23FE, 0x723E68: 0x117, 0x723F80: 0x64B, 0x7245CC: 0x15A, 0x724728: 0x250C, 0x726C34: 0x1EE4, 0x728B18: 0x1E65, 0x72A980: 0x18F8, 0x72C278: 0xA16, 0x72CC90: 0x4C, 0x72CCDC: 0xE1E, 0x75DBE4: 0x9A, 0x778494: 0x2A8, 0x77873C: 0x303, 0x778A40: 0x1A8, 0x778BE8: 0x74B, 0x779334: 0x2AE, 0x7795E4: 0xB5, 0x77969C: 0x566, 0x779C04: 0x235, 0x779E3C: 0x211, 0x77A050: 0x352, 0x77A3A4: 0x1A8, 0x77A54C: 0x171, 0x77A6C0: 0x124, 0x77A7E4: 0x24D, 0x77AA34: 0x5D9, 0x77B010: 0x167, 0x77B178: 0xD4, 0x77B24C: 0xF0, 0x77B33C: 0x2E9, 0x77B628: 0x12B, 0x77B754: 0x205, 0x77B95C: 0xF6, 0x77BA54: 0xFB, 0x77BB50: 0x326, 0x77BE78: 0x216, 0x77C090: 0x4D0, 0x77C560: 0x1A1, 0x77C704: 0x71, 0x77C778: 0x3D, 0x77C7B8: 0x2D, 0x77C7E8: 0x47, 0x77C830: 0x4C, 0x77C87C: 0x4B, 0x77C8C8: 0x9C, 0x77C964: 0x1FC, 0x77CB60: 0x1A6, 0x77CD08: 0x45D, 0x77D168: 0x2DE, 0x77D448: 0x479, 0x77D8C4: 0x170, 0x77DA34: 0x1CC, 0x77DC00: 0x3E2, 0x77DFE4: 0x158, 0x77E13C: 0x2D6, 0x77E414: 0x195, 0x77E5AC: 0x2C7, 0x77E874: 0x810, 0x77F084: 0xFB, 0x77F180: 0x182, 0x77F304: 0x1FC, 0x77F500: 0xF3, 0x77F5F4: 0xC9, 0x77F6C0: 0x1D0, 0x77F890: 0x114, 0x77F9A4: 0x503, 0x77FEA8: 0x517, 0x7803C0: 0x99, 0x78045C: 0x43C, 0x780898: 0x20C, 0x780AA4: 0x1B8, 0x780C5C: 0x342, 0x780FA0: 0x1A5, 0x781148: 0x70, 0x7811B8: 0x521, 0x7816DC: 0x5F8, 0x781CD4: 0x3E3, 0x7820B8: 0x7C8, 0x782880: 0x280, 0x782B00: 0x77D, 0x783280: 0x1C8, 0x783448: 0x72E, 0x783B78: 0x4C0, 0x784038: 0x3DB, 0x784414: 0x3B0, 0x7847C4: 0x1EC, 0x7849B0: 0x4F1, 0x784EA4: 0x544, 0x7853E8: 0x1B4, 0x78559C: 0x1DB, 0x785778: 0x1E3, 0x78595C: 0x162, 0x785AC0: 0x2A3, 0x785D64: 0x41B, 0x786180: 0x4AB, 0x78662C: 0x1C6, 0x7867F4: 0x202, 0x7869F8: 0x52F, 0x786F28: 0x236, 0x787160: 0x479, 0x7875DC: 0x259, 0x787838: 0x1D0, 0x787A08: 0x720, 0x788128: 0x299, 0x7883C4: 0x3C0, 0x788784: 0xB6F, 0x7892F4: 0x3D0, 0x7896C4: 0x362, 0x789A28: 0x244, 0x789C6C: 0x496, 0x78A104: 0x36F, 0x78A474: 0x2B6, 0x78A72C: 0x1D8, 0x78A904: 0x1D9, 0x78AAE0: 0x1EC, 0x78ACCC: 0x1DA, 0x78AEA8: 0x557, 0x78B400: 0x11C, 0x78B51C: 0x5A8, 0x78BAC4: 0x420, 0x78BEE4: 0x10D, 0x78BFF4: 0x505, 0x78C4FC: 0x7FB, 0x78CCF8: 0x546, 0x78D240: 0x74F, 0x78D990: 0x12B, 0x78DABC: 0x69D, 0x78E15C: 0x91A, 0x78EA78: 0x820, 0x78F298: 0x662, 0x78F8FC: 0x52F, 0x78FE2C: 0x4AD, 0x7902DC: 0x572, 0x790850: 0x393, 0x790BE4: 0x662, 0x791248: 0x506, 0x791750: 0x98, 0x7917E8: 0x8AF, 0x792098: 0x1C1, 0x79225C: 0x610, 0x79286C: 0x6A3, 0x792F10: 0xFC, 0x79300C: 0x317, 0x793324: 0x750, 0x793A74: 0x216, 0x793C8C: 0x762, 0x7943F0: 0x4B7, 0x7948A8: 0x68E, 0x794F38: 0x233, 0x79516C: 0x24A, 0x7953B8: 0x1CD, 0x795588: 0x2FA, 0x795884: 0x454, 0x795CD8: 0x90, 0x795D68: 0x61, 0x795DCC: 0x10B, 0x795ED8: 0xD8, 0x795FB0: 0x723, 0x7966D4: 0x4D, 0x796724: 0x367, 0x796A8C: 0x81, 0x796B10: 0x2A2, 0x796DB4: 0x93, 0x796E48: 0x2D2, 0x79711C: 0x577, 0x797694: 0x69A, 0x797D30: 0x34F, 0x798080: 0x582, 0x798604: 0x528, 0x798B2C: 0x60A, 0x799138: 0x1A9, 0x7992E4: 0x1CB, 0x7994B0: 0x711, 0x799BC4: 0x3A3, 0x799F68: 0x4B2, 0x79A41C: 0x11E, 0x79A53C: 0xEA, 0x79A628: 0x298, 0x79A8C0: 0x1F3, 0x79AAB4: 0x24E, 0x79AD04: 0x42E, 0x79B134: 0x1E1, 0x79B318: 0x8FF, 0x79BC18: 0x120, 0x79BD38: 0x107, 0x79BE40: 0x116, 0x79BF58: 0x11C, 0x79C074: 0x1FE, 0x79C274: 0x18B, 0x79C400: 0x189, 0x79C58C: 0x28A, 0x79C818: 0x1A2, 0x79C9BC: 0x22F, 0x79CBEC: 0x542, 0x79D130: 0x89B, 0x79D9CC: 0x220, 0x79DBEC: 0x245, 0x79DE34: 0x417, 0x79E24C: 0x14E, 0x79E39C: 0x6BE, 0x79EA5C: 0xA2D, 0x79F48C: 0x90B, 0x79FD98: 0x688, 0x7A0420: 0x585, 0x7A09A8: 0x196, 0x7A0B40: 0x28F, 0x7A0DD0: 0x4BC, 0x7A128C: 0x32E, 0x7A15BC: 0x353, 0x7A1910: 0x5CD, 0x7A1EE0: 0x4E0, 0x7A23C0: 0xDD, 0x7A24A0: 0x220, 0x7A26C0: 0x16C, 0x7A282C: 0xA20, 0x7A324C: 0x31D, 0x7A356C: 0x6B4, 0x7A3C20: 0x125, 0x7A3D48: 0x6DA, 0x7A4424: 0x601, 0x7A4A28: 0x147, 0x7A4B70: 0x1BF, 0x7A4D30: 0x445, 0x7A5178: 0xFC, 0x7A5274: 0x260, 0x7A54D4: 0x31A, 0x7A57F0: 0xA5F, 0x7A6250: 0x440, 0x7A6690: 0x26A, 0x7A68FC: 0x795, 0x7A7094: 0x354, 0x7A73E8: 0xC14, 0x7A7FFC: 0x5FA, 0x7A85F8: 0x198, 0x7A8790: 0x72F, 0x7A8EC0: 0x149, 0x7A900C: 0x8FB, 0x7A9908: 0x739, 0x7AA044: 0x300, 0x7AA344: 0x45C, 0x7AA7A0: 0xB6, 0x7AA858: 0x3B8, 0x7AAC10: 0x209, 0x7AAE1C: 0x117, 0x7AAF34: 0x113, 0x7AB048: 0x275, 0x7AB2C0: 0x3A, 0x7AB2FC: 0x1AC, 0x7AB4A8: 0x11D, 0x7AB5C8: 0x55E, 0x7ABB28: 0xA0, 0x7ABBC8: 0x27F, 0x7ABE48: 0x57, 0x7ABEA0: 0x48C, 0x7AC32C: 0x14B, 0x7AC478: 0x7E, 0x7AC4F8: 0xB6, 0x7AC5B0: 0x2CB, 0x7AC87C: 0x7C, 0x7AC8F8: 0x151, 0x7ACA4C: 0x305, 0x7ACD54: 0x685, 0x7AD3DC: 0x1CE, 0x7AD5AC: 0x745, 0x7ADCF4: 0x518, 0x7AE20C: 0x1BA, 0x7AE3C8: 0xB65, 0x7AEF30: 0x7E8, 0x7AF718: 0x4C2, 0x7AFBDC: 0x24BC, 0x7B2098: 0x7CA, 0x7B2864: 0x861, 0x7B30C8: 0x255, 0x7B3320: 0x1B1, 0x7B34D4: 0x240, 0x7B3714: 0x11F, 0x7B3834: 0x43D, 0x7B3C74: 0x522, 0x7B4198: 0x2ED, 0x7B4488: 0x58C, 0x7B4A14: 0x5BA, 0x7CB3F0: 0x9B3, 0x7EA2C8: 0xBE5, 0x7EAEB0: 0xF0, 0x7EAFA0: 0x1FA, 0x7EB19C: 0x7D7, 0x7EB974: 0x133, 0x7EBAA8: 0xB58, 0x7EC600: 0x959, 0x7EF468: 0x672, 0x7F0B40: 0x6F, 0x7F95F8: 0x21, 0x7F961C: 0x2D1, 0x7F98F0: 0x181, 0x7FAC00: 0x4C1} +ArchiveToSizeComp = {0x712318: 0xB24, 0x712E3C: 0x4D5, 0x713314: 0xC3C, 0x72DAFC: 0x1F7F, 0x72FA7C: 0x1E2A, 0x7318A8: 0x9B9, 0x732264: 0xD72, 0x732FD8: 0x41C, 0x7333F4: 0x57A, 0x733970: 0x95B, 0x7342CC: 0x7DB, 0x734AA8: 0xC89, 0x735734: 0xBBF, 0x7362F4: 0x1340, 0x737634: 0xC7C, 0x7382B0: 0x9B4, 0x738C64: 0x91A, 0x739580: 0xBCA, 0x73A14C: 0x6A9, 0x73A7F8: 0x426, 0x73AC20: 0xDA7, 0x73B9C8: 0x158A, 0x73CF54: 0x1A8E, 0x73E9E4: 0xD55, 0x73F73C: 0x1BF, 0x73F8FC: 0xDA1, 0x7406A0: 0x269F, 0x742D40: 0x2747, 0x745488: 0x10ED, 0x746578: 0xE7F, 0x7473F8: 0x721, 0x747B1C: 0xD77, 0x748894: 0xBAE, 0x749444: 0x162A, 0x74AA70: 0x1096, 0x74BB08: 0x1B3, 0x74BCBC: 0x5BF, 0x74C27C: 0x1F08, 0x74E184: 0x1927, 0x74FAAC: 0x79B, 0x750248: 0xA52, 0x750C9C: 0x472, 0x751110: 0x89E, 0x7519B0: 0x13CE, 0x752D80: 0xCC5, 0x753A48: 0x466, 0x753EB0: 0x4E1, 0x754394: 0x96C, 0x754D00: 0x1372, 0x756074: 0xBAC, 0x756C20: 0xB01, 0x757724: 0xFD2, 0x7586F8: 0x30D, 0x758A08: 0xC6, 0x758AD0: 0x8B4, 0x759384: 0x33D, 0x7596C4: 0x34B, 0x759A10: 0x1A3, 0x759BB4: 0x44, 0x759BF8: 0x4BC, 0x75A0B4: 0x743, 0x75A7F8: 0x5AD, 0x75ADA8: 0x842, 0x75B5EC: 0x8C4, 0x75BEB0: 0x9B1, 0x75C864: 0x4FE, 0x75CD64: 0x29F, 0x75D004: 0x1B8, 0x75D1BC: 0x21F, 0x75D3DC: 0x252, 0x75D630: 0x15E, 0x75D790: 0x15A, 0x75D8EC: 0x17A, 0x75DA68: 0x136, 0x75DBA0: 0x44, 0x75DC80: 0x44, 0x75DCC4: 0x1E0, 0x75DEA4: 0x171, 0x75E018: 0x2BB, 0x75E2D4: 0x119, 0x75E3F0: 0x44B, 0x75E83C: 0x56D, 0x75EDAC: 0x58D, 0x75F33C: 0x59C, 0x75F8D8: 0x5B7, 0x75FE90: 0x458, 0x7602E8: 0x110, 0x7603F8: 0x1A2, 0x76059C: 0x146, 0x7606E4: 0x1A3, 0x760888: 0x5A, 0x7608E4: 0x264, 0x760B48: 0x335, 0x760E80: 0x615, 0x761498: 0x4BB, 0x761954: 0x16B, 0x761AC0: 0x14E, 0x761C10: 0x1EE, 0x761E00: 0x94A, 0x76274C: 0x2B8, 0x762A04: 0x2EA, 0x762CF0: 0x43B, 0x76312C: 0x188, 0x7632B4: 0x16A, 0x763420: 0x1DC, 0x7635FC: 0x17D, 0x76377C: 0x18B, 0x763908: 0x1A9, 0x763AB4: 0x1D3, 0x763C88: 0x24E, 0x763ED8: 0x2C9, 0x7641A4: 0x211, 0x7643B8: 0x1542, 0x7658FC: 0x11E1, 0x766AE0: 0xE46, 0x767928: 0x1B8B, 0x7694B4: 0x103E, 0x76A4F4: 0x14B0, 0x76B9A4: 0x180A, 0x76D1B0: 0xAD0, 0x76DC80: 0xE62, 0x76EAE4: 0x1484, 0x76FF68: 0xB27, 0x770A90: 0x7BA, 0x77124C: 0x164A, 0x772898: 0x7E4, 0x77307C: 0x682, 0x773700: 0x7A6, 0x773EA8: 0x820, 0x7746C8: 0x8FF, 0x774FC8: 0x3C5, 0x775390: 0x5A1, 0x775934: 0x643, 0x775F78: 0x1254, 0x7771CC: 0x6BC, 0x777888: 0x651, 0x777EDC: 0x5B8, 0x7E7618: 0xAD5, 0x7E80F0: 0xABA, 0x7E8BAC: 0x3EC, 0x7E8F98: 0x132E, 0x7EE108: 0x1D2, 0x7EE2DC: 0x1DD, 0x7EE4BC: 0x2DB, 0x7EE798: 0x1D2, 0x7EE96C: 0x1DD, 0x7EEB4C: 0x1D4, 0x7EED20: 0x1CB, 0x7EEEEC: 0x1D1, 0x7EF0C0: 0x1D1, 0x7EF294: 0x1D2, 0x7F0BB0: 0x220, 0x7F0DD0: 0x19BB, 0x7F278C: 0x261, 0x7F29F0: 0x1138, 0x7F3B28: 0x210, 0x7F3D38: 0x1228, 0x7F4F60: 0x1E8, 0x7F5148: 0xEF0, 0x7F6038: 0x1BF, 0x7F61F8: 0xD87, 0x7F6F80: 0x1B3, 0x7F7134: 0xA73, 0x7F7BA8: 0x117, 0x7F7CC0: 0x770, 0x800000: 0xE87} +ArchiveToReferences = {0x684F9C: [0x02E38C, 0x12BC7C, 0x1301B8], 0x7043EC: [0x00E858, 0x00E998, 0x00ED8C, 0x010774, 0x010FA0, 0x0157F8, 0x027ABC, 0x02E398, 0x0334DC, 0x0337D8, 0x033B44, 0x0445FC], 0x704E14: [0x00E85C, 0x00E99C, 0x00ED90, 0x010778, 0x010FA4, 0x0157FC, 0x027AC0, 0x02E39C, 0x0334E0, 0x0337DC, 0x033B48, 0x044600], 0x7052F4: [0x00E46C, 0x02E3A4, 0x030368, 0x0447D4], 0x707660: [0x00E470, 0x02E3A8, 0x03036C, 0x0447D8], 0x708A5C: [0x00E860, 0x01580C, 0x027AC4, 0x04460C], 0x708ACC: [0x0069B4, 0x006DF8, 0x00E3CC, 0x00E5C0, 0x00E624, 0x010AA0, 0x0111B4, 0x0156C8], 0x708D30: [0x016094], 0x709600: [0x015BE4], 0x70A498: [0x015BE8], 0x70B350: [0x015BEC], 0x70C1EC: [0x015BF0], 0x70D078: [0x015BF4], 0x70DF40: [0x015BF8], 0x70EE24: [0x015BFC], 0x70FCD0: [0x00A168], 0x710684: [0x00A16C], 0x710DEC: [0x00A170], 0x7112E4: [0x0110D0, 0x027AD0, 0x13036C, 0x1306C4], 0x711CAC: [0x027AB8, 0x031F48, 0x033384, 0x035D54, 0x03D974, 0x044568], 0x712318: [0x033970, 0x0443CC], 0x712E3C: [0x033974], 0x713314: [], 0x713F50: [], 0x713F98: [0x12B914], 0x714078: [0x12B354], 0x7141F4: [0x12F4E0], 0x714654: [0x12D9B0], 0x714794: [0x028B1C, 0x028B20, 0x028B24, 0x028B28, 0x028B2C, 0x028B30, 0x028B34, 0x028B38, 0x028B3C], 0x716C30: [0x028B40, 0x028B44, 0x028B48, 0x028B4C, 0x028B50, 0x028B54, 0x028B58], 0x718E58: [0x028B5C, 0x028B60, 0x028B64, 0x028B68, 0x028B6C], 0x71ABFC: [0x028B70, 0x028B74, 0x028B78, 0x028B7C, 0x028B80, 0x028B84, 0x028B88, 0x028B8C, 0x028B90, 0x028B94], 0x71C950: [0x028B98, 0x028B9C, 0x028BA0, 0x028BA4, 0x028BA8, 0x028BAC, 0x028BB0], 0x71DDE8: [0x028BB4, 0x028BB8, 0x028BBC, 0x028BC0], 0x71F09C: [0x028BC4, 0x028BC8, 0x028BCC, 0x028BD0, 0x028BD4, 0x028BD8], 0x720650: [0x028BDC, 0x028BE0, 0x028BE4, 0x028BE8, 0x028BEC, 0x028BF0], 0x720C58: [0x028BF4, 0x028BF8], 0x721038: [0x028BFC, 0x028C00, 0x028C04, 0x028C08], 0x721240: [0x028C0C, 0x028C10, 0x028C14, 0x028C18, 0x028C1C], 0x721668: [0x028C20, 0x028C24, 0x028C28, 0x028C2C, 0x028C30], 0x721A68: [0x028C34, 0x028C38, 0x028C3C, 0x028C40, 0x028C44, 0x028C54, 0x028C58, 0x028C5C, 0x028C60, 0x028C64, 0x028C68], 0x723E68: [0x028C6C, 0x028C70, 0x028C74, 0x028C78, 0x028C7C, 0x028C80, 0x028C84, 0x028C88, 0x028C8C, 0x028C90, 0x028C94, 0x028C98, 0x028C9C, 0x028CA0, 0x028CA4, 0x028CA8], 0x723F80: [0x028CAC, 0x028CB0, 0x028CB4, 0x028CB8, 0x028CBC, 0x028CC0, 0x028CC4, 0x028CC8, 0x028CCC, 0x028CD0, 0x028CD4, 0x028CD8, 0x028CDC, 0x028CE0, 0x028CE4, 0x028CE8], 0x7245CC: [0x028CEC, 0x028CF0, 0x028CF4, 0x028CF8, 0x028CFC], 0x724728: [0x028D00, 0x028D04, 0x028D08, 0x028D0C], 0x726C34: [0x028D10, 0x028D14, 0x028D18], 0x728B18: [0x028D1C, 0x028D20, 0x028D24], 0x72A980: [0x028D28, 0x028D2C, 0x028D30, 0x028D34], 0x72C278: [0x028D38, 0x028D3C, 0x028D40, 0x028D44, 0x028D48, 0x028D4C, 0x028D50, 0x028D54], 0x72CC90: [0x028D58, 0x028D5C, 0x028D60], 0x72CCDC: [0x0266F8], 0x72DAFC: [0x028854], 0x72FA7C: [0x02664C], 0x7318A8: [0x028858], 0x732264: [0x02885C], 0x732FD8: [0x028860], 0x7333F4: [0x028864], 0x733970: [0x028868], 0x7342CC: [0x02886C], 0x734AA8: [0x028870], 0x735734: [0x028874], 0x7362F4: [0x028878], 0x737634: [0x02887C], 0x7382B0: [0x028880], 0x738C64: [0x028884], 0x739580: [0x028888], 0x73A14C: [0x02888C], 0x73A7F8: [0x028890], 0x73AC20: [0x028894], 0x73B9C8: [0x028898], 0x73CF54: [0x02889C], 0x73E9E4: [0x0288A0], 0x73F73C: [0x0288A4], 0x73F8FC: [0x0288A8], 0x7406A0: [0x0288AC], 0x742D40: [0x026654], 0x745488: [0x0288B0], 0x746578: [0x0288B4], 0x7473F8: [0x0288B8], 0x747B1C: [0x0288BC], 0x748894: [0x0288C0], 0x749444: [0x0288C4], 0x74AA70: [0x0288C8], 0x74BB08: [0x0288CC], 0x74BCBC: [0x0288D0], 0x74C27C: [0x0288D4], 0x74E184: [0x0288D8], 0x74FAAC: [0x0288DC], 0x750248: [0x0288E0], 0x750C9C: [0x0288E4], 0x751110: [0x0288E8], 0x7519B0: [0x0288EC], 0x752D80: [0x0288F0], 0x753A48: [0x0288F4], 0x753EB0: [0x0288F8], 0x754394: [0x0288FC], 0x754D00: [0x028900], 0x756074: [0x028904], 0x756C20: [0x028908], 0x757724: [0x02890C], 0x7586F8: [0x028910], 0x758A08: [0x028914], 0x758AD0: [0x028918], 0x759384: [0x02891C], 0x7596C4: [0x028920], 0x759A10: [0x028924], 0x759BB4: [0x028928], 0x759BF8: [0x02892C], 0x75A0B4: [0x028930], 0x75A7F8: [0x028934], 0x75ADA8: [0x028938], 0x75B5EC: [0x02893C], 0x75BEB0: [0x028940], 0x75C864: [0x028944], 0x75CD64: [0x028948], 0x75D004: [0x02894C], 0x75D1BC: [0x028950], 0x75D3DC: [0x028954], 0x75D630: [0x028958], 0x75D790: [0x02895C], 0x75D8EC: [0x028960], 0x75DA68: [0x028964], 0x75DBA0: [0x028968], 0x75DBE4: [0x12A5E8, 0x12A7B4], 0x75DC80: [0x02896C], 0x75DCC4: [0x028970], 0x75DEA4: [0x028974], 0x75E018: [0x028978], 0x75E2D4: [0x02897C], 0x75E3F0: [0x02898C], 0x75E83C: [0x028990], 0x75EDAC: [0x028994], 0x75F33C: [0x028998], 0x75F8D8: [0x02899C], 0x75FE90: [0x0289A0], 0x7602E8: [0x0289A4], 0x7603F8: [0x0289A8], 0x76059C: [0x0289AC], 0x7606E4: [0x0289B0], 0x760888: [0x0289C4], 0x7608E4: [0x0289E4], 0x760B48: [0x0289E8], 0x760E80: [0x0289EC], 0x761498: [0x0289F0], 0x761954: [0x0289F4], 0x761AC0: [0x0289F8], 0x761C10: [0x0289FC], 0x761E00: [0x028A00], 0x76274C: [0x028A04], 0x762A04: [0x028A08], 0x762CF0: [0x028A0C], 0x76312C: [0x028A10], 0x7632B4: [0x028A14], 0x763420: [0x028A18], 0x7635FC: [0x028A1C], 0x76377C: [0x028A20], 0x763908: [0x028A24], 0x763AB4: [0x028A28], 0x763C88: [0x028A2C], 0x763ED8: [0x028A30], 0x7641A4: [0x028A34], 0x7643B8: [0x028A38], 0x7658FC: [0x028A3C], 0x766AE0: [0x028A40], 0x767928: [0x028A44], 0x7694B4: [0x028A48], 0x76A4F4: [0x028A4C], 0x76B9A4: [0x028A50], 0x76D1B0: [0x028A54], 0x76DC80: [0x028A58], 0x76EAE4: [0x028A5C], 0x76FF68: [0x028A60], 0x770A90: [0x028A64], 0x77124C: [0x028A68], 0x772898: [0x028A6C], 0x77307C: [0x028A70], 0x773700: [0x028A74], 0x773EA8: [0x028A78], 0x7746C8: [0x028A7C], 0x774FC8: [0x028A80], 0x775390: [0x028A84], 0x775934: [0x028A88], 0x775F78: [0x028A8C], 0x7771CC: [0x028A90], 0x777888: [0x028A94], 0x777EDC: [0x028A98], 0x778494: [0x0FE9FC], 0x77873C: [0x0FEB6C], 0x778A40: [0x0FECA4], 0x778BE8: [0x0FEFEC], 0x779334: [0x0FF208], 0x7795E4: [0x0FF3FC], 0x77969C: [0x0FF654], 0x779C04: [0x0FF7D4], 0x779E3C: [0x0FFA70], 0x77A050: [0x0FFD84, 0x1001B8], 0x77A3A4: [0x10038C], 0x77A54C: [0x100584], 0x77A6C0: [0x100724], 0x77A7E4: [0x10096C], 0x77AA34: [0x100E04], 0x77B010: [0x100F50], 0x77B178: [0x10126C], 0x77B24C: [0x10139C], 0x77B33C: [0x101E1C], 0x77B628: [0x101F40], 0x77B754: [0x102090], 0x77B95C: [0x1023A4], 0x77BA54: [0x101514], 0x77BB50: [0x101B68], 0x77BE78: [0x101DAC], 0x77C090: [0x102680, 0x102994], 0x77C560: [0x102BB8], 0x77C704: [0x102C24], 0x77C778: [0x102C2C], 0x77C7B8: [0x102C34], 0x77C7E8: [0x102C28], 0x77C830: [0x102C30], 0x77C87C: [0x102C38], 0x77C8C8: [0x102E98], 0x77C964: [0x1031A0], 0x77CB60: [0x1034D8], 0x77CD08: [0x103704], 0x77D168: [0x1038D0], 0x77D448: [0x103B80], 0x77D8C4: [0x103D84], 0x77DA34: [0x103ED4], 0x77DC00: [0x104190], 0x77DFE4: [0x104270], 0x77E13C: [0x1044C8], 0x77E414: [0x1046E4], 0x77E5AC: [0x104B84], 0x77E874: [0x1050D8], 0x77F084: [0x105288], 0x77F180: [0x105430], 0x77F304: [0x105608], 0x77F500: [0x1057E4], 0x77F5F4: [0x105A10], 0x77F6C0: [0x105C44], 0x77F890: [0x105E48], 0x77F9A4: [0x106090, 0x106450, 0x1065B4], 0x77FEA8: [0x106AEC], 0x7803C0: [0x106C20], 0x78045C: [0x106DD0], 0x780898: [0x10705C], 0x780AA4: [0x1074D0], 0x780C5C: [0x10777C], 0x780FA0: [0x107970], 0x781148: [0x108074, 0x10818C], 0x7811B8: [0x108490], 0x7816DC: [0x10859C], 0x781CD4: [0x108720], 0x7820B8: [0x108A50], 0x782880: [0x108C0C], 0x782B00: [0x108DC0], 0x783280: [0x10901C], 0x783448: [0x109308], 0x783B78: [0x10956C], 0x784038: [0x1097F0], 0x784414: [0x109940], 0x7847C4: [0x109A60], 0x7849B0: [0x109D40], 0x784EA4: [0x10A190], 0x7853E8: [0x10A350], 0x78559C: [0x10A538], 0x785778: [0x10A768], 0x78595C: [0x10A8FC], 0x785AC0: [0x10AAB0], 0x785D64: [0x10B100], 0x786180: [0x10B4C8], 0x78662C: [0x10B728], 0x7867F4: [0x10B888], 0x7869F8: [0x10BBA8], 0x786F28: [0x10BDB4], 0x787160: [0x10BF8C, 0x10C030, 0x10C418], 0x7875DC: [0x10C584], 0x787838: [0x10C748], 0x787A08: [0x10C9F0], 0x788128: [0x10CCB8], 0x7883C4: [0x10CFA0], 0x788784: [0x10D534], 0x7892F4: [0x10D7B0], 0x7896C4: [0x10DAA8], 0x789A28: [0x10DC4C], 0x789C6C: [0x10DFF0], 0x78A104: [0x10E4C0], 0x78A474: [0x10E6E8], 0x78A72C: [0x10E968], 0x78A904: [0x10E96C], 0x78AAE0: [0x10E970], 0x78ACCC: [0x10E974], 0x78AEA8: [0x10ED04], 0x78B400: [0x10EDE4], 0x78B51C: [0x10F17C], 0x78BAC4: [0x10F620], 0x78BEE4: [0x10F8D4], 0x78BFF4: [0x10FBD0], 0x78C4FC: [0x10FFB8], 0x78CCF8: [0x11023C], 0x78D240: [0x1104A8], 0x78D990: [0x110598], 0x78DABC: [0x110C0C], 0x78E15C: [0x110FE4], 0x78EA78: [0x1113D4], 0x78F298: [0x1116E4], 0x78F8FC: [0x111964], 0x78FE2C: [0x111CC4], 0x7902DC: [0x111E18], 0x790850: [0x112054], 0x790BE4: [0x11226C], 0x791248: [0x112484], 0x791750: [0x112624], 0x7917E8: [0x11295C], 0x792098: [0x112BCC], 0x79225C: [0x112EDC], 0x79286C: [0x113030], 0x792F10: [0x113248], 0x79300C: [0x11350C], 0x793324: [0x113834], 0x793A74: [0x113A70], 0x793C8C: [0x113CA0], 0x7943F0: [0x113EF4], 0x7948A8: [0x114068], 0x794F38: [0x11420C], 0x79516C: [0x114428], 0x7953B8: [0x1147BC], 0x795588: [0x114AB4], 0x795884: [0x114DF0], 0x795CD8: [0x114F18], 0x795D68: [0x115034], 0x795DCC: [0x1151F8], 0x795ED8: [0x115364], 0x795FB0: [0x115844], 0x7966D4: [0x11596C], 0x796724: [0x115B20], 0x796A8C: [0x115C7C], 0x796B10: [0x115E30], 0x796DB4: [0x115F48], 0x796E48: [0x1160A0], 0x79711C: [0x116508], 0x797694: [0x116A54], 0x797D30: [0x116C20], 0x798080: [0x116E68], 0x798604: [0x11715C], 0x798B2C: [0x117510], 0x799138: [0x117688], 0x7992E4: [0x11768C], 0x7994B0: [0x11796C, 0x117CAC], 0x799BC4: [0x117E50], 0x799F68: [0x118050], 0x79A41C: [0x118210], 0x79A53C: [0x11838C], 0x79A628: [0x11858C], 0x79A8C0: [0x1188D4], 0x79AAB4: [0x118B30], 0x79AD04: [0x118DC8], 0x79B134: [0x118F54], 0x79B318: [0x119154], 0x79BC18: [0x119270], 0x79BD38: [0x119274], 0x79BE40: [0x119278], 0x79BF58: [0x11927C], 0x79C074: [0x1193C8], 0x79C274: [0x119538], 0x79C400: [0x119844], 0x79C58C: [0x119C80], 0x79C818: [0x119E9C], 0x79C9BC: [0x11A044], 0x79CBEC: [0x11A19C, 0x11A530], 0x79D130: [0x11A868, 0x11ABD8], 0x79D9CC: [0x11AE90], 0x79DBEC: [0x11B124], 0x79DE34: [0x11B320], 0x79E24C: [0x11B460], 0x79E39C: [0x11B6C0], 0x79EA5C: [0x11B984], 0x79F48C: [0x11BCD0], 0x79FD98: [0x11BE40], 0x7A0420: [0x11C188], 0x7A09A8: [0x11C348], 0x7A0B40: [0x11C448], 0x7A0DD0: [0x11C668], 0x7A128C: [0x11CA00], 0x7A15BC: [0x11CBD0], 0x7A1910: [0x11CCF4], 0x7A1EE0: [0x11CFFC], 0x7A23C0: [0x11D23C], 0x7A24A0: [0x11D344], 0x7A26C0: [0x11D43C], 0x7A282C: [0x11D9D4, 0x11DCF4], 0x7A324C: [0x11DEEC], 0x7A356C: [0x11E00C], 0x7A3C20: [0x11E338], 0x7A3D48: [0x11E6A0], 0x7A4424: [0x11E85C], 0x7A4A28: [0x11EA30, 0x11EE30], 0x7A4B70: [0x11EF9C], 0x7A4D30: [0x11F124, 0x11F570], 0x7A5178: [0x11F714], 0x7A5274: [0x11F9EC], 0x7A54D4: [0x11FCCC], 0x7A57F0: [0x11FF04], 0x7A6250: [0x120010, 0x1203E8], 0x7A6690: [0x12053C], 0x7A68FC: [0x1208C4], 0x7A7094: [0x1209CC], 0x7A73E8: [0x120CA4], 0x7A7FFC: [0x120E6C], 0x7A85F8: [0x120FC4], 0x7A8790: [0x121338], 0x7A8EC0: [0x1215A8], 0x7A900C: [0x121A68], 0x7A9908: [0x121ED0], 0x7AA044: [0x122120], 0x7AA344: [0x122508], 0x7AA7A0: [0x12263C], 0x7AA858: [0x12288C], 0x7AAC10: [0x122BB4], 0x7AAE1C: [0x122CC8], 0x7AAF34: [0x122E8C], 0x7AB048: [0x123098, 0x123490], 0x7AB2C0: [0x1235A0], 0x7AB2FC: [0x1236F8], 0x7AB4A8: [0x1239F0], 0x7AB5C8: [0x123BD0, 0x123FD0], 0x7ABB28: [0x12420C], 0x7ABBC8: [0x1246F8], 0x7ABE48: [0x124808], 0x7ABEA0: [0x124B30], 0x7AC32C: [0x124DF4], 0x7AC478: [0x124EE0], 0x7AC4F8: [0x12503C], 0x7AC5B0: [0x1254C0], 0x7AC87C: [0x1255E4], 0x7AC8F8: [0x125728], 0x7ACA4C: [0x125ACC], 0x7ACD54: [0x125C68, 0x125CD8, 0x125D68, 0x126188], 0x7AD3DC: [0x12647C], 0x7AD5AC: [0x1268C8], 0x7ADCF4: [0x1269F0, 0x126A3C, 0x126A8C, 0x126CD8], 0x7AE20C: [0x127030], 0x7AE3C8: [0x1271A8, 0x1271DC, 0x12722C, 0x127298, 0x1276CC], 0x7AEF30: [0x127B18], 0x7AF718: [0x127C2C, 0x127CB8, 0x127CEC, 0x127D44, 0x127FE8, 0x128130], 0x7AFBDC: [0x128648, 0x128CB0, 0x128F8C, 0x129248, 0x129664], 0x7B2098: [0x04A8E8, 0x04A91C, 0x04A930, 0x04A944, 0x04A980, 0x04A994], 0x7B2864: [0x12A34C, 0x12A570], 0x7B30C8: [0x129BB0], 0x7B3320: [0x129FC0], 0x7B34D4: [0x129BB4], 0x7B3714: [0x129FC4], 0x7B3834: [0x129BB8], 0x7B3C74: [0x129FC8], 0x7B4198: [0x129BBC, 0x12ABD8], 0x7B4488: [0x129FCC, 0x12B038], 0x7B4A14: [0x12F9A4, 0x12FB44, 0x12FBBC, 0x12FC04, 0x12FC24, 0x12FCFC, 0x12FE34, 0x12FE54, 0x12FE7C, 0x12FEB0, 0x130158], 0x7CB3F0: [0x02BA8C], 0x7E7618: [0x033968], 0x7E80F0: [0x03396C], 0x7E8BAC: [0x035908], 0x7E8F98: [0x035910], 0x7EA2C8: [0x030360], 0x7EAEB0: [0x027AC8, 0x0333D0], 0x7EAFA0: [0x027ACC, 0x02E854, 0x02F63C, 0x031AA8, 0x032CEC, 0x033540, 0x037664, 0x044664], 0x7EB19C: [0x02EA7C, 0x0404B4, 0x040AAC, 0x0447E0], 0x7EB974: [0x02EA84], 0x7EBAA8: [0x03C258], 0x7EC600: [0x13101C, 0x1311C4], 0x7EE108: [0x043DC0, 0x043DD0, 0x043DF0, 0x043E10, 0x043E40], 0x7EE2DC: [0x043E00], 0x7EE4BC: [0x043F20], 0x7EE798: [0x043DE0, 0x043E20, 0x043E30, 0x043EC0], 0x7EE96C: [0x043E50, 0x043EA0], 0x7EEB4C: [0x043E60, 0x043E80], 0x7EED20: [0x043E70, 0x043E90, 0x043ED0], 0x7EEEEC: [0x043EB0], 0x7EF0C0: [0x043EE0], 0x7EF294: [0x043EF0, 0x043F00, 0x043F10], 0x7EF468: [0x032874, 0x04162C, 0x12D698, 0x12D848], 0x7F0B40: [0x043698], 0x7F0BB0: [0x04335C], 0x7F0DD0: [0x043360], 0x7F278C: [0x043364], 0x7F29F0: [0x043368], 0x7F3B28: [0x04336C], 0x7F3D38: [0x043370], 0x7F4F60: [0x043374], 0x7F5148: [0x043378], 0x7F6038: [0x04337C], 0x7F61F8: [0x043380], 0x7F6F80: [0x043384], 0x7F7134: [0x043388], 0x7F7BA8: [0x04338C], 0x7F7CC0: [0x043390], 0x7F95F8: [0x1305DC, 0x130670], 0x7F961C: [0x1312F0, 0x1313A8], 0x7F98F0: [0x131500, 0x13155C], 0x7FAC00: [0x0486DC], 0x800000: [0x028874]} +#UncompressedArchives = [0x684F9C, 0x7043EC, 0x704E14, 0x7052F4, 0x707660, 0x708A5C, 0x708ACC, 0x708D30, 0x709600, 0x70A498, 0x70B350, 0x70C1EC, 0x70D078, 0x70DF40, 0x70EE24, 0x70FCD0, 0x710684, 0x710DEC, 0x7112E4, 0x711CAC, 0x713F50, 0x713F98, 0x714078, 0x7141F4, 0x714654, 0x714794, 0x716C30, 0x718E58, 0x71ABFC, 0x71C950, 0x71DDE8, 0x71F09C, 0x720650, 0x720C58, 0x721038, 0x721240, 0x721668, 0x721A68, 0x723E68, 0x723F80, 0x7245CC, 0x724728, 0x726C34, 0x728B18, 0x72A980, 0x72C278, 0x72CC90, 0x72CCDC, 0x75DBE4, 0x778494, 0x77873C, 0x778A40, 0x778BE8, 0x779334, 0x7795E4, 0x77969C, 0x779C04, 0x779E3C, 0x77A050, 0x77A3A4, 0x77A54C, 0x77A6C0, 0x77A7E4, 0x77AA34, 0x77B010, 0x77B178, 0x77B24C, 0x77B33C, 0x77B628, 0x77B754, 0x77B95C, 0x77BA54, 0x77BB50, 0x77BE78, 0x77C090, 0x77C560, 0x77C704, 0x77C778, 0x77C7B8, 0x77C7E8, 0x77C830, 0x77C87C, 0x77C8C8, 0x77C964, 0x77CB60, 0x77CD08, 0x77D168, 0x77D448, 0x77D8C4, 0x77DA34, 0x77DC00, 0x77DFE4, 0x77E13C, 0x77E414, 0x77E5AC, 0x77E874, 0x77F084, 0x77F180, 0x77F304, 0x77F500, 0x77F5F4, 0x77F6C0, 0x77F890, 0x77F9A4, 0x77FEA8, 0x7803C0, 0x78045C, 0x780898, 0x780AA4, 0x780C5C, 0x780FA0, 0x781148, 0x7811B8, 0x7816DC, 0x781CD4, 0x7820B8, 0x782880, 0x782B00, 0x783280, 0x783448, 0x783B78, 0x784038, 0x784414, 0x7847C4, 0x7849B0, 0x784EA4, 0x7853E8, 0x78559C, 0x785778, 0x78595C, 0x785AC0, 0x785D64, 0x786180, 0x78662C, 0x7867F4, 0x7869F8, 0x786F28, 0x787160, 0x7875DC, 0x787838, 0x787A08, 0x788128, 0x7883C4, 0x788784, 0x7892F4, 0x7896C4, 0x789A28, 0x789C6C, 0x78A104, 0x78A474, 0x78A72C, 0x78A904, 0x78AAE0, 0x78ACCC, 0x78AEA8, 0x78B400, 0x78B51C, 0x78BAC4, 0x78BEE4, 0x78BFF4, 0x78C4FC, 0x78CCF8, 0x78D240, 0x78D990, 0x78DABC, 0x78E15C, 0x78EA78, 0x78F298, 0x78F8FC, 0x78FE2C, 0x7902DC, 0x790850, 0x790BE4, 0x791248, 0x791750, 0x7917E8, 0x792098, 0x79225C, 0x79286C, 0x792F10, 0x79300C, 0x793324, 0x793A74, 0x793C8C, 0x7943F0, 0x7948A8, 0x794F38, 0x79516C, 0x7953B8, 0x795588, 0x795884, 0x795CD8, 0x795D68, 0x795DCC, 0x795ED8, 0x795FB0, 0x7966D4, 0x796724, 0x796A8C, 0x796B10, 0x796DB4, 0x796E48, 0x79711C, 0x797694, 0x797D30, 0x798080, 0x798604, 0x798B2C, 0x799138, 0x7992E4, 0x7994B0, 0x799BC4, 0x799F68, 0x79A41C, 0x79A53C, 0x79A628, 0x79A8C0, 0x79AAB4, 0x79AD04, 0x79B134, 0x79B318, 0x79BC18, 0x79BD38, 0x79BE40, 0x79BF58, 0x79C074, 0x79C274, 0x79C400, 0x79C58C, 0x79C818, 0x79C9BC, 0x79CBEC, 0x79D130, 0x79D9CC, 0x79DBEC, 0x79DE34, 0x79E24C, 0x79E39C, 0x79EA5C, 0x79F48C, 0x79FD98, 0x7A0420, 0x7A09A8, 0x7A0B40, 0x7A0DD0, 0x7A128C, 0x7A15BC, 0x7A1910, 0x7A1EE0, 0x7A23C0, 0x7A24A0, 0x7A26C0, 0x7A282C, 0x7A324C, 0x7A356C, 0x7A3C20, 0x7A3D48, 0x7A4424, 0x7A4A28, 0x7A4B70, 0x7A4D30, 0x7A5178, 0x7A5274, 0x7A54D4, 0x7A57F0, 0x7A6250, 0x7A6690, 0x7A68FC, 0x7A7094, 0x7A73E8, 0x7A7FFC, 0x7A85F8, 0x7A8790, 0x7A8EC0, 0x7A900C, 0x7A9908, 0x7AA044, 0x7AA344, 0x7AA7A0, 0x7AA858, 0x7AAC10, 0x7AAE1C, 0x7AAF34, 0x7AB048, 0x7AB2C0, 0x7AB2FC, 0x7AB4A8, 0x7AB5C8, 0x7ABB28, 0x7ABBC8, 0x7ABE48, 0x7ABEA0, 0x7AC32C, 0x7AC478, 0x7AC4F8, 0x7AC5B0, 0x7AC87C, 0x7AC8F8, 0x7ACA4C, 0x7ACD54, 0x7AD3DC, 0x7AD5AC, 0x7ADCF4, 0x7AE20C, 0x7AE3C8, 0x7AEF30, 0x7AF718, 0x7AFBDC, 0x7B2098, 0x7B2864, 0x7B30C8, 0x7B3320, 0x7B34D4, 0x7B3714, 0x7B3834, 0x7B3C74, 0x7B4198, 0x7B4488, 0x7B4A14, 0x7CB3F0, 0x7EA2C8, 0x7EAEB0, 0x7EAFA0, 0x7EB19C, 0x7EB974, 0x7EBAA8, 0x7EC600, 0x7EF468, 0x7F0B40, 0x7F95F8, 0x7F961C, 0x7F98F0, 0x7FAC00] + + +charDict = { + ' ': 0x00, '0': 0x01, '1': 0x02, '2': 0x03, '3': 0x04, '4': 0x05, '5': 0x06, '6': 0x07, '7': 0x08, '8': 0x09, '9': 0x0A, + 'A': 0x0B, 'B': 0x0C, 'C': 0x0D, 'D': 0x0E, 'E': 0x0F, 'F': 0x10, 'G': 0x11, 'H': 0x12, 'I': 0x13, 'J': 0x14, 'K': 0x15, + 'L': 0x16, 'M': 0x17, 'N': 0x18, 'O': 0x19, 'P': 0x1A, 'Q': 0x1B, 'R': 0x1C, 'S': 0x1D, 'T': 0x1E, 'U': 0x1F, 'V': 0x20, + 'W': 0x21, 'X': 0x22, 'Y': 0x23, 'Z': 0x24, 'a': 0x25, 'b': 0x26, 'c': 0x27, 'd': 0x28, 'e': 0x29, 'f': 0x2A, 'g': 0x2B, + 'h': 0x2C, 'i': 0x2D, 'j': 0x2E, 'k': 0x2F, 'l': 0x30, 'm': 0x31, 'n': 0x32, 'o': 0x33, 'p': 0x34, 'q': 0x35, 'r': 0x36, + 's': 0x37, 't': 0x38, 'u': 0x39, 'v': 0x3A, 'w': 0x3B, 'x': 0x3C, 'y': 0x3D, 'z': 0x3E, '-': 0x3F, '×': 0x40, '=': 0x41, + ':': 0x42, '+': 0x43, '÷': 0x44, '※': 0x45, '*': 0x46, '!': 0x47, '?': 0x48, '%': 0x49, '&': 0x4A, ',': 0x4B, '⋯': 0x4C, + '.': 0x4D, '・': 0x4E, ';': 0x4F, '\'': 0x50, '\"': 0x51, '~': 0x52, '/': 0x53, '(': 0x54, ')': 0x55, '「': 0x56, '」': 0x57, + "V2": 0x58, "V3": 0x59, "V4": 0x5A, "V5": 0x5B, '@': 0x5C, '♥': 0x5D, '♪': 0x5E, "MB": 0x5F, '■': 0x60, '_': 0x61, + "circle1": 0x62, "circle2": 0x63, "cross1": 0x64, "cross2": 0x65, "bracket1": 0x66, "bracket2": 0x67, "ModTools1": 0x68, + "ModTools2": 0x69, "ModTools3": 0x6A, 'Σ': 0x6B, 'Ω': 0x6C, 'α': 0x6D, 'β': 0x6E, '#': 0x6F, '…': 0x70, '>': 0x71, + '<': 0x72, 'エ': 0x73, "BowneGlobal1": 0x74, "BowneGlobal2": 0x75, "BowneGlobal3": 0x76, "BowneGlobal4": 0x77, + "BowneGlobal5": 0x78, "BowneGlobal6": 0x79, "BowneGlobal7": 0x7A, "BowneGlobal8": 0x7B, "BowneGlobal9": 0x7C, + "BowneGlobal10": 0x7D, "BowneGlobal11": 0x7E, '\n': 0xE8, 'ω': 0x6C +} + +undernet_item_indices = [27, 28, 29, 30, 31, 32, 58, 34, 34] + + +def read_u16_le(data, offset) -> int: + low_byte = data[offset] + high_byte = data[offset+1] + return (high_byte << 8) + low_byte + + +def read_u32_le(data, offset) -> int: + low_byte = data[offset] + high_byte = data[offset+1] + higher_byte = data[offset+2] + highest_byte = data[offset+3] + return (highest_byte << 24) + (higher_byte << 16) +(high_byte << 8) + low_byte + + +def int32_to_byte_list_le(x) -> bytearray: + byte32_string = "{:08x}".format(x) + data = bytearray.fromhex(byte32_string) + data.reverse() + return data + + +def int16_to_byte_list_le(x) -> bytearray: + byte32_string = "{:04x}".format(x) + data = bytearray.fromhex(byte32_string) + data.reverse() + return data + + +def generate_text_bytes(message) -> bytearray: + return bytearray(char_to_hex(c) for c in message) + + +def char_to_hex(c) -> int: + if c in charDict: + return charDict[c] + else: + # If the character doesn't exist, return one of the mod tools error characters + # Yes, it _is_ a coincidence this happens to be 69 + return 0x69 + +def generate_chip_get(chip, code, amt) -> bytearray: + chip_bytes = int16_to_byte_list_le(chip) + byte_list = [0xF6, 0x10, chip_bytes[0], chip_bytes[1], code, amt] + byte_list.extend(generate_text_bytes("Got a chip for\n\"")) + byte_list.extend([0xF9, 0x00, chip_bytes[0], 0x01 if chip < 256 else 0x02, 0x00, 0xF9, 0x00, code, 0x03]) + byte_list.extend(generate_text_bytes("\"!!")) + return bytearray(byte_list) + + +def generate_key_item_get(item, amt) -> bytearray: + byte_list = [0xF6, 0x00, item, amt] + byte_list.extend(generate_text_bytes("Got a \n\"")) + byte_list.extend([0xF9, 0x00, item, 0x00]) + byte_list.extend(generate_text_bytes("\"!!")) + return bytearray(byte_list) + + +def generate_sub_chip_get(subchip, amt) -> bytearray: + # SubChips have an extra bit of trouble. + # If you have too many, they're supposed to skip to another text bank that doesn't give you the item + # Instead, I'm going to just let it get eaten. Script indices are at a premium and I can't always add one + # It's more important to use them for progressive Undernet + byte_list = [0xF6, 0x20, subchip, amt, 0xFF, 0xFF, 0xFF] + byte_list.extend(generate_text_bytes("Got a \nSubChip for\n\"")) + byte_list.extend([0xF9, 0x00, subchip, 0x00]) + byte_list.extend(generate_text_bytes("\"!!")) + return bytearray(byte_list) + + +def generate_zenny_get(amt) -> bytearray: + zenny_bytes = int32_to_byte_list_le(amt) + byte_list = [0xF6, 0x30, *zenny_bytes, 0xFF, 0xFF, 0xFF] + byte_list.extend(generate_text_bytes(f"Got \n\"{amt} Zennys\"!!")) + return bytearray(byte_list) + + +def generate_program_get(program, color, amt) -> bytearray: + # Programs are bit shifted twice to generate the "give" bit + byte_list = [0xF6, 0x40, program << 2, amt, color] + byte_list.extend(generate_text_bytes("Got a Navi\nCustomizer Program:\n\"")) + byte_list.extend([0xF9, 0x00, program, 0x05]) + return bytearray(byte_list) + + +def generate_bugfrag_get(amt) -> bytearray: + frag_bytes = int32_to_byte_list_le(amt) + byte_list = [0xF6, 0x50, frag_bytes[0], frag_bytes[1], frag_bytes[2], frag_bytes[3], 0xFF, 0xFF, 0xFF] + byte_list.extend(generate_text_bytes("Got:\n\"" + str(amt) + " BugFrags\"!!")) + return bytearray(byte_list) + + +# This one is meant to be "silent". The text box has already been displayed. +# So this one just gives the item using the text box syntax +def generate_progressive_undernet(progression_index, next_script) -> bytearray: + if progression_index >= 8: + # If we're at max rank, give bugfrags instead + frag_bytes = int32_to_byte_list_le(20) + byte_list = [0xF6, 0x50, frag_bytes[0], frag_bytes[1], frag_bytes[2], frag_bytes[3], 0xFF, 0xFF, 0xFF] + byte_list.extend(generate_text_bytes("The extra data\ndecompiles into:\n\"20 BugFrags\"!!")) + else: + # F6 03 - Check for item. If you have it, load next_script, otherwise, continue + byte_list = [0xf6, 0x03, undernet_item_indices[progression_index], 0x01, next_script, next_script, 0xFF, 0xE9] + + # Otherwise, give the item, with different code depending on if we're at max rank already or not + byte_list.extend(generate_key_item_get(undernet_item_indices[progression_index], 1)) + byte_list.extend([0xEB, 0xE7]) # End the message + return bytearray(byte_list) + + +def generate_get_for_item(item) -> bytearray: + if item.type == ItemType.Undernet: + return generate_text_bytes("Got the next\n\"Undernet Rank\"!!") + elif item.type == ItemType.Chip: + return generate_chip_get(item.itemID, item.subItemID, item.count) + elif item.type == ItemType.KeyItem: + return generate_key_item_get(item.itemID, item.count) + elif item.type == ItemType.SubChip: + return generate_sub_chip_get(item.itemID, item.count) + elif item.type == ItemType.Zenny: + return generate_zenny_get(item.count) + elif item.type == ItemType.Program: + return generate_program_get(item.itemID, item.subItemID, item.count) + elif item.type == ItemType.BugFrag: + return generate_bugfrag_get(item.count) + + return generate_text_bytes("Empty Message") + + +def generate_item_message(item_data) -> bytearray: + byte_list = [0xF8, 0x04, 0x18] # Play Animation + byte_list.extend([0xED, 0x01]) # Hide Mugshot + byte_list.extend(generate_get_for_item(item_data)) + byte_list.extend([0xF8, 0x0C]) + byte_list.extend([0xEB, 0xE9, 0xF8, 0x08]) + byte_list.extend([0xF8, 0x10]) + return bytearray(byte_list) + + +def generate_external_item_message(item_name, item_recipient) -> bytearray: + byte_list = [0xF8, 0x04, 0x18] # Play Animation + item_name_pascal = shorten_item_name(item_name) + byte_list.extend([0xED, 0x01]) # Hide Mugshot + byte_list.extend([0xFA, 0x00, 0x85, 0x00]) # Play Sound + byte_list.extend(generate_text_bytes("Sending data for\n" + item_name_pascal + "\nTo " + item_recipient)) + byte_list.extend([0xF8, 0x0C]) + byte_list.extend([0xEB, 0xE9, 0xF8, 0x08]) + byte_list.extend([0xF8, 0x10]) + return bytearray(byte_list) + + +def shorten_item_name(item_name): + maxLength = 20 + # If it's short enough, just use it + if len(item_name) <= maxLength: + return item_name + # Next, PascalCase it + item_name = item_name.replace("_", " ").replace("-", " ").title().replace(" ", "") + if len(item_name) <= maxLength: + return item_name + # If it's still too long, start removing vowels till it's short enough or we run out + while len(item_name) > maxLength and any(c in item_name for c in ['a', 'e', 'i', 'o', 'u']): + item_name = re.sub("[aeiou]", "", item_name, 1) + # If it's somehow still too long, truncate and return + return item_name[0:maxLength] diff --git a/worlds/mmbn3/Items.py b/worlds/mmbn3/Items.py new file mode 100644 index 000000000000..2e249ce79e8c --- /dev/null +++ b/worlds/mmbn3/Items.py @@ -0,0 +1,345 @@ +import typing + +from enum import IntEnum, Enum +from BaseClasses import Item, ItemClassification +from .Names.ItemName import ItemName + + +class ItemType(str, Enum): + Undernet = "undernet" + Chip = "chip" + KeyItem = "key" + SubChip = "subchip" + Zenny = "zenny" + Program = "program" + BugFrag = "bugfrag" + External = "External" + + __str__ = str.__str__ + +class ProgramColor(IntEnum): + White = 1 + Pink = 2 + Yellow = 3 + Red = 4 + Blue = 5 + Green = 6 + Orange = 7 + Purple = 8 + Dark = 9 + + +def chip_code(c): + if c == '*': + return 26 + return ord(c) - ord('A') + + +class ItemData(typing.NamedTuple): + code: int + itemName: str + progression: ItemClassification + type: ItemType + itemID: int = 0x00 + subItemID: int = 0x00 + count: int = 1 + recipient: str = "Myself" + + +class MMBN3Item(Item): + game: str = "MegaMan Battle Network 3" + + +keyItemList: typing.List[ItemData] = [ + ItemData(0xB31000, ItemName.Progressive_Undernet_Rank, ItemClassification.progression_skip_balancing, ItemType.Undernet, 27), + ItemData(0xB31001, ItemName.CACDCPas, ItemClassification.progression, ItemType.KeyItem, 92), + ItemData(0xB31002, ItemName.CSciPas, ItemClassification.progression, ItemType.KeyItem, 93), + ItemData(0xB31003, ItemName.CYokaPas, ItemClassification.progression, ItemType.KeyItem, 94), + ItemData(0xB31004, ItemName.CBeacPas, ItemClassification.progression, ItemType.KeyItem, 95), + ItemData(0xB31005, ItemName.WWW_ID, ItemClassification.progression, ItemType.KeyItem, 47), + ItemData(0xB31006, ItemName.OrderSys, ItemClassification.useful, ItemType.KeyItem, 12), + ItemData(0xB31007, ItemName.ModTools, ItemClassification.useful, ItemType.KeyItem, 55), + ItemData(0xB31008, ItemName.ExpMem, ItemClassification.useful, ItemType.KeyItem, 97), + ItemData(0xB31009, ItemName.SpinWht, ItemClassification.useful, ItemType.KeyItem, 71), + ItemData(0xB3100A, ItemName.SpinPink, ItemClassification.useful, ItemType.KeyItem, 72), + ItemData(0xB3100B, ItemName.SpinYllw, ItemClassification.useful, ItemType.KeyItem, 73), + ItemData(0xB3100C, ItemName.SpinRed, ItemClassification.useful, ItemType.KeyItem, 74), + ItemData(0xB3100D, ItemName.SpinBlue, ItemClassification.useful, ItemType.KeyItem, 75), + ItemData(0xB3100E, ItemName.SpinGrn, ItemClassification.useful, ItemType.KeyItem, 76), + ItemData(0xB3100F, ItemName.SpinOrange, ItemClassification.useful, ItemType.KeyItem, 77), + + ItemData(0xB31010, ItemName.SpinPurple, ItemClassification.useful, ItemType.KeyItem, 78), + ItemData(0xB31011, ItemName.SpinDark, ItemClassification.useful, ItemType.KeyItem, 79), + ItemData(0xB31012, ItemName.HPMemory, ItemClassification.useful, ItemType.KeyItem, 96), + ItemData(0xB31013, ItemName.RegUP1, ItemClassification.filler, ItemType.KeyItem, 98), + ItemData(0xB31014, ItemName.RegUP2, ItemClassification.useful, ItemType.KeyItem, 99), + ItemData(0xB31015, ItemName.RegUP3, ItemClassification.useful, ItemType.KeyItem, 100), + ItemData(0xB31016, ItemName.Mr_Famous_Wristband, ItemClassification.filler, ItemType.KeyItem, 57), + ItemData(0xB31017, ItemName.SubMem, ItemClassification.filler, ItemType.KeyItem, 101), + + ItemData(0xB310B1, ItemName.SubPET, ItemClassification.progression, ItemType.KeyItem, 10), + ItemData(0xB310B2, ItemName.Needle, ItemClassification.progression, ItemType.KeyItem, 14), + ItemData(0xB310B3, ItemName.PETCase, ItemClassification.progression, ItemType.KeyItem, 16), + # ItemData(0xB310B4, ItemName.Parasol, ItemClassification.progression, ItemType.KeyItem, 3), + ItemData(0xB310DF, ItemName.Hammer, ItemClassification.progression, ItemType.KeyItem, 56) +] + +subChipList: typing.List[ItemData] = [ + ItemData(0xB31018, ItemName.Unlocker, ItemClassification.useful, ItemType.SubChip, 117), + ItemData(0xB31019, ItemName.Untrap, ItemClassification.filler, ItemType.SubChip, 115), + ItemData(0xB3101A, ItemName.LockEnmy, ItemClassification.filler, ItemType.SubChip, 116), + ItemData(0xB3101B, ItemName.MiniEnrg, ItemClassification.filler, ItemType.SubChip, 112), + ItemData(0xB3101C, ItemName.FullEnrg, ItemClassification.filler, ItemType.SubChip, 113), + ItemData(0xB3101D, ItemName.SneakRun, ItemClassification.filler, ItemType.SubChip, 114) +] + +chipList: typing.List[ItemData] = [ + ItemData(0xB3101E, ItemName.AirShoes_star, ItemClassification.useful, ItemType.Chip, 168, chip_code('*')), + ItemData(0xB3101F, ItemName.AirShot3_star, ItemClassification.useful, ItemType.Chip, 6, chip_code('*')), + + ItemData(0xB31020, ItemName.AntiNavi_M, ItemClassification.useful, ItemType.Chip, 190, chip_code('M')), + ItemData(0xB31021, ItemName.AntiRecv_B, ItemClassification.filler, ItemType.Chip, 193, chip_code('B')), + ItemData(0xB31022, ItemName.AntiSword_Y, ItemClassification.useful, ItemType.Chip, 192, chip_code('Y')), + ItemData(0xB31023, ItemName.Aqua_plus_30_star, ItemClassification.useful, ItemType.Chip, 197, chip_code('*')), + ItemData(0xB31024, ItemName.Aura_F, ItemClassification.useful, ItemType.Chip, 176, chip_code('F')), + ItemData(0xB31025, ItemName.BambooSword_N, ItemClassification.useful, ItemType.Chip, 36, chip_code('N')), + ItemData(0xB31026, ItemName.Barr100_E, ItemClassification.useful, ItemType.Chip, 174, chip_code('E')), + ItemData(0xB31027, ItemName.Barr200_E, ItemClassification.useful, ItemType.Chip, 175, chip_code('E')), + ItemData(0xB31028, ItemName.Barrier_E, ItemClassification.useful, ItemType.Chip, 173, chip_code('E')), + ItemData(0xB31029, ItemName.Barrier_L, ItemClassification.filler, ItemType.Chip, 173, chip_code('L')), + ItemData(0xB3102A, ItemName.BlkBomb1_P, ItemClassification.useful, ItemType.Chip, 27, chip_code('P')), + ItemData(0xB3102B, ItemName.BlkBomb2_S, ItemClassification.useful, ItemType.Chip, 28, chip_code('S')), + ItemData(0xB3102C, ItemName.Cannon_C, ItemClassification.filler, ItemType.Chip, 1, chip_code('C')), + ItemData(0xB3102D, ItemName.CopyDmg_star, ItemClassification.filler, ItemType.Chip, 194, chip_code('*')), + ItemData(0xB3102E, ItemName.CustSwrd_Z, ItemClassification.filler, ItemType.Chip, 37, chip_code('Z')), + ItemData(0xB3102F, ItemName.DynaWave_V, ItemClassification.progression, ItemType.Chip, 46, chip_code('V')), + + ItemData(0xB31030, ItemName.ElecSwrd_P, ItemClassification.useful, ItemType.Chip, 35, chip_code('P')), + ItemData(0xB31031, ItemName.Fire_plus_30_star, ItemClassification.filler, ItemType.Chip, 196, chip_code('*')), + ItemData(0xB31032, ItemName.FireRat_H, ItemClassification.useful, ItemType.Chip, 117, chip_code('H')), + ItemData(0xB31033, ItemName.FireSwrd_P, ItemClassification.progression, ItemType.Chip, 33, chip_code('P')), + ItemData(0xB31034, ItemName.FstGauge_star, ItemClassification.useful, ItemType.Chip, 158, chip_code('*')), + ItemData(0xB31035, ItemName.GaiaBlde_star, ItemClassification.useful, ItemType.Chip, 276, chip_code('*')), + ItemData(0xB31036, ItemName.Geddon1_star, ItemClassification.filler, ItemType.Chip, 138, chip_code('*')), + ItemData(0xB31037, ItemName.Geddon1_D, ItemClassification.filler, ItemType.Chip, 138, chip_code('D')), + ItemData(0xB31038, ItemName.Geddon3_U, ItemClassification.useful, ItemType.Chip, 140, chip_code('U')), + ItemData(0xB31039, ItemName.Geyser_B, ItemClassification.useful, ItemType.Chip, 107, chip_code('B')), + ItemData(0xB3103A, ItemName.GrabBack_K, ItemClassification.progression, ItemType.Chip, 136, chip_code('K')), + ItemData(0xB3103B, ItemName.GrabRvng_A, ItemClassification.filler, ItemType.Chip, 137, chip_code('A')), + ItemData(0xB3103C, ItemName.GrabRvng_Y, ItemClassification.filler, ItemType.Chip, 137, chip_code('Y')), + ItemData(0xB3103D, ItemName.GutStrght_S, ItemClassification.useful, ItemType.Chip, 48, chip_code('S')), + ItemData(0xB3103E, ItemName.Guardian_O, ItemClassification.useful, ItemType.Chip, 203, chip_code('O')), + ItemData(0xB3103F, ItemName.GutImpact_H, ItemClassification.useful, ItemType.Chip, 49, chip_code('H')), + + ItemData(0xB31040, ItemName.GutImpct_J, ItemClassification.useful, ItemType.Chip, 49, chip_code('J')), + ItemData(0xB31041, ItemName.GutPunch_E, ItemClassification.filler, ItemType.Chip, 47, chip_code('E')), + ItemData(0xB31042, ItemName.GutPunch_B, ItemClassification.filler, ItemType.Chip, 47, chip_code('B')), + ItemData(0xB31043, ItemName.GutStrgt_Q, ItemClassification.useful, ItemType.Chip, 48, chip_code('Q')), + ItemData(0xB31044, ItemName.Hammer_T, ItemClassification.useful, ItemType.Chip, 64, chip_code('T')), + ItemData(0xB31045, ItemName.HeatSide_T, ItemClassification.filler, ItemType.Chip, 19, chip_code('T')), + ItemData(0xB31046, ItemName.HeroSwrd_P, ItemClassification.useful, ItemType.Chip, 207, chip_code('P')), + ItemData(0xB31047, ItemName.HiCannon_star, ItemClassification.filler, ItemType.Chip, 2, chip_code('*')), + ItemData(0xB31048, ItemName.Hole_star, ItemClassification.useful, ItemType.Chip, 161, chip_code('*')), + ItemData(0xB31049, ItemName.IceStage_star, ItemClassification.filler, ItemType.Chip, 180, chip_code('*')), + ItemData(0xB3104A, ItemName.Invis_star, ItemClassification.useful, ItemType.Chip, 160, chip_code('*')), + ItemData(0xB3104B, ItemName.Jealousy_J, ItemClassification.filler, ItemType.Chip, 214, chip_code('J')), + ItemData(0xB3104C, ItemName.Lance_S, ItemClassification.useful, ItemType.Chip, 75, chip_code('S')), + ItemData(0xB3104D, ItemName.LongSwrd_E, ItemClassification.filler, ItemType.Chip, 32, chip_code('E')), + ItemData(0xB3104E, ItemName.Magnum1_A, ItemClassification.progression, ItemType.Chip, 81, chip_code('A')), + ItemData(0xB3104F, ItemName.Muramasa_M, ItemClassification.useful, ItemType.Chip, 202, chip_code('M')), + + ItemData(0xB31050, ItemName.Navi_plus_40_star, ItemClassification.filler, ItemType.Chip, 206, chip_code('*')), + ItemData(0xB31051, ItemName.Panic_C, ItemClassification.filler, ItemType.Chip, 41, chip_code('C')), + ItemData(0xB31052, ItemName.PanlOut3_star, ItemClassification.filler, ItemType.Chip, 120, chip_code('*')), + ItemData(0xB31053, ItemName.Poltergeist_G, ItemClassification.filler, ItemType.Chip, 213, chip_code('G')), + ItemData(0xB31054, ItemName.Prism_Q, ItemClassification.useful, ItemType.Chip, 142, chip_code('Q')), + ItemData(0xB31055, ItemName.Recov10_star, ItemClassification.filler, ItemType.Chip, 121, chip_code('*')), + ItemData(0xB31056, ItemName.Recov120_star, ItemClassification.useful, ItemType.Chip, 125, chip_code('*')), + ItemData(0xB31057, ItemName.Recov120_O, ItemClassification.filler, ItemType.Chip, 125, chip_code('O')), + ItemData(0xB31058, ItemName.Recov120_S, ItemClassification.progression, ItemType.Chip, 125, chip_code('S')), + ItemData(0xB31059, ItemName.Recov150_P, ItemClassification.filler, ItemType.Chip, 126, chip_code('P')), + ItemData(0xB3105A, ItemName.Recov200_N, ItemClassification.filler, ItemType.Chip, 127, chip_code('N')), + ItemData(0xB3105B, ItemName.Recov30_star, ItemClassification.progression, ItemType.Chip, 122, chip_code('*')), + ItemData(0xB3105C, ItemName.Recov300_R, ItemClassification.useful, ItemType.Chip, 128, chip_code('R')), + ItemData(0xB3105D, ItemName.Recov50_G, ItemClassification.filler, ItemType.Chip, 123, chip_code('G')), + ItemData(0xB3105E, ItemName.Repair_star, ItemClassification.filler, ItemType.Chip, 159, chip_code('*')), + ItemData(0xB3105F, ItemName.Repair_A, ItemClassification.filler, ItemType.Chip, 159, chip_code('A')), + + ItemData(0xB31060, ItemName.Rockcube_star, ItemClassification.filler, ItemType.Chip, 141, chip_code('*')), + ItemData(0xB31061, ItemName.Rook_F, ItemClassification.filler, ItemType.Chip, 153, chip_code('F')), + ItemData(0xB31062, ItemName.Salamndr_star, ItemClassification.useful, ItemType.Chip, 273, chip_code('*')), + ItemData(0xB31063, ItemName.SandStage_C, ItemClassification.filler, ItemType.Chip, 182, chip_code('C')), + ItemData(0xB31064, ItemName.SideGun_S, ItemClassification.filler, ItemType.Chip, 12, chip_code('S')), + ItemData(0xB31065, ItemName.Slasher_B, ItemClassification.useful, ItemType.Chip, 43, chip_code('B')), + ItemData(0xB31066, ItemName.SloGuage_star, ItemClassification.filler, ItemType.Chip, 157, chip_code('*')), + ItemData(0xB31067, ItemName.Snake_D, ItemClassification.useful, ItemType.Chip, 131, chip_code('D')), + ItemData(0xB31068, ItemName.Snctuary_C, ItemClassification.useful, ItemType.Chip, 184, chip_code('C')), + ItemData(0xB31069, ItemName.Spreader_star, ItemClassification.useful, ItemType.Chip, 13, chip_code('*')), + ItemData(0xB3106A, ItemName.Spreader_N, ItemClassification.useful, ItemType.Chip, 13, chip_code('N')), + ItemData(0xB3106B, ItemName.Spreader_P, ItemClassification.useful, ItemType.Chip, 13, chip_code('P')), + ItemData(0xB3106C, ItemName.StepCross_Q, ItemClassification.useful, ItemType.Chip, 40, chip_code('Q')), + ItemData(0xB3106D, ItemName.StepCross_R, ItemClassification.useful, ItemType.Chip, 40, chip_code('R')), + ItemData(0xB3106E, ItemName.StepSwrd_M, ItemClassification.useful, ItemType.Chip, 39, chip_code('M')), + ItemData(0xB3106F, ItemName.StepSwrd_N, ItemClassification.useful, ItemType.Chip, 39, chip_code('N')), + + ItemData(0xB31070, ItemName.StepSwrd_O, ItemClassification.useful, ItemType.Chip, 39, chip_code('O')), + ItemData(0xB31071, ItemName.StepCross_S, ItemClassification.useful, ItemType.Chip, 40, chip_code('S')), + ItemData(0xB31072, ItemName.Sword_E, ItemClassification.filler, ItemType.Chip, 30, chip_code('E')), + ItemData(0xB31073, ItemName.Team1_star, ItemClassification.useful, ItemType.Chip, 132, chip_code('*')), + ItemData(0xB31074, ItemName.Team2_star, ItemClassification.useful, ItemType.Chip, 169, chip_code('*')), + ItemData(0xB31075, ItemName.Thndrblt_star, ItemClassification.useful, ItemType.Chip, 275, chip_code('*')), + ItemData(0xB31076, ItemName.Tornado_L, ItemClassification.filler, ItemType.Chip, 65, chip_code('L')), + ItemData(0xB31077, ItemName.Fountain_star, ItemClassification.useful, ItemType.Chip, 274, chip_code('*')), + ItemData(0xB31078, ItemName.VarSword_B, ItemClassification.useful, ItemType.Chip, 38, chip_code('B')), + ItemData(0xB31079, ItemName.VarSword_F, ItemClassification.useful, ItemType.Chip, 38, chip_code('F')), + ItemData(0xB3107A, ItemName.WideSwrd_C, ItemClassification.progression, ItemType.Chip, 31, chip_code('C')), + ItemData(0xB3107B, ItemName.WideSwrd_E, ItemClassification.filler, ItemType.Chip, 31, chip_code('E')), + ItemData(0xB3107C, ItemName.WideSwrd_L, ItemClassification.filler, ItemType.Chip, 31, chip_code('L')), + ItemData(0xB3107D, ItemName.Yo_Yo1_D, ItemClassification.filler, ItemType.Chip, 69, chip_code('D')), + ItemData(0xB3107E, ItemName.ZeusHammer_Z, ItemClassification.useful, ItemType.Chip, 208, chip_code('Z')), + # ItemData(0xB3107F, ItemName.BassGS_X, ItemClassification.useful, ItemType.Chip, 312, ChipCode('X')), + ItemData(0xB3107F, ItemName.Bass_X, ItemClassification.useful, ItemType.Chip, 311, chip_code('X')), + + ItemData(0xB31080, ItemName.DeltaRay_Z, ItemClassification.useful, ItemType.Chip, 302, chip_code('Z')), + ItemData(0xB31081, ItemName.Punk_P, ItemClassification.useful, ItemType.Chip, 272, chip_code('P')), + ItemData(0xB31082, ItemName.DarkAura_A, ItemClassification.useful, ItemType.Chip, 309, chip_code('A')), + ItemData(0xB31083, ItemName.AlphaArm_Omega_V, ItemClassification.useful, ItemType.Chip, 310, chip_code('V')), + + ItemData(0xB310AC, ItemName.SonicWav_W, ItemClassification.progression, ItemType.Chip, 45, chip_code('W')), + ItemData(0xB310AD, ItemName.Bubbler_C, ItemClassification.progression, ItemType.Chip, 14, chip_code('C')), + ItemData(0xB310AE, ItemName.Shake1_S, ItemClassification.progression, ItemType.Chip, 110, chip_code('S')), + ItemData(0xB310AF, ItemName.HoleMetr_H, ItemClassification.progression, ItemType.Chip, 88, chip_code('H')), + ItemData(0xB310B0, ItemName.Shadow_J, ItemClassification.progression, ItemType.Chip, 165, chip_code('J')), + + ItemData(0xB310B8, ItemName.Roll_R, ItemClassification.filler, ItemType.Chip, 219, chip_code('R')), + ItemData(0xB310B9, ItemName.RollV2_R, ItemClassification.filler, ItemType.Chip, 220, chip_code('R')), + ItemData(0xB310BA, ItemName.RollV3_R, ItemClassification.useful, ItemType.Chip, 221, chip_code('R')), + + ItemData(0xB310BB, ItemName.GutsMan_G, ItemClassification.filler, ItemType.Chip, 222, chip_code('G')), + ItemData(0xB310BC, ItemName.GutsManV2_G, ItemClassification.filler, ItemType.Chip, 223, chip_code('G')), + ItemData(0xB310BD, ItemName.GutsManV3_G, ItemClassification.useful, ItemType.Chip, 224, chip_code('G')), + + ItemData(0xB310BE, ItemName.ProtoMan_B, ItemClassification.filler, ItemType.Chip, 227, chip_code('B')), + ItemData(0xB310BF, ItemName.ProtoManV2_B, ItemClassification.filler, ItemType.Chip, 228, chip_code('B')), + ItemData(0xB310C0, ItemName.ProtoManV3_B, ItemClassification.useful, ItemType.Chip, 229, chip_code('B')), + + ItemData(0xB310C1, ItemName.FlashMan_F, ItemClassification.filler, ItemType.Chip, 232, chip_code('F')), + ItemData(0xB310C2, ItemName.FlashManV2_F, ItemClassification.filler, ItemType.Chip, 233, chip_code('F')), + ItemData(0xB310C3, ItemName.FlashManV3_F, ItemClassification.useful, ItemType.Chip, 234, chip_code('F')), + + ItemData(0xB310C4, ItemName.BeastMan_B, ItemClassification.filler, ItemType.Chip, 237, chip_code('B')), + ItemData(0xB310C5, ItemName.BeastManV2_B, ItemClassification.filler, ItemType.Chip, 238, chip_code('B')), + ItemData(0xB310C6, ItemName.BeastManV3_B, ItemClassification.useful, ItemType.Chip, 239, chip_code('B')), + + ItemData(0xB310C7, ItemName.BubblMan_B, ItemClassification.filler, ItemType.Chip, 242, chip_code('B')), + ItemData(0xB310C8, ItemName.BubblManV2_B, ItemClassification.filler, ItemType.Chip, 243, chip_code('B')), + ItemData(0xB310C9, ItemName.BubblManV3_B, ItemClassification.useful, ItemType.Chip, 244, chip_code('B')), + + ItemData(0xB310CA, ItemName.DesertMan_D, ItemClassification.filler, ItemType.Chip, 247, chip_code('D')), + ItemData(0xB310CB, ItemName.DesertManV2_D, ItemClassification.filler, ItemType.Chip, 248, chip_code('D')), + ItemData(0xB310CC, ItemName.DesertManV3_D, ItemClassification.useful, ItemType.Chip, 249, chip_code('D')), + + ItemData(0xB310CD, ItemName.PlantMan_P, ItemClassification.filler, ItemType.Chip, 252, chip_code('P')), + ItemData(0xB310CE, ItemName.PlantManV2_P, ItemClassification.filler, ItemType.Chip, 253, chip_code('P')), + ItemData(0xB310CF, ItemName.PlantManV3_P, ItemClassification.useful, ItemType.Chip, 254, chip_code('P')), + + ItemData(0xB310D0, ItemName.FlamMan_F, ItemClassification.filler, ItemType.Chip, 257, chip_code('F')), + ItemData(0xB310D1, ItemName.FlamManV2_F, ItemClassification.filler, ItemType.Chip, 258, chip_code('F')), + ItemData(0xB310D2, ItemName.FlamManV3_F, ItemClassification.useful, ItemType.Chip, 259, chip_code('F')), + + ItemData(0xB310D3, ItemName.DrillMan_D, ItemClassification.filler, ItemType.Chip, 262, chip_code('D')), + ItemData(0xB310D4, ItemName.DrillManV2_D, ItemClassification.filler, ItemType.Chip, 263, chip_code('D')), + ItemData(0xB310D5, ItemName.DrillManV3_D, ItemClassification.useful, ItemType.Chip, 264, chip_code('D')), + + ItemData(0xB310D6, ItemName.MetalMan_M, ItemClassification.filler, ItemType.Chip, 267, chip_code('M')), + ItemData(0xB310D7, ItemName.MetalManV2_M, ItemClassification.filler, ItemType.Chip, 268, chip_code('M')), + ItemData(0xB310D8, ItemName.MetalManV3_M, ItemClassification.useful, ItemType.Chip, 269, chip_code('M')), + + ItemData(0xB310D9, ItemName.KingMan_K, ItemClassification.filler, ItemType.Chip, 277, chip_code('K')), + ItemData(0xB310DA, ItemName.KingManV2_K, ItemClassification.filler, ItemType.Chip, 278, chip_code('K')), + ItemData(0xB310DB, ItemName.KingManV3_K, ItemClassification.useful, ItemType.Chip, 279, chip_code('K')), + + ItemData(0xB310DC, ItemName.BowlMan_B, ItemClassification.filler, ItemType.Chip, 287, chip_code('B')), + ItemData(0xB310DD, ItemName.BowlManV2_B, ItemClassification.filler, ItemType.Chip, 288, chip_code('B')), + ItemData(0xB310DE, ItemName.BowlManV3_B, ItemClassification.useful, ItemType.Chip, 289, chip_code('B')), +] + +programList: typing.List[ItemData] = [ + ItemData(0xB31084, ItemName.Airshoes, ItemClassification.useful, ItemType.Program, 29, ProgramColor.White), + ItemData(0xB31085, ItemName.Attack_plus_White, ItemClassification.filler, ItemType.Program, 41, ProgramColor.White), + ItemData(0xB31086, ItemName.Attack_plus_Pink, ItemClassification.filler, ItemType.Program, 41, ProgramColor.Pink), + ItemData(0xB31087, ItemName.BrkChrg, ItemClassification.useful, ItemType.Program, 3, ProgramColor.Orange), + ItemData(0xB31088, ItemName.Charge_plus_Pink, ItemClassification.filler, ItemType.Program, 43, ProgramColor.Pink), + ItemData(0xB31089, ItemName.Charge_plus_White, ItemClassification.filler, ItemType.Program, 43, ProgramColor.White), + ItemData(0xB3108A, ItemName.Collect, ItemClassification.useful, ItemType.Program, 28, ProgramColor.Pink), + ItemData(0xB3108B, ItemName.GigFldr1, ItemClassification.useful, ItemType.Program, 48, ProgramColor.Purple), + ItemData(0xB3108C, ItemName.HP_100_Pink, ItemClassification.filler, ItemType.Program, 36, ProgramColor.Pink), + ItemData(0xB3108D, ItemName.HP_100_Yellow, ItemClassification.filler, ItemType.Program, 36, ProgramColor.Yellow), + ItemData(0xB3108E, ItemName.HP_200_Yellow, ItemClassification.useful, ItemType.Program, 37, ProgramColor.Yellow), + ItemData(0xB3108F, ItemName.HP_500_Yellow, ItemClassification.useful, ItemType.Program, 39, ProgramColor.Yellow), + + ItemData(0xB31090, ItemName.HubBatc, ItemClassification.useful, ItemType.Program, 49, ProgramColor.Orange), + ItemData(0xB31091, ItemName.Jungle, ItemClassification.useful, ItemType.Program, 27, ProgramColor.White), + ItemData(0xB31092, ItemName.OilBody, ItemClassification.useful, ItemType.Program, 24, ProgramColor.Yellow), + ItemData(0xB31093, ItemName.QuickGge, ItemClassification.useful, ItemType.Program, 31, ProgramColor.Pink), + ItemData(0xB31094, ItemName.SetSand, ItemClassification.useful, ItemType.Program, 7, ProgramColor.Green), + ItemData(0xB31095, ItemName.SneakRun, ItemClassification.useful, ItemType.Program, 23, ProgramColor.Yellow), + ItemData(0xB31096, ItemName.Speed_plus_Yellow, ItemClassification.filler, ItemType.Program, 42, ProgramColor.Yellow), + ItemData(0xB31097, ItemName.WpnLV_plus_White, ItemClassification.filler, ItemType.Program, 35, ProgramColor.White), + ItemData(0xB31098, ItemName.WpnLV_plus_Pink, ItemClassification.filler, ItemType.Program, 35, ProgramColor.Pink), + ItemData(0xB31099, ItemName.WpnLV_plus_Yellow, ItemClassification.filler, ItemType.Program, 35, ProgramColor.Yellow), + ItemData(0xB3109A, ItemName.Press, ItemClassification.progression, ItemType.Program, 20, ProgramColor.White), + + ItemData(0xB310B7, ItemName.UnderSht, ItemClassification.useful, ItemType.Program, 30, ProgramColor.White) +] + +zennyList: typing.List[ItemData] = [ + ItemData(0xB3109B, ItemName.zenny_200z, ItemClassification.filler, ItemType.Zenny, count=200), + ItemData(0xB3109C, ItemName.zenny_500z, ItemClassification.filler, ItemType.Zenny, count=500), + ItemData(0xB3109D, ItemName.zenny_600z, ItemClassification.filler, ItemType.Zenny, count=600), + ItemData(0xB3109E, ItemName.zenny_800z, ItemClassification.filler, ItemType.Zenny, count=800), + ItemData(0xB3109F, ItemName.zenny_900z, ItemClassification.filler, ItemType.Zenny, count=900), + + ItemData(0xB310A0, ItemName.zenny_1000z, ItemClassification.filler, ItemType.Zenny, count=1000), + ItemData(0xB310A1, ItemName.zenny_1200z, ItemClassification.filler, ItemType.Zenny, count=1200), + ItemData(0xB310A2, ItemName.zenny_1400z, ItemClassification.filler, ItemType.Zenny, count=1400), + ItemData(0xB310A3, ItemName.zenny_1600z, ItemClassification.filler, ItemType.Zenny, count=1600), + ItemData(0xB310A4, ItemName.zenny_1800z, ItemClassification.filler, ItemType.Zenny, count=1800), + ItemData(0xB310A5, ItemName.zenny_2000z, ItemClassification.filler, ItemType.Zenny, count=2000), + ItemData(0xB310A6, ItemName.zenny_3000z, ItemClassification.filler, ItemType.Zenny, count=3000), + ItemData(0xB310A7, ItemName.zenny_9000z, ItemClassification.filler, ItemType.Zenny, count=9000), + ItemData(0xB310A8, ItemName.zenny_10000z, ItemClassification.useful, ItemType.Zenny, count=10000), + ItemData(0xB310A9, ItemName.zenny_30000z, ItemClassification.useful, ItemType.Zenny, count=30000), + ItemData(0xB310AA, ItemName.zenny_50000z, ItemClassification.useful, ItemType.Zenny, count=50000) +] + +bugFragList: typing.List[ItemData] = [ + ItemData(0xB310AB, ItemName.bugfrag_30, ItemClassification.filler, ItemType.BugFrag, count=30), + ItemData(0xB310B5, ItemName.bugfrag_10, ItemClassification.filler, ItemType.BugFrag, count=10), + ItemData(0xB310B6, ItemName.bugfrag_01, ItemClassification.filler, ItemType.BugFrag, count=1) +] + +item_frequencies: typing.Dict[str, int] = { + ItemName.Progressive_Undernet_Rank: 8, + ItemName.ExpMem: 2, + ItemName.Unlocker: 10, + ItemName.HPMemory: 23, + ItemName.RegUP1: 4, + ItemName.RegUP2: 13, + ItemName.RegUP3: 4, + ItemName.Untrap: 2, + ItemName.SubMem: 4, + ItemName.MiniEnrg: 3, + ItemName.FullEnrg: 5, + ItemName.CopyDmg_star: 3, + ItemName.Charge_plus_White: 2, + ItemName.Charge_plus_Pink: 2, + ItemName.zenny_600z: 2, + ItemName.zenny_800z: 2, + ItemName.zenny_1000z: 2, + ItemName.zenny_1200z: 2, + ItemName.bugfrag_01: 5, +} +all_items: typing.List[ItemData] = keyItemList + subChipList + chipList + programList + zennyList + bugFragList +item_table: typing.Dict[str, ItemData] = {item.itemName: item for item in all_items} +items_by_id: typing.Dict[int, ItemData] = {item.code: item for item in all_items} diff --git a/worlds/mmbn3/Locations.py b/worlds/mmbn3/Locations.py new file mode 100644 index 000000000000..fc5910334055 --- /dev/null +++ b/worlds/mmbn3/Locations.py @@ -0,0 +1,359 @@ +import typing + +from BaseClasses import Location +from .Names.LocationName import LocationName + + +class LocationData: + name: str = "" + id: int = 0x00 + + flag_byte: int = 0x2000030 + flag_mask: int = 0x01 + + text_archive_address: int = 0x00 + text_script_index: int = 0 + text_box_indices: typing.List[int] = [0] + inject_name: bool = False + hint_flag: int = None + hint_flag_mask: int = None + + def __init__(self, name, id, flag, mask, text_archive_address=0x0, text_script_index=0x0, + text_box_indices=None, inject_name=False, hint_flag=None, hint_flag_mask=None): + self.name = name + self.id = id + self.flag_byte = flag + self.flag_mask = mask + self.text_archive_address = text_archive_address + self.text_script_index = text_script_index + if text_box_indices is None: + text_box_indices = [0] + self.text_box_indices = text_box_indices + self.inject_name = inject_name + self.hint_flag = hint_flag + self.hint_flag_mask = hint_flag_mask + + +class MMBN3Location(Location): + game: str = "MegaMan Battle Network 3" + + +bmds = [ + LocationData(LocationName.ACDC_1_Southwest_BMD, 0xb31000, 0x020001d0, 0x40, 0x7643B8, 231, [1]), + LocationData(LocationName.ACDC_1_Northeast_BMD, 0xb31001, 0x020001d0, 0x80, 0x7643B8, 230, [1]), + LocationData(LocationName.ACDC_2_Center_BMD, 0xb31002, 0x20001d1, 0x40, 0x7658FC, 231, [1]), + LocationData(LocationName.ACDC_2_North_BMD, 0xb31003, 0x20001d1, 0x80, 0x7658FC, 230, [1]), + LocationData(LocationName.ACDC_3_Southwest_BMD, 0xb31004, 0x20001d2, 0x80, 0x766AE0, 230, [1]), + LocationData(LocationName.ACDC_3_Northeast_BMD, 0xb31005, 0x20001d2, 0x40, 0x766AE0, 231, [1]), + LocationData(LocationName.SciLab_1_WWW_BMD, 0xb31006, 0x20001d8, 0x40, 0x7694B4, 231, [1]), + LocationData(LocationName.SciLab_1_East_BMD, 0xb31007, 0x20001d8, 0x80, 0x7694B4, 230, [1]), + LocationData(LocationName.SciLab_2_West_BMD, 0xb31008, 0x20001d9, 0x80, 0x76A4F4, 230, [1, 2]), + LocationData(LocationName.SciLab_2_South_BMD, 0xb31009, 0x20001d9, 0x40, 0x76A4F4, 231, [1]), + LocationData(LocationName.Yoka_1_North_BMD, 0xb3100a, 0x20001e0, 0x80, 0x76D1B0, 230, [1]), + LocationData(LocationName.Yoka_1_WWW_BMD, 0xb3100b, 0x20001e0, 0x20, 0x76D1B0, 232, [1]), + LocationData(LocationName.Yoka_2_Upper_BMD, 0xb3100c, 0x20001e1, 0x40, 0x76DC80, 231, [1]), + LocationData(LocationName.Yoka_2_Lower_BMD, 0xb3100d, 0x20001e1, 0x80, 0x76DC80, 230, [1]), + LocationData(LocationName.Beach_1_BMD, 0xb3100e, 0x20001e8, 0x80, 0x76FF68, 230, [1]), + LocationData(LocationName.Beach_2_West_BMD, 0xb3100f, 0x20001e9, 0x80, 0x770A90, 230, [1]), + LocationData(LocationName.Beach_2_East_BMD, 0xb31010, 0x20001e9, 0x40, 0x770A90, 231, [1, 2]), + LocationData(LocationName.Undernet_1_South_BMD, 0xb31011, 0x20001f0, 0x80, 0x77307C, 230, [1]), + LocationData(LocationName.Undernet_1_WWW_BMD, 0xb31012, 0x20001f0, 0x40, 0x77307C, 231, [1]), + LocationData(LocationName.Undernet_2_Upper_BMD, 0xb31013, 0x20001f1, 0x80, 0x773700, 230, [1, 2]), + LocationData(LocationName.Undernet_2_Lower_BMD, 0xb31014, 0x20001f1, 0x40, 0x773700, 231, [1]), + LocationData(LocationName.Undernet_3_South_BMD, 0xb31015, 0x20001f2, 0x40, 0x773EA8, 231, [1]), + LocationData(LocationName.Undernet_3_Central_BMD, 0xb31016, 0x20001f2, 0x80, 0x773EA8, 230, [1]), + LocationData(LocationName.Undernet_4_Bottom_West_BMD, 0xb31017, 0x20001f3, 0x40, 0x7746C8, 231, [1]), + LocationData(LocationName.Undernet_4_Top_Pillar_BMD, 0xb31018, 0x20001f3, 0x20, 0x7746C8, 232, [1]), + LocationData(LocationName.Undernet_4_Top_North_BMD, 0xb31019, 0x20001f3, 0x80, 0x7746C8, 230, [1]), + LocationData(LocationName.Undernet_5_Upper_BMD, 0xb3101a, 0x20001f4, 0x40, 0x774FC8, 231, [1]), + LocationData(LocationName.Undernet_5_Lower_BMD, 0xb3101b, 0x20001f4, 0x80, 0x774FC8, 230, [1]), + LocationData(LocationName.Undernet_6_East_BMD, 0xb3101c, 0x20001f5, 0x80, 0x775390, 230, [1, 2]), + LocationData(LocationName.Undernet_6_Central_BMD, 0xb3101d, 0x20001f5, 0x20, 0x775390, 232, [1]), + LocationData(LocationName.Undernet_6_TV_BMD, 0xb3101e, 0x20001f5, 0x40, 0x775390, 231, [1]), + LocationData(LocationName.Undernet_7_West_BMD, 0xb3101f, 0x20001f6, 0x80, 0x775934, 230, [1]), + LocationData(LocationName.Undernet_7_Northwest_BMD, 0xb31020, 0x20001f6, 0x20, 0x775934, 232, [1]), + LocationData(LocationName.Undernet_7_Northeast_BMD, 0xb31021, 0x20001f6, 0x40, 0x775934, 231, [1]), + LocationData(LocationName.Secret_1_South_BMD, 0xb31022, 0x2000200, 0x40, 0x7771CC, 241, [1]), + LocationData(LocationName.Secret_1_Northeast_BMD, 0xb31023, 0x2000200, 0x20, 0x7771CC, 242, [1]), + LocationData(LocationName.Secret_1_Northwest_BMD, 0xb31024, 0x2000200, 0x80, 0x7771CC, 240, [1]), + LocationData(LocationName.Secret_2_Upper_BMD, 0xb31025, 0x2000201, 0x80, 0x777888, 240, [1]), + LocationData(LocationName.Secret_2_Lower_BMD, 0xb31026, 0x2000201, 0x20, 0x777888, 242, [1]), + LocationData(LocationName.Secret_2_Island_BMD, 0xb31027, 0x2000201, 0x40, 0x777888, 241, [1]), + LocationData(LocationName.Secret_3_South_BMD, 0xb31028, 0x2000202, 0x80, 0x777EDC, 240, [1]), + LocationData(LocationName.Secret_3_Island_BMD, 0xb31029, 0x2000202, 0x40, 0x777EDC, 241, [1]), + LocationData(LocationName.Secret_3_BugFrag_BMD, 0xb3102a, 0x2000202, 0x20, 0x777EDC, 242, [1]), + LocationData(LocationName.School_1_Entrance_BMD, 0xb3102b, 0x2000208, 0x2, 0x759BF8, 237, [1, 2]), + LocationData(LocationName.School_1_North_Central_BMD, 0xb3102c, 0x2000208, 0x4, 0x759BF8, 236, [1]), + LocationData(LocationName.School_1_Far_West_BMD_2, 0xb3102d, 0x2000208, 0x1, 0x759BF8, 238, [1]), + LocationData(LocationName.School_2_Entrance_BMD, 0xb3102e, 0x2000209, 0x4, 0x75A0B4, 236, [1]), + LocationData(LocationName.School_2_South_BMD, 0xb3102f, 0x2000209, 0x1, 0x75A0B4, 238, [1]), + LocationData(LocationName.School_2_Mainframe_BMD, 0xb31030, 0x2000209, 0x2, 0x75A0B4, 237, [1]), + LocationData(LocationName.Zoo_1_East_BMD, 0xb31031, 0x2000210, 0x80, 0x75A7F8, 230, [1]), + LocationData(LocationName.Zoo_1_Central_BMD, 0xb31032, 0x2000210, 0x20, 0x75A7F8, 232, [1]), + LocationData(LocationName.Zoo_1_North_BMD, 0xb31033, 0x2000210, 0x40, 0x75A7F8, 231, [1]), + LocationData(LocationName.Zoo_2_East_BMD, 0xb31034, 0x2000211, 0x40, 0x75ADA8, 231, [1]), + LocationData(LocationName.Zoo_2_Central_BMD, 0xb31035, 0x2000211, 0x80, 0x75ADA8, 230, [1]), + LocationData(LocationName.Zoo_2_West_BMD, 0xb31036, 0x2000211, 0x20, 0x75ADA8, 232, [1]), + LocationData(LocationName.Zoo_3_North_BMD, 0xb31037, 0x2000212, 0x10, 0x75B5EC, 238, [1]), + LocationData(LocationName.Zoo_3_Central_BMD, 0xb31038, 0x2000212, 0x80, 0x75B5EC, 235, [1]), + LocationData(LocationName.Zoo_3_Path_BMD, 0xb31039, 0x2000212, 0x40, 0x75B5EC, 236, [1]), + LocationData(LocationName.Zoo_3_Northwest_BMD, 0xb3103a, 0x2000212, 0x20, 0x75B5EC, 237, [1]), + LocationData(LocationName.Zoo_4_West_BMD, 0xb3103b, 0x2000213, 0x40, 0x75BEB0, 236, [1, 2]), + LocationData(LocationName.Zoo_4_Northwest_BMD, 0xb3103c, 0x2000213, 0x80, 0x75BEB0, 235, [1]), + LocationData(LocationName.Zoo_4_Southeast_BMD, 0xb3103d, 0x2000213, 0x20, 0x75BEB0, 237, [1]), + LocationData(LocationName.Hades_South_BMD, 0xb3103e, 0x20001eb, 0x20, 0x772898, 232, [1]), + LocationData(LocationName.Hospital_1_North_BMD, 0xb3103f, 0x2000218, 0x20, 0x75C864, 232, [1]), + LocationData(LocationName.Hospital_1_Center_BMD, 0xb31040, 0x2000218, 0x80, 0x75C864, 230, [1]), + LocationData(LocationName.Hospital_1_West_BMD, 0xb31041, 0x2000218, 0x40, 0x75C864, 231, [1, 2]), + LocationData(LocationName.Hospital_2_Southwest_BMD, 0xb31042, 0x2000219, 0x20, 0x75CD64, 232, [1]), + LocationData(LocationName.Hospital_2_Central_BMD, 0xb31043, 0x2000219, 0x40, 0x75CD64, 231, [1]), + LocationData(LocationName.Hospital_2_Island_BMD, 0xb31044, 0x2000219, 0x80, 0x75CD64, 230, [1]), + LocationData(LocationName.Hospital_3_Central_BMD, 0xb31045, 0x200021a, 0x80, 0x75D004, 230, [1]), + LocationData(LocationName.Hospital_3_West_BMD, 0xb31046, 0x200021a, 0x40, 0x75D004, 231, [1]), + LocationData(LocationName.Hospital_3_Northwest_BMD, 0xb31047, 0x200021a, 0x20, 0x75D004, 232, [1, 2]), + LocationData(LocationName.Hospital_4_Central_BMD, 0xb31048, 0x200021b, 0x20, 0x75D1BC, 232, [1]), + LocationData(LocationName.Hospital_4_Southeast_BMD, 0xb31049, 0x200021b, 0x80, 0x75D1BC, 230, [1]), + LocationData(LocationName.Hospital_4_North_BMD, 0xb3104a, 0x200021b, 0x40, 0x75D1BC, 231, [1]), + LocationData(LocationName.Hospital_5_Southwest_BMD, 0xb3104b, 0x200021c, 0x20, 0x75D3DC, 232, [1]), + LocationData(LocationName.Hospital_5_Northeast_BMD, 0xb3104c, 0x200021c, 0x80, 0x75D3DC, 230, [1]), + LocationData(LocationName.Hospital_5_Island_BMD, 0xb3104d, 0x200021c, 0x40, 0x75D3DC, 231, [1]), + LocationData(LocationName.WWW_1_Central_BMD, 0xb3104e, 0x2000220, 0x10, 0x75D630, 233, [1]), + LocationData(LocationName.WWW_1_West_BMD, 0xb3104f, 0x2000220, 0x40, 0x75D630, 231, [1]), + LocationData(LocationName.WWW_1_East_BMD, 0xb31050, 0x2000220, 0x20, 0x75D630, 232, [1]), + LocationData(LocationName.WWW_2_East_BMD, 0xb31051, 0x2000221, 0x40, 0x75D790, 231, [1, 2]), + LocationData(LocationName.WWW_2_Northwest_BMD, 0xb31052, 0x2000221, 0x20, 0x75D790, 232, [1]), + LocationData(LocationName.WWW_3_East_BMD, 0xb31053, 0x2000222, 0x40, 0x75D8EC, 231, [1]), + LocationData(LocationName.WWW_3_North_BMD, 0xb31054, 0x2000222, 0x20, 0x75D8EC, 232, [1]), + LocationData(LocationName.WWW_4_Northwest_BMD, 0xb31055, 0x2000223, 0x40, 0x75DA68, 231, [1]), + LocationData(LocationName.WWW_4_Central_BMD, 0xb31056, 0x2000223, 0x20, 0x75DA68, 232, [1]), + LocationData(LocationName.ACDC_Dog_House_BMD, 0xb31057, 0x2000240, 0x80, 0x7608E4, 230, [1]), + LocationData(LocationName.ACDC_Lans_Security_Panel_BMD, 0xb31058, 0x2000242, 0x80, 0x761954, 230, [1]), + LocationData(LocationName.ACDC_Yais_Phone_BMD, 0xb31059, 0x2000244, 0x8, 0x762A04, 230, [1]), + LocationData(LocationName.ACDC_NumberMan_Display_BMD, 0xb3105a, 0x2000248, 0x8, 0x763AB4, 230, [1]), + LocationData(LocationName.ACDC_Tank_BMD_1, 0xb3105b, 0x2000247, 0x40, 0x7635FC, 231, [1, 2]), + LocationData(LocationName.ACDC_Tank_BMD_2, 0xb3105c, 0x2000247, 0x80, 0x7635FC, 230, [1]), + LocationData(LocationName.ACDC_School_Server_BMD_1, 0xb3105d, 0x2000242, 0x8, 0x761AC0, 230, [1]), + LocationData(LocationName.ACDC_School_Server_BMD_2, 0xb3105e, 0x2000242, 0x4, 0x761AC0, 231, [1]), + LocationData(LocationName.ACDC_School_Blackboard_BMD, 0xb3105f, 0x2000240, 0x8, 0x760B48, 230, [1, 2]), + LocationData(LocationName.SciLab_Vending_Machine_BMD, 0xb31060, 0x2000241, 0x80, 0x760E80, 230, [1, 2]), + LocationData(LocationName.SciLab_Virus_Lab_Door_BMD_1, 0xb31061, 0x2000249, 0x8, 0x763ED8, 230, [1]), + LocationData(LocationName.SciLab_Virus_Lab_Door_BMD_2, 0xb310ed, 0x2000249, 0x4, 0x763ED8, 231, [1]), + LocationData(LocationName.SciLab_Dads_Computer_BMD, 0xb31062, 0x2000241, 0x8, 0x761498, 230, [1]), + LocationData(LocationName.Yoka_Armor_BMD, 0xb31063, 0x2000248, 0x80, 0x763908, 230, [1, 2]), + LocationData(LocationName.Yoka_TV_BMD, 0xb31064, 0x2000247, 0x8, 0x76377C, 230, [1]), + LocationData(LocationName.Yoka_Hot_Spring_BMD, 0xb31065, 0x200024b, 0x20, 0x7603F8, 230, [1]), + LocationData(LocationName.Yoka_Ticket_Machine_BMD, 0xb31066, 0x2000246, 0x8, 0x763420, 230, [1, 2]), + LocationData(LocationName.Yoka_Giraffe_BMD, 0xb31067, 0x200024b, 0x80, 0x7602E8, 230, [1]), + LocationData(LocationName.Yoka_Panda_BMD, 0xb31068, 0x2000249, 0x80, 0x763C88, 230, [1, 2]), + LocationData(LocationName.Beach_Hospital_Bed_BMD, 0xb31069, 0x2000245, 0x8, 0x76312C, 230, [1, 2]), + LocationData(LocationName.Beach_TV_BMD, 0xb3106a, 0x2000245, 0x80, 0x762CF0, 230, [1]), + LocationData(LocationName.Beach_Vending_Machine_BMD, 0xb3106b, 0x2000246, 0x80, 0x7632B4, 230, [1]), + LocationData(LocationName.Beach_News_Van_BMD, 0xb3106c, 0x2000243, 0x80, 0x761C10, 230, [1]), + LocationData(LocationName.Beach_Battle_Console_BMD, 0xb3106d, 0x2000243, 0x8, 0x761E00, 230, [1]), + LocationData(LocationName.Beach_Security_System_BMD, 0xb3106e, 0x2000244, 0x40, 0x76274C, 231, [1, 2]), + LocationData(LocationName.Beach_Broadcast_Computer_BMD, 0xb3106f, 0x200024b, 0x2, 0x7606E4, 230, [1]), + LocationData(LocationName.Hades_Gargoyle_BMD, 0xb31070, 0x200024b, 0x8, 0x76059C, 230, [1]), + LocationData(LocationName.WWW_Wall_BMD, 0xb31071, 0x200024a, 0x80, 0x7641A4, 230, [1]), + LocationData(LocationName.Mayls_HP_BMD, 0xb31072, 0x2000239, 0x80, 0x75DCC4, 230, [1]), + LocationData(LocationName.Yais_HP_BMD_1, 0xb31073, 0x200023b, 0x80, 0x75E018, 230, [1]), + LocationData(LocationName.Yais_HP_BMD_2, 0xb31074, 0x200023b, 0x40, 0x75E018, 231, [1, 2]), + LocationData(LocationName.Dexs_HP_BMD_1, 0xb31075, 0x200023a, 0x40, 0x75DEA4, 231, [1]), + LocationData(LocationName.Dexs_HP_BMD_2, 0xb31076, 0x200023a, 0x80, 0x75DEA4, 230, [1]), + LocationData(LocationName.Tamakos_HP_BMD, 0xb31077, 0x200023c, 0x80, 0x75E2D4, 230, [1]), + LocationData(LocationName.Undernet_7_Upper_BMD, 0xb31078, 0x20001f6, 0x1, 0x775934, 250, [1]), + LocationData(LocationName.School_1_KeyDataA_BMD, 0xb31079, 0x2000208, 0x80, 0x759BF8, 230, [1]), + LocationData(LocationName.School_1_KeyDataB_BMD, 0xb3107a, 0x2000208, 0x40, 0x759BF8, 231, [1]), + LocationData(LocationName.School_1_KeyDataC_BMD, 0xb3107b, 0x2000208, 0x20, 0x759BF8, 232, [1]), + LocationData(LocationName.School_2_CodeC_BMD, 0xb3107c, 0x2000209, 0x20, 0x75A0B4, 232, [1]), + LocationData(LocationName.School_2_CodeA_BMD, 0xb3107d, 0x2000209, 0x80, 0x75A0B4, 230, [1]), + LocationData(LocationName.School_2_CodeB_BMD, 0xb3107e, 0x2000209, 0x40, 0x75A0B4, 231, [1]), + # LocationData(LocationName.Hades_HadesKey_BMD, 0xb3107f, 0x20001eb, 0x40, 0x772898, 231, [1]), + # LocationData(LocationName.WWW_1_South_BMD, 0xb31080, 0x2000220, 0x80, 0x75D630, 230, [1]), + # LocationData(LocationName.WWW_2_West_BMD, 0xb31081, 0x2000221, 0x80, 0x75D790, 230, [1]), + # LocationData(LocationName.WWW_3_South_BMD, 0xb31082, 0x2000222, 0x80, 0x75D8EC, 230, [1]), + # LocationData(LocationName.WWW_4_East_BMD, 0xb31083, 0x2000223, 0x80, 0x75DA68, 230, [1]) +] + +pmds = [ + LocationData(LocationName.ACDC_1_PMD, 0xb31084, 0x020001d0, 0x20, 0x7643B8, 232, [1]), + LocationData(LocationName.Yoka_1_PMD, 0xb31085, 0x20001e0, 0x40, 0x76D1B0, 231, [1]), + LocationData(LocationName.Beach_1_PMD, 0xb31086, 0x20001e8, 0x40, 0x76FF68, 231, [1, 2]), + LocationData(LocationName.Undernet_7_PMD, 0xb31087, 0x20001f6, 0x10, 0x775934, 233, [1]), + LocationData(LocationName.Mayls_HP_PMD, 0xb31088, 0x2000239, 0x40, 0x75DCC4, 231, [1]), + LocationData(LocationName.SciLab_Dads_Computer_PMD, 0xb31089, 0x2000241, 0x4, 0x761498, 231, [1]), + LocationData(LocationName.Zoo_Panda_PMD, 0xb3108a, 0x2000249, 0x40, 0x763C88, 231, [1]), + LocationData(LocationName.Beach_DNN_Security_Panel_PMD, 0xb3108b, 0x2000244, 0x80, 0x76274C, 230, [1]), + LocationData(LocationName.Beach_DNN_Main_Console_PMD, 0xb3108c, 0x200024b, 0x1, 0x7606E4, 231, [1]), + LocationData(LocationName.Tamakos_HP_PMD, 0xb3108d, 0x200023c, 0x40, 0x75E2D4, 231, [1]) +] + +overworlds = [ + LocationData(LocationName.Yoka_Mr_Quiz, 0xb310ec, 0x200005f, 0x8, 0x7473F8, 197, [0, 1]), + LocationData(LocationName.Yoka_Quiz_Master, 0xb3108e, 0x200005f, 0x4, 0x748894, 202, [0]), + LocationData(LocationName.Hospital_Quiz_Queen, 0xb3108f, 0x200005f, 0x2, 0x757724, 202, [0]), + LocationData(LocationName.Hades_Quiz_King, 0xb31090, 0x2000164, 0x8, 0x7519B0, 207, [0]), + LocationData(LocationName.ACDC_SonicWav_W_Trade, 0xb31091, 0x2000162, 0x10, 0x73A7F8, 192, [0], True, 0x2000160, 0x80), + LocationData(LocationName.ACDC_Bubbler_C_Trade, 0xb31092, 0x2000162, 0x8, 0x737634, 192, [0], True, 0x2000160, 0x40), + LocationData(LocationName.ACDC_Recov120_S_Trade, 0xb31093, 0x2000163, 0x40, 0x72FA7C, 192, [0], True, 0x2000160, 0x02), + LocationData(LocationName.SciLab_Shake1_S_Trade, 0xb31094, 0x2000163, 0x10, 0x73B9C8, 192, [0], True, 0x2000161, 0x80), + LocationData(LocationName.Yoka_FireSwrd_P_Trade, 0xb31095, 0x2000162, 0x4, 0x745488, 192, [0], True, 0x2000160, 0x20), + LocationData(LocationName.Hospital_DynaWav_V_Trade, 0xb31096, 0x2000163, 0x4, 0x754D00, 202, [0], True, 0x2000161, 0x20), + LocationData(LocationName.Beach_DNN_WideSwrd_C_Trade, 0xb31097, 0x2000162, 0x1, 0x750C9C, 192, [0], True, 0x2000160, 0x08), + LocationData(LocationName.Beach_DNN_HoleMetr_H_Trade, 0xb31098, 0x2000164, 0x10, 0x751110, 192, [0], True, 0x2000162, 0x80), + LocationData(LocationName.Beach_DNN_Shadow_J_Trade, 0xb31099, 0x2000163, 0x2, 0x750248, 192, [0], True, 0x2000161, 0x10), + LocationData(LocationName.Hades_GrabBack_K_Trade, 0xb3109a, 0x2000164, 0x80, 0x753A48, 192, [0], True, 0x2000161, 0x04), + LocationData(LocationName.Comedian, 0xb3109b, 0x200024d, 0x20, 0x76DC80, 3, [22]), + LocationData(LocationName.Villain, 0xb3109c, 0x200024d, 0x10, 0x77124C, 24, [24]), + LocationData(LocationName.ACDC_School_Desk, 0xb3109d, 0x200024c, 0x1, 0x739580, 236, [4, 5]), + LocationData(LocationName.ACDC_Class_5B_Bookshelf, 0xb3109e, 0x200024c, 0x40, 0x737634, 235, [5, 6]), + LocationData(LocationName.SciLab_Garbage_Can, 0xb3109f, 0x200024c, 0x8, 0x73AC20, 222, [4, 5]), + LocationData(LocationName.Yoka_Inn_Jars, 0xb310a0, 0x200024c, 0x80, 0x747B1C, 237, [4, 5]), + LocationData(LocationName.Yoka_Zoo_Garbage, 0xb310a1, 0x200024d, 0x8, 0x749444, 226, [4]), + LocationData(LocationName.Beach_Department_Store, 0xb310a2, 0x2000161, 0x40, 0x74C27C, 196, [0, 1]), + LocationData(LocationName.Beach_Hospital_Plaque, 0xb310a3, 0x200024c, 0x4, 0x754394, 220, [3, 4]), + LocationData(LocationName.Beach_Hospital_Pink_Door, 0xb310a4, 0x200024d, 0x4, 0x754D00, 220, [4]), + LocationData(LocationName.Beach_Hospital_Tree, 0xb310a5, 0x200024c, 0x2, 0x757724, 222, [4]), + LocationData(LocationName.Beach_Hospital_Hidden_Conversation, 0xb310a6, 0x2000162, 0x20, 0x7586F8, 191, [0]), + LocationData(LocationName.Beach_Hospital_Girl, 0xb310a7, 0x2000160, 0x1, 0x754394, 191, [0, 1]), + LocationData(LocationName.Beach_DNN_Kiosk, 0xb310a8, 0x200024e, 0x80, 0x74E184, 76, [0]), + LocationData(LocationName.Beach_DNN_Boxes, 0xb310a9, 0x200024c, 0x20, 0x74FAAC, 222, [4, 5]), + LocationData(LocationName.Beach_DNN_Poster, 0xb310aa, 0x200024d, 0x80, 0x751110, 227, [3, 4]), + LocationData(LocationName.Hades_Boat_Dock, 0xb310ab, 0x200024c, 0x10, 0x7519B0, 223, [3]), + LocationData(LocationName.WWW_Control_Room_1_Screen, 0xb310ac, 0x200024d, 0x40, 0x7596C4, 222, [3, 4]), + LocationData(LocationName.WWW_Wilys_Desk, 0xb310ad, 0x200024d, 0x2, 0x759384, 229, [3]), + LocationData(LocationName.Undernet_4_Pillar_Prog, 0xb310ae, 0x2000161, 0x1, 0x7746C8, 191, [0, 1]) +] + +jobs = [ + LocationData(LocationName.Please_deliver_this, 0xb310af, 0x2000300, 0x8, 0x7643B8, 195, [0]), + LocationData(LocationName.My_Navi_is_sick, 0xb310b0, 0x2000300, 0x4, 0x73AC20, 192, [0, 1]), + LocationData(LocationName.Help_me_with_my_son, 0xb310b1, 0x2000300, 0x2, 0x73F8FC, 193, [0, 1]), + LocationData(LocationName.Transmission_error, 0xb310b2, 0x2000300, 0x1, 0x73CF54, 193, [0]), + LocationData(LocationName.Chip_Prices, 0xb310b3, 0x2000301, 0x80, 0x767928, 195, [0]), + LocationData(LocationName.Im_broke, 0xb310b4, 0x2000301, 0x40, 0x746578, 194, [1]), + LocationData(LocationName.Rare_chips_for_cheap, 0xb310b5, 0x2000301, 0x20, 0x762A04, 192, [0]), + LocationData(LocationName.Be_my_boyfriend, 0xb310b6, 0x2000301, 0x10, 0x77124C, 203, [0]), + LocationData(LocationName.Will_you_deliver, 0xb310b7, 0x2000301, 0x8, 0x745488, 205, [0]), + # LocationData(LocationName.Look_for_friends, 0xb310b8, 0x2000300, 0x80, 0x72DAFC, 210, [0]), + # LocationData(LocationName.Stuntmen_wanted, 0xb310b9, 0x2000300, 0x40, 0x76FF68, 194, [0]), + # LocationData(LocationName.Riot_stopped, 0xb310ba, 0x2000300, 0x20, 0x74E184, 193, [0]), + # LocationData(LocationName.Gathering_Data, 0xb310bb, 0x2000300, 0x10, 0x739580, 193, [0]), + LocationData(LocationName.Somebody_please_help, 0xb310bc, 0x2000301, 0x4, 0x73A14C, 193, [0]), + LocationData(LocationName.Looking_for_condor, 0xb310bd, 0x2000301, 0x2, 0x749444, 203, [0]), + LocationData(LocationName.Help_with_rehab, 0xb310be, 0x2000301, 0x1, 0x762CF0, 192, [3]), + LocationData(LocationName.Old_Master, 0xb310bf, 0x2000302, 0x80, 0x760E80, 193, [0]), + LocationData(LocationName.Catching_gang_members, 0xb310c0, 0x2000302, 0x40, 0x76EAE4, 193, [0]), + LocationData(LocationName.Please_adopt_a_virus, 0xb310c1, 0x2000302, 0x20, 0x76A4F4, 193, [0]), + LocationData(LocationName.Legendary_Tomes, 0xb310c2, 0x2000302, 0x10, 0x772898, 193, [0]), + LocationData(LocationName.Legendary_Tomes_Treasure, 0xb310c3, 0x200024e, 0x40, 0x739580, 225, [15]), + LocationData(LocationName.Hide_and_seek_First_Child, 0xb310c4, 0x2000188, 0x4, 0x75A7F8, 191, [0]), + LocationData(LocationName.Hide_and_seek_Second_Child, 0xb310c5, 0x2000188, 0x2, 0x75ADA8, 191, [0]), + LocationData(LocationName.Hide_and_seek_Third_Child, 0xb310c6, 0x2000188, 0x1, 0x75B5EC, 191, [0]), + LocationData(LocationName.Hide_and_seek_Fourth_Child, 0xb310c7, 0x2000189, 0x80, 0x75BEB0, 191, [0]), + LocationData(LocationName.Hide_and_seek_Completion, 0xb310c8, 0x2000302, 0x8, 0x7406A0, 193, [0]), + LocationData(LocationName.Finding_the_blue_Navi, 0xb310c9, 0x2000302, 0x4, 0x773700, 192, [0]), + LocationData(LocationName.Give_your_support, 0xb310ca, 0x2000302, 0x2, 0x752D80, 192, [0]), + LocationData(LocationName.Stamp_collecting, 0xb310cb, 0x2000302, 0x1, 0x756074, 193, [0]), + LocationData(LocationName.Help_with_a_will, 0xb310cc, 0x2000303, 0x80, 0x7382B0, 197, [0]) +] + +number_traders = [ + LocationData(LocationName.Numberman_Code_01, 0xb310cd, 0x2000430, 0x80, 0x735734, 30, [0]), + LocationData(LocationName.Numberman_Code_02, 0xb310ce, 0x2000430, 0x40, 0x735734, 31, [0]), + LocationData(LocationName.Numberman_Code_03, 0xb310cf, 0x2000430, 0x20, 0x735734, 32, [0]), + LocationData(LocationName.Numberman_Code_04, 0xb310d0, 0x2000430, 0x10, 0x735734, 33, [0]), + LocationData(LocationName.Numberman_Code_05, 0xb310d1, 0x2000430, 0x08, 0x735734, 34, [0]), + LocationData(LocationName.Numberman_Code_06, 0xb310d2, 0x2000430, 0x04, 0x735734, 35, [0]), + LocationData(LocationName.Numberman_Code_07, 0xb310d3, 0x2000430, 0x02, 0x735734, 36, [0]), + LocationData(LocationName.Numberman_Code_08, 0xb310d4, 0x2000430, 0x01, 0x735734, 37, [0]), + LocationData(LocationName.Numberman_Code_09, 0xb310d5, 0x2000431, 0x80, 0x735734, 38, [0]), + LocationData(LocationName.Numberman_Code_10, 0xb310d6, 0x2000431, 0x40, 0x735734, 39, [0]), + LocationData(LocationName.Numberman_Code_11, 0xb310d7, 0x2000431, 0x20, 0x735734, 40, [0]), + LocationData(LocationName.Numberman_Code_12, 0xb310d8, 0x2000431, 0x10, 0x735734, 41, [0]), + LocationData(LocationName.Numberman_Code_13, 0xb310d9, 0x2000431, 0x08, 0x735734, 42, [0]), + LocationData(LocationName.Numberman_Code_14, 0xb310da, 0x2000431, 0x04, 0x735734, 43, [0]), + LocationData(LocationName.Numberman_Code_15, 0xb310db, 0x2000431, 0x02, 0x735734, 44, [0]), + LocationData(LocationName.Numberman_Code_16, 0xb310dc, 0x2000431, 0x01, 0x735734, 45, [0]), + LocationData(LocationName.Numberman_Code_17, 0xb310dd, 0x2000432, 0x80, 0x735734, 46, [0]), + LocationData(LocationName.Numberman_Code_18, 0xb310de, 0x2000432, 0x40, 0x735734, 47, [0]), + LocationData(LocationName.Numberman_Code_19, 0xb310df, 0x2000432, 0x20, 0x735734, 48, [0]), + LocationData(LocationName.Numberman_Code_20, 0xb310e0, 0x2000432, 0x10, 0x735734, 49, [0]), + LocationData(LocationName.Numberman_Code_21, 0xb310e1, 0x2000432, 0x08, 0x735734, 50, [0]), + LocationData(LocationName.Numberman_Code_22, 0xb310e2, 0x2000432, 0x04, 0x735734, 51, [0]), + LocationData(LocationName.Numberman_Code_23, 0xb310e3, 0x2000432, 0x02, 0x735734, 52, [0]), + LocationData(LocationName.Numberman_Code_24, 0xb310e4, 0x2000432, 0x01, 0x735734, 53, [0]), + LocationData(LocationName.Numberman_Code_25, 0xb310e5, 0x2000433, 0x80, 0x735734, 54, [0]), + LocationData(LocationName.Numberman_Code_26, 0xb310e6, 0x2000433, 0x40, 0x735734, 55, [0]), + LocationData(LocationName.Numberman_Code_27, 0xb310e7, 0x2000433, 0x20, 0x735734, 56, [0]), + LocationData(LocationName.Numberman_Code_28, 0xb310e8, 0x2000433, 0x10, 0x735734, 57, [0]), + LocationData(LocationName.Numberman_Code_29, 0xb310e9, 0x2000433, 0x08, 0x735734, 58, [0]), + LocationData(LocationName.Numberman_Code_30, 0xb310ea, 0x2000433, 0x04, 0x735734, 59, [0]), + LocationData(LocationName.Numberman_Code_31, 0xb310eb, 0x2000433, 0x02, 0x735734, 60, [0]) +] + +chocolate_shop = [ + LocationData(LocationName.Chocolate_Shop_01, 0xb310ee, 0x20001c0, 0x80, 0x73F8FC, 150, [0]), + LocationData(LocationName.Chocolate_Shop_02, 0xb310ef, 0x20001c0, 0x40, 0x73F8FC, 151, [0]), + LocationData(LocationName.Chocolate_Shop_03, 0xb310f0, 0x20001c0, 0x20, 0x73F8FC, 152, [0]), + LocationData(LocationName.Chocolate_Shop_04, 0xb310f1, 0x20001c0, 0x10, 0x73F8FC, 153, [0]), + LocationData(LocationName.Chocolate_Shop_05, 0xb310f2, 0x20001c0, 0x08, 0x73F8FC, 154, [0]), + LocationData(LocationName.Chocolate_Shop_06, 0xb310f3, 0x20001c0, 0x04, 0x73F8FC, 155, [0]), + LocationData(LocationName.Chocolate_Shop_07, 0xb310f4, 0x20001c0, 0x02, 0x73F8FC, 156, [0]), + LocationData(LocationName.Chocolate_Shop_08, 0xb310f5, 0x20001c0, 0x01, 0x73F8FC, 157, [0]), + + LocationData(LocationName.Chocolate_Shop_09, 0xb310f6, 0x20001c1, 0x80, 0x73F8FC, 158, [0]), + LocationData(LocationName.Chocolate_Shop_10, 0xb310f7, 0x20001c1, 0x40, 0x73F8FC, 159, [0]), + LocationData(LocationName.Chocolate_Shop_11, 0xb310f8, 0x20001c1, 0x20, 0x73F8FC, 160, [0]), + LocationData(LocationName.Chocolate_Shop_12, 0xb310f9, 0x20001c1, 0x10, 0x73F8FC, 161, [0]), + LocationData(LocationName.Chocolate_Shop_13, 0xb310fa, 0x20001c1, 0x08, 0x73F8FC, 162, [0]), + LocationData(LocationName.Chocolate_Shop_14, 0xb310fb, 0x20001c1, 0x04, 0x73F8FC, 163, [0]), + LocationData(LocationName.Chocolate_Shop_15, 0xb310fc, 0x20001c1, 0x02, 0x73F8FC, 164, [0]), + LocationData(LocationName.Chocolate_Shop_16, 0xb310fd, 0x20001c1, 0x01, 0x73F8FC, 165, [0]), + + LocationData(LocationName.Chocolate_Shop_17, 0xb310fe, 0x20001c2, 0x80, 0x73F8FC, 166, [0]), + LocationData(LocationName.Chocolate_Shop_18, 0xb310ff, 0x20001c2, 0x40, 0x73F8FC, 167, [0]), + LocationData(LocationName.Chocolate_Shop_19, 0xb31100, 0x20001c2, 0x20, 0x73F8FC, 168, [0]), + LocationData(LocationName.Chocolate_Shop_20, 0xb31101, 0x20001c2, 0x10, 0x73F8FC, 169, [0]), + LocationData(LocationName.Chocolate_Shop_21, 0xb31102, 0x20001c2, 0x08, 0x73F8FC, 170, [0]), + LocationData(LocationName.Chocolate_Shop_22, 0xb31103, 0x20001c2, 0x04, 0x73F8FC, 171, [0]), + LocationData(LocationName.Chocolate_Shop_23, 0xb31104, 0x20001c2, 0x02, 0x73F8FC, 172, [0]), + LocationData(LocationName.Chocolate_Shop_24, 0xb31105, 0x20001c2, 0x01, 0x73F8FC, 173, [0]), + + LocationData(LocationName.Chocolate_Shop_25, 0xb31106, 0x20001c3, 0x80, 0x73F8FC, 174, [0]), + LocationData(LocationName.Chocolate_Shop_26, 0xb31107, 0x20001c3, 0x40, 0x73F8FC, 175, [0]), + LocationData(LocationName.Chocolate_Shop_27, 0xb31108, 0x20001c3, 0x20, 0x73F8FC, 176, [0]), + LocationData(LocationName.Chocolate_Shop_28, 0xb31109, 0x20001c3, 0x10, 0x73F8FC, 177, [0]), + LocationData(LocationName.Chocolate_Shop_29, 0xb3110a, 0x20001c3, 0x08, 0x73F8FC, 178, [0]), + LocationData(LocationName.Chocolate_Shop_30, 0xb3110b, 0x20001c3, 0x04, 0x73F8FC, 179, [0]), + LocationData(LocationName.Chocolate_Shop_31, 0xb3110c, 0x20001c3, 0x02, 0x73F8FC, 180, [0]), + LocationData(LocationName.Chocolate_Shop_32, 0xb3110d, 0x20001c3, 0x01, 0x73F8FC, 181, [0]), +] + +always_excluded_locations = [ + LocationName.Undernet_7_PMD, + LocationName.Undernet_7_Northeast_BMD, + LocationName.Undernet_7_Northwest_BMD, + LocationName.Secret_1_Northwest_BMD, + LocationName.Secret_1_Northeast_BMD, + LocationName.Secret_1_South_BMD, + LocationName.Secret_2_Upper_BMD, + LocationName.Secret_2_Lower_BMD, + LocationName.Secret_2_Island_BMD, + LocationName.Secret_3_Island_BMD, + LocationName.Secret_3_BugFrag_BMD, + LocationName.Secret_3_South_BMD +] + + +all_locations: typing.List[LocationData] = bmds + pmds + overworlds + jobs + number_traders + chocolate_shop +scoutable_locations: typing.List[LocationData] = [loc for loc in all_locations if loc.hint_flag is not None] +location_table: typing.Dict[str, int] = {locData.name: locData.id for locData in all_locations} +location_data_table: typing.Dict[str, LocationData] = {locData.name: locData for locData in all_locations} + + +""" +def setup_locations(world, player: int): + # If we later include options to change what gets added to the random pool, + # this is where they would be changed + return {locData.name: locData.id for locData in all_locations} +""" diff --git a/worlds/mmbn3/Names/ItemName.py b/worlds/mmbn3/Names/ItemName.py new file mode 100644 index 000000000000..441bdc591c51 --- /dev/null +++ b/worlds/mmbn3/Names/ItemName.py @@ -0,0 +1,238 @@ +class ItemName(): + ## Chips + AirShoes_star = "AirShoes *" + AirShot3_star = "AirShot3 *" + AntiNavi_M = "AntiNavi M" + AntiRecv_B = "AntiRecv B" + AntiSword_Y = "AntiSword Y" + Aqua_plus_30_star = "Aqua+30 *" + Aura_F = "Aura F" + BambooSword_N = "BambooSword N" + Barr100_E = "Barr100 E" + Barr200_E = "Barr200 E" + Barrier_E = "Barrier E" + Barrier_L = "Barrier L" + BlkBomb1_P = "BlkBomb1 P" + BlkBomb2_S = "BlkBomb2 S" + Cannon_C = "Cannon C" + CopyDmg_star = "CopyDmg *" + CustSwrd_Z = "CustSwrd Z" + DynaWave_V = "DynaWave V" + ElecSwrd_P = "ElecSwrd P" + Fire_plus_30_star = "Fire+30 *" + FireRat_H = "FireRat H" + FireSwrd_P = "FireSwrd P" + FstGauge_star = "FstGauge *" + GaiaBlde_star = "GaiaBlde *" + Geddon1_star = "Geddon1 *" + Geddon1_D = "Geddon1 D" + Geddon3_U = "Geddon3 U" + Geyser_B = "Geyser B" + GrabBack_K = "GrabBack K" + GrabRvng_A = "GrabRvng A" + GrabRvng_Y = "GrabRvng Y" + GutStrght_S = "GtStrght S" + Guardian_O = "Guardian O" + GutImpact_H = "GutImpact H" + GutImpct_J = "GutImpct J" + GutPunch_E = "GutPunch E" + GutPunch_B = "GutPunch B" + GutStrgt_Q = "GutStrgt Q" + Hammer_T = "Hammer T" + HeatSide_T = "HeatSide T" + HeroSwrd_P = "HeroSwrd P" + HiCannon_star = "HiCannon *" + Hole_star = "Hole *" + IceStage_star = "IceStage *" + Invis_star = "Invis *" + Jealousy_J = "Jealousy J" + Lance_S = "Lance S" + LongSwrd_E = "LongSwrd E" + Magnum1_A = "Magnum1 A" + Muramasa_M = "Muramasa M" + Navi_plus_40_star = "Navi+40 *" + Panic_C = "Panic C" + PanlOut3_star = "PanlOut3 *" + Poltergeist_G = "Poltergeist G" + Prism_Q = "Prism Q" + Recov10_star = "Recov10 *" + Recov120_star = "Recov120 *" + Recov120_O = "Recov120 O" + Recov120_S = "Recov120 S" + Recov150_P = "Recov150 P" + Recov200_N = "Recov200 N" + Recov30_star = "Recov30 *" + Recov300_R = "Recov300 R" + Recov50_G = "Recov50 G" + Repair_star = "Repair *" + Repair_A = "Repair A" + Rockcube_star = "Rockcube *" + Rook_F = "Rook F" + Salamndr_star = "Salamndr *" + SandStage_C = "SandStage C" + SideGun_S = "SideGun S" + Slasher_B = "Slasher B" + SloGuage_star = "SloGuage *" + Snake_D = "Snake D" + Snctuary_C = "Snctuary C" + Spreader_star = "Spreader *" + Spreader_N = "Spreader N" + Spreader_P = "Spreader P" + StepCross_Q = "StepCross Q" + StepCross_R = "StepCross R" + StepSwrd_M = "StepSwrd M" + StepSwrd_N = "StepSwrd N" + StepSwrd_O = "StepSwrd O" + StepCross_S = "StpCross S" + Sword_E = "Sword E" + Team1_star = "Team1 *" + Team2_star = "Team2 *" + Thndrblt_star = "Thndrblt *" + Tornado_L = "Tornado L" + Fountain_star = "Fountain *" + VarSword_B = "VarSword B" + VarSword_F = "VarSword F" + WideSwrd_C = "WideSwrd C" + WideSwrd_E = "WideSwrd E" + WideSwrd_L = "WideSwrd L" + Yo_Yo1_D = "Yo-Yo1 D" + ZeusHammer_Z = "ZeusHammer Z" + BassGS_X = "BassGS X" + DeltaRay_Z = "DeltaRay Z" + Punk_P = "Punk P" + DarkAura_A = "DarkAura A" + AlphaArm_Omega_V = "AlphaArmΩ V" + SonicWav_W = "SonicWav W" + Bubbler_C = "Bubbler C" + Shake1_S = "Shake1 S" + HoleMetr_H = "HoleMetr H" + Shadow_J = "Shadow J" + Roll_R = "Roll R" + RollV2_R = "Roll V2 R" + RollV3_R = "Roll V3 R" + GutsMan_G = "GutsMan G" + GutsManV2_G = "GutsMan V2 G" + GutsManV3_G = "GutsMan V3 G" + ProtoMan_B = "ProtoMan B" + ProtoManV2_B = "ProtoMan V2 B" + ProtoManV3_B = "ProtoMan V3 B" + FlashMan_F = "FlashMan F" + FlashManV2_F = "FlashMan V2 F" + FlashManV3_F = "FlashMan V3 F" + BeastMan_B = "BeastMan B" + BeastManV2_B = "BeastMan V2 B" + BeastManV3_B = "BeastMan V3 B" + BubblMan_B = "BubblMan B" + BubblManV2_B = "BubblMan V2 B" + BubblManV3_B = "BubblMan V3 B" + DesertMan_D = "DesertMan D" + DesertManV2_D = "DesertMan V2 D" + DesertManV3_D = "DesertMan V3 D" + PlantMan_P = "PlantMan P" + PlantManV2_P = "PlantMan V2 P" + PlantManV3_P = "PlantMan V3 P" + FlamMan_F = "FlamMan F" + FlamManV2_F = "FlamMan V2 F" + FlamManV3_F = "FlamMan V3 F" + DrillMan_D = "DrillMan D" + DrillManV2_D = "DrillMan V2 D" + DrillManV3_D = "DrillMan V3 D" + MetalMan_M = "MetalMan M" + MetalManV2_M = "MetalMan V2 M" + MetalManV3_M = "MetalMan V3 M" + KingMan_K = "KingMan K" + KingManV2_K = "KingMan V2 K" + KingManV3_K = "KingMan V3 K" + BowlMan_B = "BowlMan B" + BowlManV2_B = "BowlMan V2 B" + BowlManV3_B = "BowlMan V3 B" + Bass_X = "Bass+ X" + + ## Navi Cust Programs + Airshoes = "Airshoes" + Attack_plus_White = "Attack+1 (White)" + Attack_plus_Pink = "Attack+1 (Pink)" + BrkChrg = "BrkChrg" + Charge_plus_Pink = "Charge+1 (Pink)" + Charge_plus_White = "Charge+1 (White)" + Collect = "Collect" + GigFldr1 = "GigFldr1" + HP_100_Pink = "HP+100 (Pink)" + HP_100_Yellow = "HP+100 (Yellow)" + HP_200_Yellow = "HP+200 (Yellow)" + HP_500_Yellow = "HP+500 (Yellow)" + HubBatc = "HubBatc" + Jungle = "Jungle" + OilBody = "OilBody" + QuickGge = "QuickGge" + SetSand = "SetSand" + SneakRun = "SneakRun" + Speed_plus_Yellow = "Speed+1 (Yellow)" + WpnLV_plus_Yellow = "WpnLV+1 (Yellow)" + WpnLV_plus_Pink = "WpnLV+1 (Pink)" + WpnLV_plus_White = "WpnLV+1 (White)" + Press = "Press" + UnderSht = "UnderSht" + + ## Currency + zenny_200z = "200z" + zenny_500z = "500z" + zenny_600z = "600z" + zenny_800z = "800z" + zenny_900z = "900z" + zenny_1000z = "1000z" + zenny_1200z = "1200z" + zenny_1400z = "1400z" + zenny_1600z = "1600z" + zenny_1800z = "1800z" + zenny_2000z = "2000z" + zenny_3000z = "3000z" + zenny_9000z = "9000z" + zenny_10000z = "10000z" + zenny_30000z = "30000z" + zenny_50000z = "50000z" + bugfrag_30 = "30 BugFrags" + bugfrag_10 = "10 BugFrags" + bugfrag_01 = "1 BugFrag" + + ## SubChips + MiniEnrg = "MiniEnrg" + FullEnrg = "FullEnrg" + Unlocker = "Unlocker" + Untrap = "Untrap" + LockEnmy = "LockEnmy" + + ## KeyItems + Progressive_Undernet_Rank = "Progressive Undernet Rank" + CACDCPas = "CACDCPas" + CSciPas = "CSciPass" + CYokaPas = "CYokaPas" + CBeacPas = "CBeacPas" + WWW_ID = "WWW ID" + OrderSys = "OrderSys" + Mr_Famous_Wristband = "Mr Famous' Wristband" + ModTools = "ModTools" + ExpMem = "ExpMem" + SpinDark = "SpinDark" + SpinPink = "SpinPink" + SpinPurple = "SpinPurple" + SpinOrange = "SpinOrange" + SpinBlue = "SpinBlue" + SpinGrn = "SpinGrn" + SpinRed = "SpinRed" + SpinWht = "SpinWht" + SpinYllw = "SpinYllw" + Parasol = "Parasol" + SubPET = "SubPET" + Needle = "Needle" + PETCase = "PETCase" + Hammer = "Hammer" + + ## Upgrades + HPMemory = "HPMemory" + RegUP1 = "RegUP1" + RegUP2 = "RegUP2" + RegUP3 = "RegUP3" + SubMem = "SubMem" + + Victory = "Victory" \ No newline at end of file diff --git a/worlds/mmbn3/Names/LocationName.py b/worlds/mmbn3/Names/LocationName.py new file mode 100644 index 000000000000..36060b12ec39 --- /dev/null +++ b/worlds/mmbn3/Names/LocationName.py @@ -0,0 +1,313 @@ +from enum import Enum + + +class LocationName(): + ## Blue Mystery Datas + # ACDC Area + ACDC_1_Southwest_BMD = "ACDC 1 Southwest BMD" + ACDC_1_Northeast_BMD = "ACDC 1 Northeast BMD" + ACDC_2_Center_BMD = "ACDC 2 Center BMD" + ACDC_2_North_BMD = "ACDC 2 North BMD" + ACDC_3_Southwest_BMD = "ACDC 3 Southwest BMD" + ACDC_3_Northeast_BMD = "ACDC 3 Northeast BMD" + + # SciLab Area + SciLab_1_WWW_BMD = "SciLab 1 WWW BMD" + SciLab_1_East_BMD = "SciLab 1 East BMD" + SciLab_2_West_BMD = "SciLab 2 West BMD" + SciLab_2_South_BMD = "SciLab 2 South BMD" + + # Yoka Area + Yoka_1_North_BMD = "Yoka 1 North BMD" + Yoka_1_WWW_BMD = "Yoka 1 WWW BMD" + Yoka_2_Upper_BMD = "Yoka 2 Upper BMD" + Yoka_2_Lower_BMD = "Yoka 2 Lower BMD" + + # Beach Area + Beach_1_BMD = "Beach 1 BMD" + Beach_2_West_BMD = "Beach 2 West BMD" + Beach_2_East_BMD = "Beach 2 East BMD" + + # Undernet Area + Undernet_1_South_BMD = "Undernet 1 South BMD" + Undernet_1_WWW_BMD = "Undernet 1 WWW BMD" + Undernet_2_Upper_BMD = "Undernet 2 Upper BMD" + Undernet_2_Lower_BMD = "Undernet 2 Lower BMD" + Undernet_3_South_BMD = "Undernet 3 South BMD" + Undernet_3_Central_BMD = "Undernet 3 Central BMD" + Undernet_4_Bottom_West_BMD = "Undernet 4 Bottom West BMD" + Undernet_4_Top_Pillar_BMD = "Undernet 4 Top Pillar BMD" + Undernet_4_Top_North_BMD = "Undernet 4 Top North BMD" + Undernet_5_Upper_BMD = "Undernet 5 Upper BMD" + Undernet_5_Lower_BMD = "Undernet 5 Lower BMD" + Undernet_6_East_BMD = "Undernet 6 East BMD" + Undernet_6_Central_BMD = "Undernet 6 Central BMD" + Undernet_6_TV_BMD = "Undernet 6 TV BMD" + Undernet_7_West_BMD = "Undernet 7 West BMD" + Undernet_7_Northwest_BMD = "Undernet 7 Northwest BMD" + Undernet_7_Northeast_BMD = "Undernet 7 Northeast BMD" + + # Secret Area + Secret_1_South_BMD = "Secret 1 South BMD" + Secret_1_Northeast_BMD = "Secret 1 Northeast BMD" + Secret_1_Northwest_BMD = "Secret 1 Northwest BMD" + Secret_2_Upper_BMD = "Secret 2 Upper BMD" + Secret_2_Lower_BMD = "Secret 2 Lower BMD" + Secret_2_Island_BMD = "Secret 2 Island BMD" + Secret_3_South_BMD = "Secret 3 South BMD" + Secret_3_Island_BMD = "Secret 3 Island BMD" + Secret_3_BugFrag_BMD = "Secret 3 BugFrag BMD" + + # School Area + School_1_Entrance_BMD = "School 1 Entrance BMD" + School_1_North_Central_BMD = "School 1 North Central BMD" + School_1_Far_West_BMD_2 = "School 1 Far West BMD 2" + School_2_Entrance_BMD = "School 2 Entrance BMD" + School_2_South_BMD = "School 2 South BMD" + School_2_Mainframe_BMD = "School 2 Mainframe BMD" + + # Zoo Area + Zoo_1_East_BMD = "Zoo 1 East BMD" + Zoo_1_Central_BMD = "Zoo 1 Central BMD" + Zoo_1_North_BMD = "Zoo 1 North BMD" + Zoo_2_East_BMD = "Zoo 2 East BMD" + Zoo_2_Central_BMD = "Zoo 2 Central BMD" + Zoo_2_West_BMD = "Zoo 2 West BMD" + Zoo_3_North_BMD = "Zoo 3 North BMD" + Zoo_3_Central_BMD = "Zoo 3 Central BMD" + Zoo_3_Path_BMD = "Zoo 3 Path BMD" + Zoo_3_Northwest_BMD = "Zoo 3 Northwest BMD" + Zoo_4_West_BMD = "Zoo 4 West BMD" + Zoo_4_Northwest_BMD = "Zoo 4 Northwest BMD" + Zoo_4_Southeast_BMD = "Zoo 4 Southeast BMD" + + # Hades Area + Hades_South_BMD = "Hades South BMD" + + # Hospital Area + Hospital_1_Center_BMD = "Hospital 1 Center BMD" + Hospital_1_West_BMD = "Hospital 1 West BMD" + Hospital_1_North_BMD = "Hospital 1 North BMD" + Hospital_2_Southwest_BMD = "Hospital 2 Southwest BMD" + Hospital_2_Central_BMD = "Hospital 2 Central BMD" + Hospital_2_Island_BMD = "Hospital 2 Island BMD" + Hospital_3_Central_BMD = "Hospital 3 Central BMD" + Hospital_3_West_BMD = "Hospital 3 West BMD" + Hospital_3_Northwest_BMD = "Hospital 3 Northwest BMD" + Hospital_4_Central_BMD = "Hospital 4 Central BMD" + Hospital_4_Southeast_BMD = "Hospital 4 Southeast BMD" + Hospital_4_North_BMD = "Hospital 4 North BMD" + Hospital_5_Southwest_BMD = "Hospital 5 Southwest BMD" + Hospital_5_Northeast_BMD = "Hospital 5 Northeast BMD" + Hospital_5_Island_BMD = "Hospital 5 Island BMD" + + # WWW Area + WWW_1_Central_BMD = "WWW 1 Central BMD" + WWW_1_West_BMD = "WWW 1 West BMD" + WWW_1_East_BMD = "WWW 1 East BMD" + WWW_2_East_BMD = "WWW 2 East BMD" + WWW_2_Northwest_BMD = "WWW 2 Northwest BMD" + WWW_3_East_BMD = "WWW 3 East BMD" + WWW_3_North_BMD = "WWW 3 North BMD" + WWW_4_Northwest_BMD = "WWW 4 Northwest BMD" + WWW_4_Central_BMD = "WWW 4 Central BMD" + + # Misc Net Area + ACDC_Dog_House_BMD = "ACDC Dog House BMD" + ACDC_Lans_Security_Panel_BMD = "ACDC Lan's Security Panel BMD" + ACDC_Yais_Phone_BMD = "ACDC Yai's Phone BMD" + ACDC_NumberMan_Display_BMD = "ACDC NumberMan Display BMD" + ACDC_Tank_BMD_1 = "ACDC Tank BMD 1" + ACDC_Tank_BMD_2 = "ACDC Tank BMD 2" + ACDC_School_Server_BMD_1 = "ACDC School Server BMD 1" + ACDC_School_Server_BMD_2 = "ACDC School Server BMD 2" + ACDC_School_Blackboard_BMD = "ACDC School Blackboard BMD" + SciLab_Vending_Machine_BMD = "SciLab Vending Machine BMD" + SciLab_Virus_Lab_Door_BMD_1 = "SciLab Virus Lab Door BMD 1" + SciLab_Virus_Lab_Door_BMD_2 = "SciLab Virus Lab Door BMD 2" + SciLab_Dads_Computer_BMD = "SciLab Dad's Computer BMD" + Yoka_Armor_BMD = "Yoka Armor BMD" + Yoka_TV_BMD = "Yoka TV BMD" + Yoka_Hot_Spring_BMD = "Yoka Hot Spring BMD" + Yoka_Ticket_Machine_BMD = "Yoka Ticket Machine BMD" + Yoka_Giraffe_BMD = "Yoka Giraffe BMD" + Yoka_Panda_BMD = "Yoka Panda BMD" + Beach_Hospital_Bed_BMD = "Beach Hospital Bed BMD" + Beach_TV_BMD = "Beach TV BMD" + Beach_Vending_Machine_BMD = "Beach Vending Machine BMD" + Beach_News_Van_BMD = "Beach News Van BMD" + Beach_Battle_Console_BMD = "Beach Battle Console BMD" + Beach_Security_System_BMD = "Beach Security System BMD" + Beach_Broadcast_Computer_BMD = "Beach Broadcast Computer BMD" + Hades_Gargoyle_BMD = "Hades Gargoyle BMD" + WWW_Wall_BMD = "WWW Wall BMD" + Mayls_HP_BMD = "Mayl's HP BMD" + Yais_HP_BMD_1 = "Yai's HP BMD 1" + Yais_HP_BMD_2 = "Yai's HP BMD 2" + Dexs_HP_BMD_1 = "Dex's HP BMD 1" + Dexs_HP_BMD_2 = "Dex's HP BMD 2" + Tamakos_HP_BMD = "Tamako's HP BMD" + + # Story Item BMDs + Undernet_7_Upper_BMD = "Undernet 7 Upper BMD" + School_1_KeyDataA_BMD = "School 1 KeyDataA BMD" + School_1_KeyDataB_BMD = "School 1 KeyDataB BMD" + School_1_KeyDataC_BMD = "School 1 KeyDataC BMD" + School_2_CodeC_BMD = "School 2 CodeC BMD" + School_2_CodeA_BMD = "School 2 CodeA BMD" + School_2_CodeB_BMD = "School 2 CodeB BMD" + Hades_HadesKey_BMD = "Hades HadesKey BMD" + WWW_1_South_BMD = "WWW 1 South BMD" + WWW_2_West_BMD = "WWW 2 West BMD" + WWW_3_South_BMD = "WWW 3 South BMD" + WWW_4_East_BMD = "WWW 4 East BMD" + + ## Purple Mystery Data + ACDC_1_PMD = "ACDC 1 PMD" + Yoka_1_PMD = "Yoka 1 PMD" + Beach_1_PMD = "Beach 1 PMD" + Undernet_7_PMD = "Undernet 7 PMD" + Mayls_HP_PMD = "Mayl's HP PMD" + SciLab_Dads_Computer_PMD = "SciLab Dad's Computer PMD" + Zoo_Panda_PMD = "Zoo Panda PMD" + Beach_DNN_Security_Panel_PMD = "Beach DNN Security Panel PMD" + Beach_DNN_Main_Console_PMD = "Beach DNN Main Console PMD" + Tamakos_HP_PMD = "Tamako's HP PMD" + + ## Overworld Items + Yoka_Mr_Quiz = "Yoka Mr Quiz" + Yoka_Quiz_Master = "Yoka Quiz Master" + Hospital_Quiz_Queen = "Hospital Quiz Queen" + Hades_Quiz_King = "Hades Quiz King" + ACDC_SonicWav_W_Trade = "ACDC SonicWav W Trade" + ACDC_Bubbler_C_Trade = "ACDC Bubbler C Trade" + ACDC_Recov120_S_Trade = "ACDC Recov120 S Trade" + SciLab_Shake1_S_Trade = "SciLab Shake1 S Trade" + Yoka_FireSwrd_P_Trade = "Yoka FireSwrd P Trade" + Hospital_DynaWav_V_Trade = "Hospital DynaWav V Trade" + Beach_DNN_WideSwrd_C_Trade = "Beach DNN WideSwrd C Trade" + Beach_DNN_HoleMetr_H_Trade = "Beach DNN HoleMetr H Trade" + Beach_DNN_Shadow_J_Trade = "Beach DNN Shadow J Trade" + Hades_GrabBack_K_Trade = "Hades GrabBack K Trade" + Comedian = "Comedian" + Villain = "Villain" + Mod_Tools_Guy = "Mod Tools Guy" + ACDC_School_Desk = "ACDC School Desk" + ACDC_Class_5B_Bookshelf = "ACDC Class 5B Bookshelf" + SciLab_Garbage_Can = "SciLab Garbage Can" + Yoka_Inn_Jars = "Yoka Inn Jars" + Yoka_Zoo_Garbage = "Yoka Zoo Garbage" + Beach_Department_Store = "Beach Department Store" + Beach_Hospital_Plaque = "Beach Hospital Plaque" + Beach_Hospital_Pink_Door = "Beach Hospital Pink Door" + Beach_Hospital_Tree = "Beach Hospital Tree" + Beach_Hospital_Hidden_Conversation = "Beach Hospital Hidden Conversation" + Beach_Hospital_Girl = "Beach Hospital Girl" + Beach_DNN_Kiosk = "Beach DNN Kiosk" + Beach_DNN_Boxes = "Beach DNN Boxes" + Beach_DNN_Poster = "Beach DNN Poster" + Hades_Boat_Dock = "Hades Boat Dock" + WWW_Control_Room_1_Screen = "WWW Control Room 1 Screen" + WWW_Wilys_Desk = "WWW Wily's Desk" + Undernet_4_Pillar_Prog = "Undernet 4 Pillar Prog" + + ## Numberman Codes + Numberman_Code_01 = "Numberman Code 01" + Numberman_Code_02 = "Numberman Code 02" + Numberman_Code_03 = "Numberman Code 03" + Numberman_Code_04 = "Numberman Code 04" + Numberman_Code_05 = "Numberman Code 05" + Numberman_Code_06 = "Numberman Code 06" + Numberman_Code_07 = "Numberman Code 07" + Numberman_Code_08 = "Numberman Code 08" + Numberman_Code_09 = "Numberman Code 09" + Numberman_Code_10 = "Numberman Code 10" + Numberman_Code_11 = "Numberman Code 11" + Numberman_Code_12 = "Numberman Code 12" + Numberman_Code_13 = "Numberman Code 13" + Numberman_Code_14 = "Numberman Code 14" + Numberman_Code_15 = "Numberman Code 15" + Numberman_Code_16 = "Numberman Code 16" + Numberman_Code_17 = "Numberman Code 17" + Numberman_Code_18 = "Numberman Code 18" + Numberman_Code_19 = "Numberman Code 19" + Numberman_Code_20 = "Numberman Code 20" + Numberman_Code_21 = "Numberman Code 21" + Numberman_Code_22 = "Numberman Code 22" + Numberman_Code_23 = "Numberman Code 23" + Numberman_Code_24 = "Numberman Code 24" + Numberman_Code_25 = "Numberman Code 25" + Numberman_Code_26 = "Numberman Code 26" + Numberman_Code_27 = "Numberman Code 27" + Numberman_Code_28 = "Numberman Code 28" + Numberman_Code_29 = "Numberman Code 29" + Numberman_Code_30 = "Numberman Code 30" + Numberman_Code_31 = "Numberman Code 31" + + ## Jobs + Please_deliver_this = "Job: Please deliver this" + My_Navi_is_sick = "Job: My Navi is sick" + Help_me_with_my_son = "Job: Help me with my son!" + Transmission_error = "Job: Transmission error" + Chip_Prices = "Job: Chip Prices" + Im_broke = "Job: I'm broke?!" + Rare_chips_for_cheap = "Job: Rare chips for cheap!" + Be_my_boyfriend = "Job: Be my boyfriend" + Will_you_deliver = "Job: Will you deliver?" + Look_for_friends = "Job: Look for friends (Tora)" + Stuntmen_wanted = "Job: Stuntmen wanted! (Tora)" + Riot_stopped = "Job: Riot stopped (Tora)" + Gathering_Data = "Job: Gathering Data (Tora)" + Somebody_please_help = "Job: Somebody, please help!" + Looking_for_condor = "Job: Looking for condor" + Help_with_rehab = "Job: Help with rehab" + Old_Master = "Job: Old Master" + Catching_gang_members = "Job: Catching gang members" + Please_adopt_a_virus = "Job: Please adopt a virus!" + Legendary_Tomes = "Job: Legendary Tomes" + Legendary_Tomes_Treasure = "Job: Legendary Tomes - Treasure" + Hide_and_seek_First_Child = "Job: Hide and seek! First Child" + Hide_and_seek_Second_Child = "Job: Hide and seek! Second Child" + Hide_and_seek_Third_Child = "Job: Hide and seek! Third Child" + Hide_and_seek_Fourth_Child = "Job: Hide and seek! Fourth Child" + Hide_and_seek_Completion = "Job: Hide and seek! Completion" + Finding_the_blue_Navi = "Job: Finding the blue Navi" + Give_your_support = "Job: Give your support" + Stamp_collecting = "Job: Stamp collecting" + Help_with_a_will = "Job: Help with a will" + + Alpha_Defeated = "Alpha Defeated" + + ## Chocolates + Chocolate_Shop_01 = "Chocolate Shop 01" + Chocolate_Shop_02 = "Chocolate Shop 02" + Chocolate_Shop_03 = "Chocolate Shop 03" + Chocolate_Shop_04 = "Chocolate Shop 04" + Chocolate_Shop_05 = "Chocolate Shop 05" + Chocolate_Shop_06 = "Chocolate Shop 06" + Chocolate_Shop_07 = "Chocolate Shop 07" + Chocolate_Shop_08 = "Chocolate Shop 08" + Chocolate_Shop_09 = "Chocolate Shop 09" + Chocolate_Shop_10 = "Chocolate Shop 10" + Chocolate_Shop_11 = "Chocolate Shop 11" + Chocolate_Shop_12 = "Chocolate Shop 12" + Chocolate_Shop_13 = "Chocolate Shop 13" + Chocolate_Shop_14 = "Chocolate Shop 14" + Chocolate_Shop_15 = "Chocolate Shop 15" + Chocolate_Shop_16 = "Chocolate Shop 16" + Chocolate_Shop_17 = "Chocolate Shop 17" + Chocolate_Shop_18 = "Chocolate Shop 18" + Chocolate_Shop_19 = "Chocolate Shop 19" + Chocolate_Shop_20 = "Chocolate Shop 20" + Chocolate_Shop_21 = "Chocolate Shop 21" + Chocolate_Shop_22 = "Chocolate Shop 22" + Chocolate_Shop_23 = "Chocolate Shop 23" + Chocolate_Shop_24 = "Chocolate Shop 24" + Chocolate_Shop_25 = "Chocolate Shop 25" + Chocolate_Shop_26 = "Chocolate Shop 26" + Chocolate_Shop_27 = "Chocolate Shop 27" + Chocolate_Shop_28 = "Chocolate Shop 28" + Chocolate_Shop_29 = "Chocolate Shop 29" + Chocolate_Shop_30 = "Chocolate Shop 30" + Chocolate_Shop_31 = "Chocolate Shop 31" + Chocolate_Shop_32 = "Chocolate Shop 32" \ No newline at end of file diff --git a/worlds/mmbn3/Options.py b/worlds/mmbn3/Options.py new file mode 100644 index 000000000000..96a01290a5c7 --- /dev/null +++ b/worlds/mmbn3/Options.py @@ -0,0 +1,48 @@ +from Options import Choice, Range, DefaultOnToggle + + +class ExtraRanks(Range): + """ + How many extra Undernet Ranks to add to the pool in place of filler items. + The more ranks there are, the faster the game will go. + Depending on your other options, you might not have enough filler items to replace. + If generation errors occur, consider reducing this value. + """ + display_name = "Extra Undernet Ranks" + range_start = 0 + range_end = 16 + default = 0 + + +class IncludeJobs(DefaultOnToggle): + """ + Whether Jobs can be included in logic. + """ + display_name = "Include Jobs" + +# Possible logic options: +# - Include Number Trader +# - Include Secret Area +# - Overworld Item Restrictions +# - Cybermetro Locked Shortcuts + + +class TradeQuestHinting(Choice): + """ + Whether NPCs offering Chip Trades should show what item they provide. + None - NPCs will not provide any information on what item they will give + Partial - NPCs will state if an item is progression or not, but not the specific item + Full - NPCs will state what item they will give, providing an Archipelago Hint when doing so + """ + display_name = "Trade Quest Hinting" + option_none = 0 + option_partial = 1 + option_full = 2 + default = 2 + + +MMBN3Options = { + "extra_ranks": ExtraRanks, + "include_jobs": IncludeJobs, + "trade_quest_hinting": TradeQuestHinting, +} diff --git a/worlds/mmbn3/Regions.py b/worlds/mmbn3/Regions.py new file mode 100644 index 000000000000..1dc58600cbc4 --- /dev/null +++ b/worlds/mmbn3/Regions.py @@ -0,0 +1,354 @@ +import typing +from .Names.LocationName import LocationName + + +class RegionName: + Menu = "Menu" + ACDC_Overworld = "ACDC Overworld" + ACDC_Cyberworld = "ACDC Cyberworld" + SciLab_Overworld = "SciLab Overworld" + SciLab_Cyberworld = "SciLab Cyberworld" + Yoka_Overworld = "Yoka Overworld" + Yoka_Cyberworld = "Yoka Cyberworld" + Beach_Overworld = "Beach Overworld" + Beach_Cyberworld = "Beach Cyberworld" + Undernet = "Undernet" + Deep_Undernet = "Deep Undernet" + Secret_Area = "Secret Area" + WWW_Island = "WWW Island" + + +class RegionInfo: + name: str + connections: typing.List[str] + locations: typing.List[str] + + def __init__(self, name, connections, locations): + self.name = name + self.connections = connections + self.locations = locations + + +regions = [ + RegionInfo(RegionName.Menu, [RegionName.ACDC_Overworld], []), + RegionInfo(RegionName.ACDC_Overworld, + [RegionName.ACDC_Cyberworld, RegionName.SciLab_Overworld, RegionName.Yoka_Overworld, RegionName.Beach_Overworld], + [ + LocationName.ACDC_SonicWav_W_Trade, + LocationName.ACDC_Bubbler_C_Trade, + LocationName.ACDC_Recov120_S_Trade, + LocationName.ACDC_School_Desk, + LocationName.ACDC_Class_5B_Bookshelf, + LocationName.School_1_Entrance_BMD, + LocationName.School_1_North_Central_BMD, + LocationName.School_1_Far_West_BMD_2, + LocationName.School_1_KeyDataA_BMD, + LocationName.School_1_KeyDataB_BMD, + LocationName.School_1_KeyDataC_BMD, + LocationName.School_2_South_BMD, + LocationName.School_2_Entrance_BMD, + LocationName.School_2_Mainframe_BMD, + LocationName.School_2_CodeA_BMD, + LocationName.School_2_CodeB_BMD, + LocationName.School_2_CodeC_BMD, + LocationName.ACDC_Dog_House_BMD, + LocationName.ACDC_Lans_Security_Panel_BMD, + LocationName.ACDC_Yais_Phone_BMD, + LocationName.ACDC_NumberMan_Display_BMD, + LocationName.ACDC_Tank_BMD_1, + LocationName.ACDC_Tank_BMD_2, + LocationName.ACDC_School_Server_BMD_1, + LocationName.ACDC_School_Server_BMD_2, + LocationName.ACDC_School_Blackboard_BMD, + LocationName.Numberman_Code_01, + LocationName.Numberman_Code_02, + LocationName.Numberman_Code_03, + LocationName.Numberman_Code_04, + LocationName.Numberman_Code_05, + LocationName.Numberman_Code_06, + LocationName.Numberman_Code_07, + LocationName.Numberman_Code_08, + LocationName.Numberman_Code_09, + LocationName.Numberman_Code_10, + LocationName.Numberman_Code_11, + LocationName.Numberman_Code_12, + LocationName.Numberman_Code_13, + LocationName.Numberman_Code_14, + LocationName.Numberman_Code_15, + LocationName.Numberman_Code_16, + LocationName.Numberman_Code_17, + LocationName.Numberman_Code_18, + LocationName.Numberman_Code_19, + LocationName.Numberman_Code_20, + LocationName.Numberman_Code_21, + LocationName.Numberman_Code_22, + LocationName.Numberman_Code_23, + LocationName.Numberman_Code_24, + LocationName.Numberman_Code_25, + LocationName.Numberman_Code_26, + LocationName.Numberman_Code_27, + LocationName.Numberman_Code_28, + LocationName.Numberman_Code_29, + LocationName.Numberman_Code_30, + LocationName.Numberman_Code_31, + LocationName.Mayls_HP_BMD, + LocationName.Yais_HP_BMD_1, + LocationName.Yais_HP_BMD_2, + LocationName.Dexs_HP_BMD_1, + LocationName.Dexs_HP_BMD_2, + LocationName.Mayls_HP_PMD + ]), + RegionInfo(RegionName.ACDC_Cyberworld, + [RegionName.SciLab_Cyberworld, RegionName.Yoka_Cyberworld, RegionName.Beach_Cyberworld], + [ + LocationName.ACDC_1_Southwest_BMD, + LocationName.ACDC_1_Northeast_BMD, + LocationName.ACDC_1_PMD, + LocationName.ACDC_2_Center_BMD, + LocationName.ACDC_2_North_BMD, + LocationName.ACDC_3_Southwest_BMD, + LocationName.ACDC_3_Northeast_BMD, + ]), + RegionInfo(RegionName.SciLab_Overworld, + [RegionName.SciLab_Cyberworld, RegionName.ACDC_Overworld, RegionName.Yoka_Overworld, RegionName.Beach_Overworld], + [ + LocationName.SciLab_Shake1_S_Trade, + LocationName.SciLab_Garbage_Can, + LocationName.SciLab_Vending_Machine_BMD, + LocationName.SciLab_Virus_Lab_Door_BMD_1, + LocationName.SciLab_Virus_Lab_Door_BMD_2, + LocationName.SciLab_Dads_Computer_BMD, + LocationName.SciLab_Dads_Computer_PMD, + LocationName.Please_deliver_this, + LocationName.My_Navi_is_sick, + LocationName.Help_me_with_my_son, + LocationName.Transmission_error, + LocationName.Chip_Prices, + LocationName.Im_broke, + LocationName.Rare_chips_for_cheap, + LocationName.Be_my_boyfriend, + LocationName.Will_you_deliver, + #LocationName.Look_for_friends, + #LocationName.Stuntmen_wanted, + #LocationName.Riot_stopped, + #LocationName.Gathering_Data, + LocationName.Somebody_please_help, + LocationName.Looking_for_condor, + LocationName.Help_with_rehab, + LocationName.Old_Master, + LocationName.Catching_gang_members, + LocationName.Please_adopt_a_virus, + LocationName.Legendary_Tomes, + LocationName.Legendary_Tomes_Treasure, + LocationName.Hide_and_seek_First_Child, + LocationName.Hide_and_seek_Second_Child, + LocationName.Hide_and_seek_Third_Child, + LocationName.Hide_and_seek_Fourth_Child, + LocationName.Hide_and_seek_Completion, + LocationName.Finding_the_blue_Navi, + LocationName.Give_your_support, + LocationName.Stamp_collecting, + LocationName.Help_with_a_will + ]), + RegionInfo(RegionName.SciLab_Cyberworld, + [RegionName.ACDC_Cyberworld, RegionName.Yoka_Cyberworld, RegionName.Beach_Cyberworld,RegionName.Deep_Undernet], + [ + LocationName.SciLab_1_East_BMD, + LocationName.SciLab_1_WWW_BMD, + LocationName.SciLab_2_South_BMD, + LocationName.SciLab_2_West_BMD + ]), + RegionInfo(RegionName.Yoka_Overworld, + [RegionName.Yoka_Cyberworld, RegionName.ACDC_Overworld, RegionName.SciLab_Overworld, RegionName.Beach_Overworld, RegionName.Secret_Area], + [ + LocationName.Yoka_Mr_Quiz, + LocationName.Yoka_Quiz_Master, + LocationName.Yoka_FireSwrd_P_Trade, + LocationName.Yoka_Inn_Jars, + LocationName.Yoka_Zoo_Garbage, + LocationName.Zoo_Panda_PMD, + LocationName.Zoo_1_East_BMD, + LocationName.Zoo_1_North_BMD, + LocationName.Zoo_1_Central_BMD, + LocationName.Zoo_2_East_BMD, + LocationName.Zoo_2_Central_BMD, + LocationName.Zoo_2_West_BMD, + LocationName.Zoo_3_North_BMD, + LocationName.Zoo_3_Central_BMD, + LocationName.Zoo_3_Path_BMD, + LocationName.Zoo_3_Northwest_BMD, + LocationName.Zoo_4_West_BMD, + LocationName.Zoo_4_Northwest_BMD, + LocationName.Zoo_4_Southeast_BMD, + LocationName.Yoka_TV_BMD, + LocationName.Yoka_Armor_BMD, + LocationName.Yoka_Hot_Spring_BMD, + LocationName.Yoka_Ticket_Machine_BMD, + LocationName.Yoka_Giraffe_BMD, + LocationName.Yoka_Panda_BMD, + LocationName.Tamakos_HP_BMD, + LocationName.Tamakos_HP_PMD, + LocationName.Comedian, + LocationName.Chocolate_Shop_01, + LocationName.Chocolate_Shop_02, + LocationName.Chocolate_Shop_03, + LocationName.Chocolate_Shop_04, + LocationName.Chocolate_Shop_05, + LocationName.Chocolate_Shop_06, + LocationName.Chocolate_Shop_07, + LocationName.Chocolate_Shop_08, + LocationName.Chocolate_Shop_09, + LocationName.Chocolate_Shop_10, + LocationName.Chocolate_Shop_11, + LocationName.Chocolate_Shop_12, + LocationName.Chocolate_Shop_13, + LocationName.Chocolate_Shop_14, + LocationName.Chocolate_Shop_15, + LocationName.Chocolate_Shop_16, + LocationName.Chocolate_Shop_17, + LocationName.Chocolate_Shop_18, + LocationName.Chocolate_Shop_19, + LocationName.Chocolate_Shop_20, + LocationName.Chocolate_Shop_21, + LocationName.Chocolate_Shop_22, + LocationName.Chocolate_Shop_23, + LocationName.Chocolate_Shop_24, + LocationName.Chocolate_Shop_25, + LocationName.Chocolate_Shop_26, + LocationName.Chocolate_Shop_27, + LocationName.Chocolate_Shop_28, + LocationName.Chocolate_Shop_29, + LocationName.Chocolate_Shop_30, + LocationName.Chocolate_Shop_31, + LocationName.Chocolate_Shop_32 + ]), + RegionInfo(RegionName.Yoka_Cyberworld, + [RegionName.ACDC_Cyberworld, RegionName.SciLab_Cyberworld, RegionName.Beach_Cyberworld], + [ + LocationName.Yoka_1_North_BMD, + LocationName.Yoka_1_WWW_BMD, + LocationName.Yoka_1_PMD, + LocationName.Yoka_2_Lower_BMD, + LocationName.Yoka_2_Upper_BMD, + ]), + RegionInfo(RegionName.Beach_Overworld, + [RegionName.ACDC_Overworld, RegionName.SciLab_Overworld, RegionName.Yoka_Overworld, RegionName.WWW_Island], + [ + LocationName.Hospital_Quiz_Queen, + LocationName.Hades_Quiz_King, + LocationName.Hospital_DynaWav_V_Trade, + LocationName.Beach_DNN_WideSwrd_C_Trade, + LocationName.Beach_DNN_HoleMetr_H_Trade, + LocationName.Beach_DNN_Shadow_J_Trade, + LocationName.Hades_GrabBack_K_Trade, + #LocationName.Mod_Tools_Guy, + LocationName.Beach_Department_Store, + LocationName.Beach_Hospital_Plaque, + LocationName.Beach_Hospital_Pink_Door, + LocationName.Beach_Hospital_Tree, + LocationName.Beach_Hospital_Hidden_Conversation, + LocationName.Beach_Hospital_Girl, + LocationName.Beach_DNN_Kiosk, + LocationName.Beach_DNN_Boxes, + LocationName.Beach_DNN_Poster, + LocationName.Hades_Boat_Dock, + LocationName.Hades_South_BMD, + LocationName.Hades_Gargoyle_BMD, + LocationName.Hospital_1_North_BMD, + LocationName.Hospital_1_West_BMD, + LocationName.Hospital_1_Center_BMD, + LocationName.Hospital_2_Island_BMD, + LocationName.Hospital_2_Central_BMD, + LocationName.Hospital_2_Southwest_BMD, + LocationName.Hospital_3_West_BMD, + LocationName.Hospital_3_Central_BMD, + LocationName.Hospital_3_Northwest_BMD, + LocationName.Hospital_4_North_BMD, + LocationName.Hospital_4_Central_BMD, + LocationName.Hospital_4_Southeast_BMD, + LocationName.Hospital_5_Island_BMD, + LocationName.Hospital_5_Northeast_BMD, + LocationName.Hospital_5_Southwest_BMD, + LocationName.Beach_Hospital_Bed_BMD, + LocationName.Beach_TV_BMD, + LocationName.Beach_Vending_Machine_BMD, + LocationName.Beach_News_Van_BMD, + LocationName.Beach_Battle_Console_BMD, + LocationName.Beach_Security_System_BMD, + LocationName.Beach_Broadcast_Computer_BMD, + LocationName.Beach_DNN_Security_Panel_PMD, + LocationName.Beach_DNN_Main_Console_PMD, + LocationName.Undernet_6_TV_BMD + ]), + RegionInfo(RegionName.Beach_Cyberworld, + [RegionName.ACDC_Cyberworld, RegionName.SciLab_Cyberworld, RegionName.Yoka_Cyberworld, RegionName.Undernet], + [ + LocationName.Beach_1_BMD, + LocationName.Beach_1_PMD, + LocationName.Beach_2_East_BMD, + LocationName.Beach_2_West_BMD, + LocationName.Villain + ]), + RegionInfo(RegionName.Undernet, + [], + [ + LocationName.Undernet_1_South_BMD, + LocationName.Undernet_1_WWW_BMD, + LocationName.Undernet_2_Lower_BMD, + LocationName.Undernet_2_Upper_BMD, + LocationName.Undernet_3_South_BMD, + LocationName.Undernet_3_Central_BMD, + LocationName.Undernet_4_Pillar_Prog, + LocationName.Undernet_4_Top_North_BMD, + LocationName.Undernet_4_Bottom_West_BMD, + LocationName.Undernet_4_Top_Pillar_BMD, + LocationName.Undernet_5_Upper_BMD + + ]), + RegionInfo(RegionName.Deep_Undernet, + [], + [ + LocationName.Undernet_5_Lower_BMD, + LocationName.Undernet_6_East_BMD, + LocationName.Undernet_6_Central_BMD, + LocationName.Undernet_7_PMD, + LocationName.Undernet_7_West_BMD, + LocationName.Undernet_7_Northeast_BMD, + LocationName.Undernet_7_Northwest_BMD, + LocationName.Undernet_7_Upper_BMD, + ]), + RegionInfo(RegionName.WWW_Island, + [], + [ + LocationName.WWW_Control_Room_1_Screen, + LocationName.WWW_Wilys_Desk, + LocationName.WWW_Wall_BMD, + LocationName.WWW_1_East_BMD, + LocationName.WWW_1_West_BMD, + LocationName.WWW_1_Central_BMD, + #LocationName.WWW_1_South_BMD, + LocationName.WWW_2_East_BMD, + LocationName.WWW_2_Northwest_BMD, + #LocationName.WWW_2_West_BMD, + LocationName.WWW_3_East_BMD, + LocationName.WWW_3_North_BMD, + #LocationName.WWW_3_South_BMD, + LocationName.WWW_4_Central_BMD, + LocationName.WWW_4_Northwest_BMD, + #LocationName.WWW_4_East_BMD + LocationName.Alpha_Defeated + ]), + RegionInfo(RegionName.Secret_Area, + [], + [ + LocationName.Secret_1_South_BMD, + LocationName.Secret_1_Northeast_BMD, + LocationName.Secret_1_Northwest_BMD, + LocationName.Secret_2_Island_BMD, + LocationName.Secret_2_Lower_BMD, + LocationName.Secret_2_Upper_BMD, + LocationName.Secret_3_Island_BMD, + LocationName.Secret_3_South_BMD, + LocationName.Secret_3_BugFrag_BMD + ]) +] diff --git a/worlds/mmbn3/Rom.py b/worlds/mmbn3/Rom.py new file mode 100644 index 000000000000..e1b7cedd1a9f --- /dev/null +++ b/worlds/mmbn3/Rom.py @@ -0,0 +1,347 @@ +from BaseClasses import ItemClassification +from Patch import APDeltaPatch + +import Utils +import os +import hashlib +import bsdiff4 +from .lz10 import gba_decompress, gba_compress + +from .BN3RomUtils import ArchiveToReferences, read_u16_le, read_u32_le, int16_to_byte_list_le, int32_to_byte_list_le,\ + generate_progressive_undernet, ArchiveToSizeComp, ArchiveToSizeUncomp, generate_item_message, \ + generate_external_item_message, generate_text_bytes + +from .Items import ItemType + +CHECKSUM_BLUE = "6fe31df0144759b34ad666badaacc442" + + +def list_contains_subsequence(lst, sublist) -> bool: + sub_index = 0 + for index, item in enumerate(lst): + if item == sublist[sub_index]: + sub_index += 1 + if sub_index >= len(sublist): + return True + else: + sub_index = 0 + return False + + +class ArchiveScript: + def __init__(self, index, message_bytes): + self.index = index + self.messageBoxes = [] + + self.set_bytes(message_bytes) + + def get_bytes(self): + data = [] + for message in self.messageBoxes: + data.extend(message) + return data + + def set_bytes(self, message_bytes): + self.messageBoxes = [] + + message_box = [] + + command_index = 0 + for byte in message_bytes: + if command_index <= 0 and (byte == 0xE9 or byte == 0xE7): + if byte == 0xE9: # More textboxes to come, don't end it yet + message_box.append(byte) + self.messageBoxes.append(message_box) + else: # It's the end of the script, add another message to end it after this one + self.messageBoxes.append(message_box) + self.messageBoxes.append([0xE7]) + message_box = [] + + else: + if command_index <- 0: + # We can hit a command that might contain an E9 or an E7. If we do, skip checking the next few bytes + if byte == 0xF6: # CheckItem + command_index = 7 + if byte == 0xF3: # CheckFlag + command_index = 7 + if byte == 0xF2: # FlagSet + command_index = 4 + command_index -= 1 + message_box.append(byte) + # If there's still bytes left over, add them even if we didn't hit an end + if len(message_box) > 0: + self.messageBoxes.append(message_box) + + def __str__(self): + s = str(self.index)+' - \n' + for messageBox in self.messageBoxes: + s += ' '+str(["{:02x}".format(x) for x in messageBox])+'\n' + + +class TextArchive: + def __init__(self, data, offset, size, compressed=True): + self.startOffset = offset + self.compressed = compressed + self.scripts = {} + self.scriptCount = 0xFF + self.references = ArchiveToReferences[offset] + self.unused_indices = [] # A list of places it's okay to inject new scripts + self.progressive_undernet_indices = [] # If this archive has progressive undernet, here they are in order + + self.text_changed = False + + if compressed: + self.compressedSize = size + self.compressedData = data + self.uncompressedData = gba_decompress(self.compressedData) + self.uncompressedSize = len(self.uncompressedData) + else: + self.uncompressedSize = size + self.uncompressedData = data + self.compressedData = gba_compress(self.uncompressedData) + self.compressedSize = len(self.compressedData) + self.scriptCount = (read_u16_le(self.uncompressedData, 0)) >> 1 + + for i in range(0, self.scriptCount): + start_offset = read_u16_le(self.uncompressedData, i * 2) + next_offset = read_u16_le(self.uncompressedData, (i + 1) * 2) + + if start_offset != next_offset: + message_bytes = list(self.uncompressedData[start_offset:next_offset]) + message = ArchiveScript(i, message_bytes) + self.scripts[i] = message + else: + self.unused_indices.append(i) + + def generate_data(self, compressed=True): + header = [] + scripts = [] + byte_offset = self.scriptCount * 2 + for i in range(0, self.scriptCount): + header.extend(int16_to_byte_list_le(byte_offset)) + if i in self.scripts: + script = self.scripts[i] + scriptbytes = script.get_bytes() + scripts.extend(scriptbytes) + byte_offset += len(scriptbytes) + + data = [] + data.extend(header) + data.extend(scripts) + byte_data = bytes(data) + if compressed: + byte_data = gba_compress(byte_data) + + return bytearray(byte_data) + + def inject_item_message(self, script_index, message_indices, new_bytes): + # First step, if the old message had any flag sets or flag clears, we need to keep them. + # Mystery data has a flag set to actually remove the mystery data, and jobs often have a completion flag + for message_index in message_indices: + # print(hex(self.startOffset) + ": " + str(script_index) + " " + str(message_indices)) + oldbytes = self.scripts[script_index].messageBoxes[message_index] + for i in range(len(oldbytes)-3): + # F2 00 is the code for "flagSet", with the two bytes after it being the flag to set. + # F2 04 is the code for "flagClear", which also needs to come along for the ride + # Add those to the message box after the other text. + if oldbytes[i] == 0xF2 and (oldbytes[i+1] == 0x00 or oldbytes[i+1] == 0x04): + flag = oldbytes[i:i+4] + new_bytes.extend(flag) + + first_message_index = message_indices[0] + # Then, overwrite the existing script with the new one + self.scripts[script_index].messageBoxes[first_message_index] = new_bytes + for index in message_indices[1:]: + self.scripts[script_index].messageBoxes[index] = [] + + def inject_into_rom(self, modified_rom_data): + working_data = self.generate_data(self.compressed) + + # It needs to start on a byte divisible by 4. If the rom data is not, add an FF + while len(modified_rom_data) % 4 != 0: + modified_rom_data.append(0xFF) + new_start_offset = 0x08000000 + len(modified_rom_data) + offset_byte = int32_to_byte_list_le(new_start_offset) + modified_rom_data.extend(working_data) + for offset in self.references: + modified_rom_data[offset:offset+4] = offset_byte + return modified_rom_data + + def add_progression_scripts(self): + if len(self.unused_indices) < 9: + # As far as I know, this should literally not be possible. + # Every script I've looked at has dozens of unused indices, so finding 9 (8 plus one "ending" script) + # should be no problem. We re-use these so we don't have to worry about an area getting tons of these + raise AssertionError("Error in generation -- not enough room for progressive undernet in archive "+self.startOffset) + for i in range(9): # There are 8 progressive undernet ranks + new_script_index = self.unused_indices[i] + new_script = ArchiveScript(new_script_index, generate_progressive_undernet(i, self.unused_indices[i+1])) + self.scripts[new_script_index] = new_script + self.progressive_undernet_indices.append(new_script_index) + self.unused_indices = self.unused_indices[9:] # Remove the first eight elements + + def inject_item_text(self, item_text, next_message=""): + item_text_bytes = generate_text_bytes(item_text) + next_message_bytes = generate_text_bytes(next_message) + for script_index in self.scripts: + script = self.scripts[script_index] + # Loop through the bytes + for message_index in range(0, len(script.messageBoxes)): + oldbytes = self.scripts[script_index].messageBoxes[message_index] + for i in range(0, len(oldbytes)-1): + if oldbytes[i] == 0x68 and oldbytes[i+1] == 0x68: + oldbytes[i:i+2] = item_text_bytes + self.text_changed = True + + # If there's another text box to display, add it to the message bytes before setting them back + if len(next_message) > 0: + oldbytes.extend(next_message_bytes) + # TODO append end message nextline etc. + # I think this is "wait for button press" then "clearmessage" + oldbytes.extend([0xEB, 0xE9]) + self.scripts[script_index].messageBoxes[message_index] = oldbytes + + +class LocalRom: + def __init__(self, file, name=None): + self.name = name + self.changed_archives = {} + + self.rom_data = bytearray(get_patched_rom_bytes(file)) + + def get_data_chunk(self, start_offset, size): + if start_offset+size > len(self.rom_data): + print("Attempting to get data chunk beyond the size of the ROM: "+hex(start_offset)+", ROM size ends at: "+hex(len(self.rom_data))) + return self.rom_data[start_offset:start_offset + size] + + def replace_item(self, location, item): + offset = location.text_archive_address + # If the archive is already loaded, use that + if offset in self.changed_archives: + archive = self.changed_archives[offset] + else: + is_compressed = offset in ArchiveToSizeComp.keys() + size = ArchiveToSizeComp[offset] if is_compressed\ + else ArchiveToSizeUncomp[offset] + data = self.get_data_chunk(offset, size) + # Check if the archive we want to load has been moved by the patch. This is indicated by a 0xFF 0xFF + # as the first two bytes of the chunk + + if data[0] == 0xFF and data[1] == 0xFF: + new_size_bytes = data[2:4] + new_address_le = data[4:8] + # Last byte should be zero since we're dealing with purely ROM address space + new_address_le[3] = 0x0 + size = read_u16_le(new_size_bytes, 0) + data = self.get_data_chunk(read_u32_le(new_address_le, 0), size) + + + archive = TextArchive(data, offset, size, is_compressed) + self.changed_archives[offset] = archive + + if item.type == ItemType.Undernet: + if len(archive.progressive_undernet_indices) == 0: + archive.add_progression_scripts() # Generate the new scripts + # Replace the item text box as normal. We just also add a new jump at the end of the script + item_bytes = generate_item_message(item) + changed_script = archive.scripts[location.text_script_index] + # There isn't a "Jump unconditional", so we fake one. Check flag 0 and jump + # to the start of our progression regardless of outcome + jump_to_first_undernet_bytes = [0xF3, 0x00, + 0x00, 0x00, + archive.progressive_undernet_indices[0], + archive.progressive_undernet_indices[0]] + # Insert the new message second-to-last (the last index should be an end all by itself) + changed_script.messageBoxes.insert(-1, jump_to_first_undernet_bytes) + # item_bytes = jump_to_first_undernet_bytes + elif item.type == ItemType.External: + item_bytes = generate_external_item_message(item.itemName, item.recipient) + else: + item_bytes = generate_item_message(item) + archive.inject_item_message(location.text_script_index, location.text_box_indices, + item_bytes) + + + def insert_hint_text(self, location, short_text, long_text = ""): + """ + Replaces the placeholder text in this location's archive with short_text, + gives another text box for long_text if it's present + """ + + # Replace item name placeholders + if location.inject_name: + offset = location.text_archive_address + # If the archive is already loaded, use that + if offset in self.changed_archives: + archive = self.changed_archives[offset] + else: + # It should be theoretically impossible to call insert_hint_text before actually injecting the item. + raise AssertionError("Inserting a hint at a location that doesn't have an item!") + archive.inject_item_text(short_text, long_text) + + + def inject_name(self, player): + authname = player + authname = authname+('\x00' * (63 - len(player))) + self.rom_data[0x7FFFC0:0x7FFFFF] = bytes(authname, 'utf8') + + def write_changed_rom(self): + for archive in self.changed_archives.values(): + self.rom_data = archive.inject_into_rom(self.rom_data) + + def write_to_file(self, out_path): + with open(out_path, "wb") as rom: + rom.write(self.rom_data) + + +class MMBN3DeltaPatch(APDeltaPatch): + hash = CHECKSUM_BLUE + game = "MegaMan Battle Network 3" + patch_file_ending = ".apbn3" + result_file_ending = ".gba" + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + +def get_base_rom_path(file_name: str = "") -> str: + options = Utils.get_options() + if not file_name: + bn3_options = options.get("mmbn3_options", None) + if bn3_options is None: + file_name = "Mega Man Battle Network 3 - Blue Version (USA).gba" + else: + file_name = bn3_options["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.local_path(file_name) + return file_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(open(file_name, "rb").read()) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if CHECKSUM_BLUE != basemd5.hexdigest(): + raise Exception('Supplied Base Rom does not match US GBA Blue Version.' + 'Please provide the correct ROM version') + + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + + +def get_patched_rom_bytes(file_name: str = "") -> bytes: + """ + Gets the patched ROM data generated from applying the ap-patch diff file to the provided ROM. + Diff patch generated by https://github.com/digiholic/bn3-ap-patch + Which should contain all changed text banks and assembly code + """ + import pkgutil + base_rom_bytes = get_base_rom_bytes(file_name) + patch_bytes = pkgutil.get_data(__name__, "data/bn3-ap-patch.bsdiff") + patched_rom_bytes = bsdiff4.patch(base_rom_bytes, patch_bytes) + return patched_rom_bytes diff --git a/worlds/mmbn3/__init__.py b/worlds/mmbn3/__init__.py new file mode 100644 index 000000000000..9c6d11fade03 --- /dev/null +++ b/worlds/mmbn3/__init__.py @@ -0,0 +1,483 @@ +import os +import typing +import threading + +from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification, Region, Entrance, \ + LocationProgressType + +from worlds.AutoWorld import WebWorld, World +from .Rom import MMBN3DeltaPatch, LocalRom, get_base_rom_path +from .Items import MMBN3Item, ItemData, item_table, all_items, item_frequencies, items_by_id, ItemType +from .Locations import Location, MMBN3Location, all_locations, location_table, location_data_table, \ + always_excluded_locations, jobs +from .Options import MMBN3Options +from .Regions import regions, RegionName +from .Names.ItemName import ItemName +from .Names.LocationName import LocationName + + +class MMBN3Web(WebWorld): + theme = "ice" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the MegaMan Battle Network 3 Randomizer connected to an Archipelago Multiworld.", + "English", + "setup_en.md", + "setup/en", + ["digiholic"] + ) + tutorials = [setup_en] + + +class MMBN3World(World): + """ + Play as Lan and MegaMan to stop the evil organization WWW led by the nefarious + Dr. Wily in their plans to take over the Net! Collect BattleChips, Customize your Navi, + and utilize powerful Style Changes to grow strong enough to take on the greatest + threat the Internet has ever faced! + """ + game = "MegaMan Battle Network 3" + option_definitions = MMBN3Options + topology_present = False + + data_version = 1 + + item_name_to_id = {name: data.code for name, data in item_table.items()} + location_name_to_id = {loc_data.name: loc_data.id for loc_data in all_locations} + + excluded_locations: typing.List[str] + item_frequencies: typing.Dict[str, int] + + web = MMBN3Web() + + def generate_early(self) -> None: + """ + called per player before any items or locations are created. You can set properties on your world here. + Already has access to player options and RNG. + """ + self.item_frequencies = item_frequencies.copy() + if self.multiworld.extra_ranks[self.player] > 0: + self.item_frequencies[ItemName.Progressive_Undernet_Rank] = 8 + self.multiworld.extra_ranks[self.player] + + if not self.multiworld.include_jobs[self.player]: + self.excluded_locations = always_excluded_locations + [job.name for job in jobs] + else: + self.excluded_locations = always_excluded_locations + + def create_regions(self) -> None: + """ + called to place player's regions into the MultiWorld's regions list. If it's hard to separate, this can be done + during generate_early or basic as well. + """ + name_to_region = {} + for region_info in regions: + region = Region(region_info.name, self.player, self.multiworld) + name_to_region[region_info.name] = region + for location in region_info.locations: + loc = MMBN3Location(self.player, location, self.location_name_to_id.get(location, None), region) + if location in self.excluded_locations: + loc.progress_type = LocationProgressType.EXCLUDED + region.locations.append(loc) + self.multiworld.regions.append(region) + for region_info in regions: + region = name_to_region[region_info.name] + for connection in region_info.connections: + connection_region = name_to_region[connection] + entrance = Entrance(self.player, connection, region) + entrance.connect(connection_region) + + # ACDC Pending with Start Randomizer + # if connection == RegionName.ACDC_Overworld: + # entrance.access_rule = lambda state: state.has(ItemName.Parasol, self.player) + if connection == RegionName.SciLab_Overworld: + entrance.access_rule = lambda state: state.has(ItemName.SubPET, self.player) + if connection == RegionName.Yoka_Overworld: + entrance.access_rule = lambda state: state.has(ItemName.Needle, self.player) + if connection == RegionName.Beach_Overworld: + entrance.access_rule = lambda state: state.has(ItemName.PETCase, self.player) + + # ACDC Pending with Start Randomizer + # if connection == RegionName.ACDC_Cyberworld: + # entrance.access_rule = lambda state: state.has(ItemName.CACDCPas, self.player) + if connection == RegionName.SciLab_Cyberworld: + entrance.access_rule = lambda state: \ + state.has(ItemName.CSciPas, self.player) or \ + state.can_reach(RegionName.SciLab_Overworld, "Region", self.player) + if connection == RegionName.Yoka_Cyberworld: + entrance.access_rule = lambda state: \ + state.has(ItemName.CYokaPas, self.player) or \ + ( + state.can_reach(RegionName.SciLab_Overworld, "Region", self.player) and + state.has(ItemName.Press, self.player) + ) + if connection == RegionName.Beach_Cyberworld: + entrance.access_rule = lambda state: state.has(ItemName.CBeacPas, self.player) and\ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) + + if connection == RegionName.Undernet: + entrance.access_rule = lambda state: self.explore_score(state) > 8 and\ + state.has(ItemName.Press, self.player) + if connection == RegionName.Secret_Area: + entrance.access_rule = lambda state: self.explore_score(state) > 12 and\ + state.has(ItemName.Hammer, self.player) + if connection == RegionName.WWW_Island: + entrance.access_rule = lambda state:\ + state.has(ItemName.Progressive_Undernet_Rank, self.player, 8) + region.exits.append(entrance) + + def create_items(self) -> None: + # First add in all progression and useful items + required_items = [] + for item in all_items: + if item.progression != ItemClassification.filler: + freq = self.item_frequencies.get(item.itemName, 1) + required_items += [item.itemName for _ in range(freq)] + + for itemName in required_items: + self.multiworld.itempool.append(self.create_item(itemName)) + + # Then, get a random amount of fillers until we have as many items as we have locations + filler_items = [] + for item in all_items: + if item.progression == ItemClassification.filler: + freq = self.item_frequencies.get(item.itemName, 1) + filler_items += [item.itemName for _ in range(freq)] + + remaining = len(all_locations) - len(required_items) + for i in range(remaining): + filler_item_name = self.multiworld.random.choice(filler_items) + item = self.create_item(filler_item_name) + self.multiworld.itempool.append(item) + filler_items.remove(filler_item_name) + + def set_rules(self) -> None: + """ + called to set access and item rules on locations and entrances. + """ + + # Set WWW ID requirements + def has_www_id(state): return state.has(ItemName.WWW_ID, self.player) + self.multiworld.get_location(LocationName.ACDC_1_PMD, self.player).access_rule = has_www_id + self.multiworld.get_location(LocationName.SciLab_1_WWW_BMD, self.player).access_rule = has_www_id + self.multiworld.get_location(LocationName.Yoka_1_WWW_BMD, self.player).access_rule = has_www_id + self.multiworld.get_location(LocationName.Undernet_1_WWW_BMD, self.player).access_rule = has_www_id + + # Set Press Program requirements + def has_press(state): return state.has(ItemName.Press, self.player) + self.multiworld.get_location(LocationName.Yoka_1_PMD, self.player).access_rule = has_press + self.multiworld.get_location(LocationName.Yoka_2_Upper_BMD, self.player).access_rule = has_press + self.multiworld.get_location(LocationName.Beach_2_East_BMD, self.player).access_rule = has_press + self.multiworld.get_location(LocationName.Hades_South_BMD, self.player).access_rule = has_press + self.multiworld.get_location(LocationName.Secret_3_BugFrag_BMD, self.player).access_rule = has_press + self.multiworld.get_location(LocationName.Secret_3_Island_BMD, self.player).access_rule = has_press + + # Set Job additional area access + self.multiworld.get_location(LocationName.Please_deliver_this, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) + self.multiworld.get_location(LocationName.My_Navi_is_sick, self.player).access_rule =\ + lambda state: \ + state.has(ItemName.Recov30_star, self.player) + self.multiworld.get_location(LocationName.Help_me_with_my_son, self.player).access_rule =\ + lambda state:\ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) + self.multiworld.get_location(LocationName.Transmission_error, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Chip_Prices, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) and \ + state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player) + self.multiworld.get_location(LocationName.Im_broke, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) + self.multiworld.get_location(LocationName.Rare_chips_for_cheap, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Be_my_boyfriend, self.player).access_rule =\ + lambda state: \ + state.can_reach(RegionName.Beach_Cyberworld, "Region", self.player) + self.multiworld.get_location(LocationName.Will_you_deliver, self.player).access_rule=\ + lambda state: \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) + self.multiworld.get_location(LocationName.Somebody_please_help, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Looking_for_condor, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Help_with_rehab, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Beach_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Old_Master, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.Beach_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Catching_gang_members, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) and \ + state.has(ItemName.Press, self.player) + self.multiworld.get_location(LocationName.Please_adopt_a_virus, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player) + self.multiworld.get_location(LocationName.Legendary_Tomes, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.Undernet, "Region", self.player) and \ + state.can_reach(RegionName.Deep_Undernet, "Region", self.player) and \ + state.has_all({ItemName.Press, ItemName.Magnum1_A}, self.player) + self.multiworld.get_location(LocationName.Legendary_Tomes_Treasure, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \ + state.can_reach(LocationName.Legendary_Tomes, "Location", self.player) + self.multiworld.get_location(LocationName.Hide_and_seek_First_Child, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Hide_and_seek_Second_Child, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Hide_and_seek_Third_Child, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Hide_and_seek_Fourth_Child, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Hide_and_seek_Completion, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Finding_the_blue_Navi, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Undernet, "Region", self.player) + self.multiworld.get_location(LocationName.Give_your_support, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Beach_Overworld, "Region", self.player) + self.multiworld.get_location(LocationName.Stamp_collecting, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) and \ + state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player) and \ + state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) and \ + state.can_reach(RegionName.Beach_Cyberworld, "Region", self.player) + self.multiworld.get_location(LocationName.Help_with_a_will, self.player).access_rule = \ + lambda state: \ + state.can_reach(RegionName.ACDC_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.ACDC_Cyberworld, "Region", self.player) and \ + state.can_reach(RegionName.Yoka_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player) and \ + state.can_reach(RegionName.Beach_Overworld, "Region", self.player) and \ + state.can_reach(RegionName.Undernet, "Region", self.player) + + # Set Trade quests + self.multiworld.get_location(LocationName.ACDC_SonicWav_W_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.SonicWav_W, self.player) + self.multiworld.get_location(LocationName.ACDC_Bubbler_C_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.Bubbler_C, self.player) + self.multiworld.get_location(LocationName.ACDC_Recov120_S_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.Recov120_S, self.player) + self.multiworld.get_location(LocationName.SciLab_Shake1_S_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.Shake1_S, self.player) + self.multiworld.get_location(LocationName.Yoka_FireSwrd_P_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.FireSwrd_P, self.player) + self.multiworld.get_location(LocationName.Hospital_DynaWav_V_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.DynaWave_V, self.player) + self.multiworld.get_location(LocationName.Beach_DNN_WideSwrd_C_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.WideSwrd_C, self.player) + self.multiworld.get_location(LocationName.Beach_DNN_HoleMetr_H_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.HoleMetr_H, self.player) + self.multiworld.get_location(LocationName.Beach_DNN_Shadow_J_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.Shadow_J, self.player) + self.multiworld.get_location(LocationName.Hades_GrabBack_K_Trade, self.player).access_rule =\ + lambda state: state.has(ItemName.GrabBack_K, self.player) + + # Set Number Traders + + # The first 8 are considered cheap enough to grind for in ACDC. Protip: Try grinding in the tank + self.multiworld.get_location(LocationName.Numberman_Code_09, self.player).access_rule = \ + lambda state: self.explore_score(state) > 2 + self.multiworld.get_location(LocationName.Numberman_Code_10, self.player).access_rule = \ + lambda state: self.explore_score(state) > 2 + self.multiworld.get_location(LocationName.Numberman_Code_11, self.player).access_rule = \ + lambda state: self.explore_score(state) > 2 + self.multiworld.get_location(LocationName.Numberman_Code_12, self.player).access_rule = \ + lambda state: self.explore_score(state) > 2 + self.multiworld.get_location(LocationName.Numberman_Code_13, self.player).access_rule = \ + lambda state: self.explore_score(state) > 2 + self.multiworld.get_location(LocationName.Numberman_Code_14, self.player).access_rule = \ + lambda state: self.explore_score(state) > 2 + self.multiworld.get_location(LocationName.Numberman_Code_15, self.player).access_rule = \ + lambda state: self.explore_score(state) > 2 + self.multiworld.get_location(LocationName.Numberman_Code_16, self.player).access_rule = \ + lambda state: self.explore_score(state) > 2 + + self.multiworld.get_location(LocationName.Numberman_Code_17, self.player).access_rule =\ + lambda state: self.explore_score(state) > 4 + self.multiworld.get_location(LocationName.Numberman_Code_18, self.player).access_rule =\ + lambda state: self.explore_score(state) > 4 + self.multiworld.get_location(LocationName.Numberman_Code_19, self.player).access_rule =\ + lambda state: self.explore_score(state) > 4 + self.multiworld.get_location(LocationName.Numberman_Code_20, self.player).access_rule =\ + lambda state: self.explore_score(state) > 4 + self.multiworld.get_location(LocationName.Numberman_Code_21, self.player).access_rule =\ + lambda state: self.explore_score(state) > 4 + self.multiworld.get_location(LocationName.Numberman_Code_22, self.player).access_rule =\ + lambda state: self.explore_score(state) > 4 + self.multiworld.get_location(LocationName.Numberman_Code_23, self.player).access_rule =\ + lambda state: self.explore_score(state) > 4 + self.multiworld.get_location(LocationName.Numberman_Code_24, self.player).access_rule =\ + lambda state: self.explore_score(state) > 4 + + self.multiworld.get_location(LocationName.Numberman_Code_25, self.player).access_rule =\ + lambda state: self.explore_score(state) > 8 + self.multiworld.get_location(LocationName.Numberman_Code_26, self.player).access_rule =\ + lambda state: self.explore_score(state) > 8 + self.multiworld.get_location(LocationName.Numberman_Code_27, self.player).access_rule =\ + lambda state: self.explore_score(state) > 8 + self.multiworld.get_location(LocationName.Numberman_Code_28, self.player).access_rule =\ + lambda state: self.explore_score(state) > 8 + + self.multiworld.get_location(LocationName.Numberman_Code_29, self.player).access_rule =\ + lambda state: self.explore_score(state) > 10 + self.multiworld.get_location(LocationName.Numberman_Code_30, self.player).access_rule =\ + lambda state: self.explore_score(state) > 10 + self.multiworld.get_location(LocationName.Numberman_Code_31, self.player).access_rule =\ + lambda state: self.explore_score(state) > 10 + + def not_undernet(item): return item.code != item_table[ItemName.Progressive_Undernet_Rank].code or item.player != self.player + self.multiworld.get_location(LocationName.WWW_1_Central_BMD, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_1_East_BMD, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_2_East_BMD, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_2_Northwest_BMD, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_3_East_BMD, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_3_North_BMD, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_4_Northwest_BMD, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_4_Central_BMD, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_Wall_BMD, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_Control_Room_1_Screen, self.player).item_rule = not_undernet + self.multiworld.get_location(LocationName.WWW_Wilys_Desk, self.player).item_rule = not_undernet + + # place "Victory" at "Final Boss" and set collection as win condition + self.multiworld.get_location(LocationName.Alpha_Defeated, self.player) \ + .place_locked_item(self.create_event(ItemName.Victory)) + self.multiworld.completion_condition[self.player] = \ + lambda state: state.has(ItemName.Victory, self.player) + + def generate_output(self, output_directory: str) -> None: + rompath: str = "" + + try: + world = self.multiworld + player = self.player + + rom = LocalRom(get_base_rom_path()) + + for location_name in location_table.keys(): + location = world.get_location(location_name, player) + ap_item = location.item + item_id = ap_item.code + if item_id is not None: + if ap_item.player != player or item_id not in items_by_id: + item = ItemData(item_id, ap_item.name, ap_item.classification, ItemType.External) + item = item._replace(recipient=self.multiworld.player_name[ap_item.player]) + else: + item = items_by_id[item_id] + + location_data = location_data_table[location_name] + # print("Placing item "+item.itemName+" at location "+location_data.name) + rom.replace_item(location_data, item) + if location_data.inject_name: + item_name_text = "Item" + long_item_text = "" + + # No item hinting + if self.multiworld.trade_quest_hinting[self.player] == 0: + item_name_text = "Check" + # Partial item hinting + elif self.multiworld.trade_quest_hinting[self.player] == 1: + if item.progression == ItemClassification.progression \ + or item.progression == ItemClassification.progression_skip_balancing: + item_name_text = "Progress" + elif item.progression == ItemClassification.useful \ + or item.progression == ItemClassification.trap: + item_name_text = "Item" + else: + item_name_text = "Garbage" + + if item.recipient == 'Myself': + item_name_text = "Your " + item_name_text + else: + item_name_text = item.recipient + "'s " + item_name_text + # Full item hinting + else: + owners_name = "Your" if item.recipient == 'Myself' else item.recipient + "'s" + long_item_text = f"It's {owners_name} \n\"{item.itemName}\"!!" + + rom.insert_hint_text(location_data, item_name_text, long_item_text) + + rom.inject_name(world.player_name[player]) + + rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gba") + + rom.write_changed_rom() + rom.write_to_file(rompath) + + patch = MMBN3DeltaPatch(os.path.splitext(rompath)[0]+MMBN3DeltaPatch.patch_file_ending, player=player, + player_name=world.player_name[player], patched_path=rompath) + patch.write() + except: + raise + finally: + if os.path.exists(rompath): + os.unlink(rompath) + + @classmethod + def stage_assert_generate(cls, multiworld: "MultiWorld") -> None: + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + + def create_item(self, name: str) -> "Item": + item = item_table[name] + return MMBN3Item(item.itemName, item.progression, item.code, self.player) + + def create_event(self, event: str): + # while we are at it, we can also add a helper to create events + return MMBN3Item(event, ItemClassification.progression, None, self.player) + + def fill_slot_data(self): + return {name: getattr(self.multiworld, name)[self.player].value for name in self.option_definitions} + + + def explore_score(self, state): + """ + Determine roughly how much of the game you can explore to make certain checks not restrict much movement + """ + score = 0 + if state.can_reach(RegionName.WWW_Island, "Region", self.player): + return 999 + if state.can_reach(RegionName.SciLab_Overworld, "Region", self.player): + score += 3 + if state.can_reach(RegionName.SciLab_Cyberworld, "Region", self.player): + score += 1 + if state.can_reach(RegionName.Yoka_Overworld, "Region", self.player): + score += 2 + if state.can_reach(RegionName.Yoka_Cyberworld, "Region", self.player): + score += 1 + if state.can_reach(RegionName.Beach_Overworld, "Region", self.player): + score += 3 + if state.can_reach(RegionName.Beach_Cyberworld, "Region", self.player): + score += 1 + if state.can_reach(RegionName.Undernet, "Region", self.player): + score += 2 + if state.can_reach(RegionName.Deep_Undernet, "Region", self.player): + score += 1 + if state.can_reach(RegionName.Secret_Area, "Region", self.player): + score += 1 + return score diff --git a/worlds/mmbn3/data/bn3-ap-patch.bsdiff b/worlds/mmbn3/data/bn3-ap-patch.bsdiff new file mode 100644 index 0000000000000000000000000000000000000000..d3548b4c949a459da53701090d5f6fa5f565b7f8 GIT binary patch literal 59914 zcmaI7V{j$R6YzWDb(0+uYc8?(=`|t$M%R{?aquJyX+F zT|G5bzY$Rtla`QRXZ{NX_&>5l^8ae_e?k9eAfjW%!y>Frsj7_?&GHKX%MkeepL~9Q zKmSi(e|HC7Utdc;_Wp0S@wx36X!xxJiJOdtZ#9)=G)D{q4N&HngA&cmkZB47Dia^s z0X`9n5Q~Tq!iDU3_IO!{0H2__qCx;WLOj6A3PMpi5nx4_ov%b17q+-=<6|$iF5*L2 zd0O%KQwc93Kgt-99jpZrC!1L!iyhDEksVr2R3d0>)(Rn1XqFuhkA2MgzpW6#iqKKS z%*spGf2oR!E?NQXh2}uPr+8pzRX01!JF@D+nk}gU_ z1WN<}{yQcB03Gx{FCK)%|IuJ)|E*w$&IX4N`kxtK_1~)hVuYgr09*hb01PA%oQOaf z^k0nGfA(}@_ZS`dKInMrJh#lWZtEu+VgJr48dp^Z1H^=Fi+SY5FTt z&lR6n{Syg-O62$n?r88NNn!^ztRe`)ek8VzNP!dUFG8h`U zP*ho5R21e@FT>IbXC?KJWJAgyZ-e1fvn`2F8uu#9E{>nbdUu4l9>Y~aB#EY#v9a%5 z(R8ps(KuFzm;Al$YB<%fn_mL{NN*~9pFIRRV`UVi` zb?3xw7E8m4Q%{45B~AcAm=#k0)u|F1VDUzM_bf}@eRRKLU{=X_>gErnOo)yM4gNk( zfb;jU9xg??k6fcwq>0!cNuE<;x<+2Zg?MOfmv;)};84abA!mVqWHMB{)f6&WFIi2U zdEpRVRj=;Em8gxE7e^q3k#A+t6`ew zkW52?mo!*0@#3kvRKJqGGy=M;k1<5N23QHSu|79>oX#|_dtbx@i0cvtL~$@;E9Vf! zS!!mKf(RKP7_lDbudQ3{zg#!WiQOhRw_#PZ^=r?azoqZ!v>B)Kamb-iVTsXT0pMX6 zBw^t~r6@uaVALVcpUw}+p)Jb%*|wxJsnE?a`kjh5GGDG~26Y$!Q-NSm1r zmm-&66(G;SQfw0d0zC>S(r|()TFdjx%ay(4i{FLPDKJtnjtgQKSI9aUL{BrV=VK(-7W{P-OoS-6%G0JRkMG4AmE715J#=7-M zwvmn8;+-+aPqj}pdk*%e5Tg{cU-F9bh2`O#<>3NUAvQkw<*5Rqi^j-upxFpTpicx` z^WqfAKRh4NMIXu3M>c8!s>(An+I-3=GdwFqZ~>H|@I~ zlX<%s!r6Jw=xo%Gg3?F+%D3$N{BVIYv0KFn0f}F#a~wbB__L|EOCN^D7M6aBkxj{y zNuFRBL#llOf+WJmpp~FQF-9mut=9vvxxvuyyD{GlQM|r2*jZXw3o|VKrNvJ<*1HxmWR!y;`tc3}vs=womP{Zl zNS|4anOQxsM#M9dad4{QWwW!ISy>nU5M!T$D;zTOVa+5dkwBR{MP#3~Vzn-6K?uUL zG6Ar|rY3@5t@6X!zz}{(%?aSKW}4%IQszp>9y0N)*`yl*@|?khAUQ=cG@LOjGSp0Y zGI>ybg+-LvkdHMpgjhdk)B-zP;(8WVkHR5wV0a}rhhUC`oAK{{S?|@!H}P8OUy!F6 z4_jI4$uAn5L4lme>iU|UfeEHTz44ZrBX6FFNp3EId|yl_0h}Ue?YX;8Zun@+%^K%z z2s@jB-JE${Aj6 zN$e)`JC}@;cY}{@s;hV9_ewS6JhS(EO7%hC8MeEV(Mb~GV<@o@L}rfT0HI9T=vP2w z1(~7yPzyEFo1IRdL>4E#_@{ASO*AH5$;4>$n?}PMdP;k%wgbka-RsszFWksyZVumX z{bAJlx?29c*9P2~hrb>_ht;^M2Hh!Qdd*645jC>W+qCcYkRcA_J(H&thZ!|IDEB)& zp1Xop8Z~Avn+4-wFvPAq-B5XPRAY#q7d<8YYIUeSXqHdO>3+p3f~TI zw}fb>mzoARKc0FcB9QkPE)hK{F`=y5pFfFW&cpcxCMB*Id0MR}W!7*I7N!X5|2f!E zb!zbkdDi!wJodPy#Ah0}Uz)d0>tE7iTep4Y3i8+Oh7#hHkoAO6MXJ1}dHVTHbcL(F z)Z*bMSkM!{Co>`B*d*J(7PZv3DK#c%mHG2Fe=w2CzgatEkE5z0gr8v6#^%KISDz_e zx#=3)!Pb{;Ej=?@u*m(sYIb_fiRodWO`rH61V)Cv>v^6u zqj5`z3uXs9tfD*{kNrv)HC??27t)p|lY&RSZYZ~C_ZU&&%J6w1uC8{1Iy%ezMcIN_YUT1cOV->nZ;E^%AfX#?Pj_wos%{^0&JUs=?roB)?qM= zPu4jJG@7qSJ%FBErT#;U)IXsyu^{5HXfKQJ*gJe~!s$x*dnW=d={7is!~QHUfvsb; z#Cj~wJVI>K%E}7-VJBVi7Mfm)_@Vr^>h{j(Xg!Y zq&WYAV-~>X4B)XM85t&&fGP-A=%7l$UW)d8)1XCn&HF1431b#TpoguFeIYKI6|ylT z(j}Lx@hVu`Cah9(=iv;00k|WMa$-Ti5IZaObMlPBIbpoSWVvTRC2XXzM4>MZJ{EaL z3HU(a`2*b+^=O0AKdK5qZ1qb#Gi~gX@9L5*fi&}coX}}^ zC0-*$q%U8UpL>UkZzl2v$q)~k+D z!+5^3#n>mz!zDOh!jv%M3aArI4k9y-j9+)aZPrY>_+$K2C%Dgzc1=2wpLQG?T5P-ishoYe7!13L{7h4aqx6eI^BN6`N_x{b9O7qa@^XPp3aF0mQ z(8&oB%u;y`k%iu#^+n%``0(G>{LtRfwxEqcCeKE4 z#T{ifOfaaZX=r+Amx>K)dWgfBbCU(3}`ET2~g*x5Ipof@n z5kbxxLKtvLRf&8Fk@;#>=a2L&xO@r<{sBSm=VAjl{kBTk*rw9Z5HjO@c&frOP&4>q z)I7!;7if6YmQ@(hL$bb7lD%2kkN{ylV#>VqdA%V1^iK)Nlt|1s^HL`p`WoP@%!Goz z8D&)b!Yq5CutKM)ugzBh(_LI_^N#SUabh6T#^5mlQ(UxvDmuA%t_v>{yuf>B`4uPAKLCDHDsNQzP?=CKrw?Zv;~F))&W6d zc_}$JrojR&_+zhj`0vt>AMs}h6|cuOE_YTYvm*2$)`?yxAvLHwLxuSfORCO^mAN!Au3`|oZ4ltjM zj*8t-6u_a@;WB^q0t@RzIp!15mH|*vKzkO(!iXJgbwmX9B}KBFVh|QA$;BuK#)1%1 zt(TLiDhA@VdxNMh3ja$a6}B-3d}er;E6b#~{2A>Z>N+N`!jqyKA-B(Xu-0EUQ7RO< z1{zfUBXjtF`TpQaK~+O2^H}K^SqaD?(^9_T$2r`vHz0oDV@SpYJ0s^2wT#TF{z{Oc zHR^~F91>#Y?tp$KG=MXqgeHub5)Dg0gZc|o5cwG7jK_;3Mfse4QFTD<&GJmrp~yPBm<2r^l^NZP)J4 z7HDX=_4{V87o6B>w8EE*`$v+Zt_4Yqr$StBry=JXi#~W>BgGY!^JTPh0fA>S;H^0s zFYWd_+$tq#wBw@iHXuWKmyh35mXmRi_0$_n(*}%&%=pr(aDq~PbD+aXh=i4*pos)V z*Gdq_Y=0>Q#hsr@-0N?$To9l!ceH3JUO{tcTD=4sW+6CjL}`9L1V|}AL%nn-^YJFN8Ign*Zt;| zX96cWXQ#rLfo9<5CFuN7*F1P+`oOVhd;=lebr6b_v`cw7ft5}yM?6Z6ogp#g^DHq@ zgBT@v+L;mhs_lY(D}1uM6BjBv!+p}4h?}!oi`2`;=0sTUC^tzG@nZ3VQu?+ep4^j! zt~*&+@iw+EjQB&uOA`!&{1?yuiyW7J+ZtvYrnE_yrX=w-mM!GjQOO}YA-{T5Qq#~- zeY!iNfcwZ(1*zdUH;AK|FyM#g1V_~heqVKbtrvzzMXGHz`+wY*KeVhsp?nNan1D(!~@d@fUwW%T9c z9GUCj7n3duFFgz|^OEu@jFr5~|5vxWB zuHn`g3C~m!do*bi(D3mEsQ`MP7s#ffjNSCgIka+(n>gRVfW@)GSM@ zBTa60n!jF98kp|TitAJoO-v|w#en%1HzOD&i5s$Epm*R4KfATnw-ciiJ~O%?wz0sn z`6qx!RkC6E6tgydJ(U1YqwL5?YGUNqxR1Ye7Th!_%G|E|t**6p(5f>l*yY&pZ1je0 z8Al1i+hwyAt0wIl4RC4<{LJ6?BWOuH0S5M^qPqPXvJw$W zED4KbdEg2foH>+OIKJ(}@E6?QUWtpQQC>N18{-|_{R)#m$R>$(9)5ZzFX9`6$p5vbDx>wgT%|q#_BxP7 zB98CE$kd45(u(jAA;&sX#?g5j}{pi1_MdT12c$2d#j>Sgwa8pQ|0(Hb#xJ}GZ3 z#h);6h*=cbsGr+|PTzQ&N2SkRh9^=@PDXC4RIz6_5WY0J=XHu5yGQQ3!cHvVZ`1W( zjak`~V6B%H&>s}uUJ%;~w>xW?cU_T!?Yhs1U%E@Gd?A5V$ek{$w0)}rA+}49lZxUT z0#V8>>ZId-YuwqX=A%ChA>sYC03YyQik`%)6?LEXn9hq-Mx6FN@AWFGS+h zEeRLo80maEbDx0LvC9;NT+(Qi;668eb3V#xd3OW0^tr1E!zkVy?G?c;rxxiqfAk*= ziVnEgvUCQh>cW`F%nJf89Hcp4y@tg_VZ|Dx<(-^_E|Nk~ST`D3Dv;@$MLJW?ySbr2 zmDrIjO~2_|qyF&D1lodeCc*KHY+q$Tgh`X0%0M&xzy^p2(J6AT)p~F7!U^&AAHzi` z2U+<<$q-q??8Xa}n7h!@xVuF)m#>!OI58?_s0AG^g8;2h15ZpE6-mTQYMud`ub1{E zh`F4U6$Do~q=2DM;=eDq;z3oSFJC)M9!>(o@ZjkMraxZC3|;HHSy9#UL%fyxgDEoq zAO=dcxt{VA42P9Ybsz-U!$P6C9tmu9Y-YmR!Eo!_>U!4qMzX}!;*a#e_p-}%4z zYPPU%d~@pNi`9RxIfZ)bQj@F*l5b1r`#_aTgF= z@p(x~tTAbs#?9oUD^{9KW4_+=UCMyUYUjfW5zq#t-ySt+r(ta8+0QwqsCUqX{E(|( zt3&E|k68WXEU!*(D-36KvCh zM3$CISj~=X-p5}EA8AOKXvP0J&}4%@XATY2Mw;ljv-wfXZzRZMBs>^ri?2B&>C~C+ zX*ms)7&m*Ag)Uu&UW*>mg~E>z6bF#$I$F*CF){)y$vxTbc0LxY3Gx;aZ~bsPfTN+a zN8?Yir@^DO{PdtOMZHj{&ag$QlpHaf2mnJT{^BvwK-qke5=IO3Mu$QRTACi^)Z{RW zs)xJpES;?!5X)|~)TCT;iTGqrkRMv{kxup!o%RDaA*0}W4h@ferWWu_1t;hW9j(GC z<(Dr%vz~%`^P&8@K6_%M8S>?a9?S}z)GY4~r{WawH%qAzqwFUh&^O79BxtoMY_x6R zsTpt6t0SY{_#FwS^2jo!nPC#0;5gpj&2eM(rh~A;yCVQ}Q-zV!M(~qFU|}f;u*as6 zf5`HNz)a?*i0)^autTTNHsdKCPqRHD@X~jk$Yl0w!j^6~0y&1VpNmRL_Gpj|hbeU+ zl)?o5efyY9fS72~MMsMVUQEH8PcBKP13;80W#Ha zOeojXS%hZAjUS(`K}{FzCQyg1O!_b6B)^sESy;7F!I*aIyTRh1vKuQdfqR)svabbwciG1HEj!9z0GOuotbc&>c#eGuA(+IMFszRc6 z-P#l5yQeo2@>lrNGa!02%S780P4oI<*U+wCOf= zjjrvX*KagKO4^Q5&ee4k>=3B))Vch ziacA7ZmFciiB@aO+bzr0&Jm-+WM+cG@--IY5oxZ(F7h3r_r&;u(Qz8*Q`Wecym)cT zO|^eG=Uc;-D2Ia)Q$~Brx()H+scycS*~r*mwd`7Bgzhkm_}K}!I6nORFpWQ`E*oot zK|v$*?07_{KnY1k`yC7w+ilpgWb7F?kw`c6g9ki0nVLvTNJpD9oV+Y@vXLJyz|ui2r&A|A?9g?W7Q|o9k64cnV7u>48Ef zBEqB!8MS^Ym?v_c&>S3XQ=#8uLyj0xOtO#RDbmMov`h>Kk8m)5tW- ztRmDz1N=xT0b8SamSW)bk$D}J^F|bQY5}6s_n$8A*wkH>X+_4u9*R~Xh+H7$dj@WB zh>E}jHpf)1&1a3=&%GTNI@T^(bFry!)+`_xcfqlk1a;J&_lQt9xww>2>P6SZ*p-OAX%I zB;lY^-zFCeUr(Jnd+i>*-5)>CP>SI-?`08vnT*}(fZo$T|IYU!**@n}VR86%wOi3F z?ZQ_56@5&Sg!UAp+g7EK=#45K!pHZ`JHiO12DB_^fX{ZYEPwnqCA~~0Cuji2`>~vM z8%3|4!}q0yB&oYrV`>(440iex0vpIvPO#+c>3?T6uYm4s$_`sAXo~7A0LbG9VTQ0OWSub)uzkl5lHu*eR4$mLdV%8AgJ!K*`(?G!M|MbTOR|H zSB(q5_WXzbb9tWr*)l~eW58+Dw>30OKRXdnMYgD$#4>So>FP25M=hpy+D>>%q4Sq8 z({r|tMbg33E4`7{FZWULq_JULOW_0Q%(MyKoh&%c_1~+A)?^;>Z85oF3Qz2iGr^}36+-jIYE?B_uSwqEqjCBPrGOjK1~91 zY&S2Jlj%l*tF$#Y@booPk(ms=iF=m_uJP?vQkOqRzpJ4w>mL!li@los-B6OmB* za%`d_eTEo?W03#_mDneq0BiTX!o`M%#rp}72ERAo^6LXL(Oa!L_$q2*;6Iy4G?d$I zS*k0i9#Dsh1opPx^xHg_P*Q)Bijp^c9w+Lj6n?Q#32Zyy;z-7-Oss8v)OB7M``^a- z1oH`+3mzs=z1ZM@;Jo090y@2Fw~n)sPEE;f+Ch+*J9MDzye&bX<)o2;>c;4R7ZS~J{tm1k+pJ}=|uyqcf zqXCe~{P+BI!!@t9P0-S9aCyP98OZ{vlPg^(QE=(`1SR>HwfkOiYeZUzF-v9na)bMX z<$mVc4Qgf5;(N+`(fEdw++4CksxZk52q*=G6Y8_}QmvL|*SYoha4}VuP&%e|taW@~OVtARAx#|}6 zpnyo@Uvv}ba1xA9L&}Stvy6x#n^@=0w;84NY%55}!aBV&E*3sOTaqbxY~19ocEQ$i zz~)wq5*2p_ItOSb|MZhVwmKcD#}M~At{Rl!3I-v&{@B?s`a3W5X2`nWIHAy1mL_6~ zdSFizcj>`XD;Xt+27cQ5M`IY6=FdSV`NB%e*5e?t6f^fg_5$zVipM>&jMCX6yBBNo?bez&X9Jv(urPtNd=5ju&9nzKNzTa1SM32x+?<*uo5SVmMbTb zp+?EyF81nu$9QGpR#&4jHZi#mcYA3#_K?C*nk#Ck>HJ4DIIe|xNRw@Ygs+2;EZGn- z;bn`%c7e86{&J0P@y!xvYQ{l7St6~g-fZw1F;A%cKn{V)7Fm3PQ<;>r#6;Q<=+_Ev-FOLplHVjVW zaW?~BPx|vz#@mjz!=vxP`O_HRO3o|_}__?h&1dN2~AJ@ayz@M&!!FZ)t}uh!_w z-$UGY+PJ@jlJ}Mc+xg((;EJ&Wrc}fQzSBoa$<9aPfra%_{khBFyzMTb!+08BVhcUb zWrcMEoGnvVi!_$O5&2MMmORPmO+uS}U(({sPq#0rap~dwHooL)LyW_&MKR)pO;Dz7 zd5|K*`kf;L!(w4t7R{|c;*f=-&Wxwpa%KYm`l>rt&UO($lH}Q=aS~xQ6d{5+WN7q!L{>z>zEv7c1f7QEQUz< z{F=R@(*$*uda+D|G?lTj#PZLY*~!w~5|y^W6%4JdTA>ov+UaO}f`027c`H}9KiIX- zjL(j*AxzKPR?pRY(?sNkU}yK^v*&1QtNL>Skvh8<& znoEY}B}0#M9f;c8i>+e+v{bG(rPMlLi~H&^g>3cKc4yDtgSz+Q8Lhvxxs-4*)v2)!5k3|Hl=ALuV5ANx-F0#>vGA znf1wmn;F64fu)E-jsXLL=8aS25y3=h4grvTa;RZeV+-gg?EixfUXpVhhSd=yZ{6AU|f--0p z0EUS7A3K@FOeQA>2a2HJ$M{5)0L(GN4%m1}fX4r$cvy|1PtOn)$z*(qtt-!btmMel zzG<>iQ$%TT6#S0l|20|Y@DktDLi1v3N4VCM=EB!VFO z*yjq-0dkb=l<}+x=;#s=a^)e=mEDMV>l@vW1N7?A7QguNw z_+TnjkixfOM67+7v(4zCH|UvB218(xN#bBHrF593!m6QPWUCWy9$6u86yBOVC!@qE zJ~55oL{kDjpBcD_izvR+b~ibqhZ5WwqE@$&!KQ2hU{2K*=N6K z)JJDq@b+`82e9SUo!2jfgh?2|*M^5S0;>&LMotyKZRz2VB&pL}R(*8oa5Dl7G zopY;aS%vid3kbUKDHz^Q^tW?0W#32H?SmCXFAie{4r3g0hehxQ1IC*3Ykuk62D5S< z+kNfkgW)Na{j6kHgnf|nGaTKO+YXR# z_8a}-!1L?Ijhf+C?O!;y9Q5v>orNiRy1aE~fy{$!y3&4TD4%@<%e6;4+ZnGmo zIROP)qp?f63mQjq0Yt7b`-1pqDQJpl@J^sKo+zuC7e{Cy(-?n$t!#RA?iN&s&~h>d zXavOeK&in-KL1nNlx)lKBO*+QEgVe!7j3?axcxxutDMfy?2jXrGKanDr4pwR$`giX zIUVk^y-gj|hTPtS0Eyn`6z)73R|t_+(t^TFowpG!=XSrM5274_{UgL}efrNoT8+O&qRXUd#@w+dNHzODFFhdG4PGeMX4{wW6^$nECeZJQHIW+UICtR@2k4nz`U472^%qx zXr*rs)hd~l0i~~F`@a`D40=vn2Y_!V(=*>Al&y$uV=|~T+?ucvyzvS5SgdiTjf@kg z<-V>Do7WPF%p_5~mUlGYx)?Ih-~MFGKW^w1KmkBf{aJnW27|6Z-uDPlL;27KgXLJoR-2twehwbfJEEE z59h9Jn4V2!I7P-dn6_F=dE-9q5YL*VL;IO}DKf>>}O%21~ox5)^7VRJBNeR8LrnJs776kFldQ%6PkArhNJm0v3y zu*ZB|^!yCawZWGXIZv9Z8JV3!*V)S~d$zBb@RM2#FRiJ|NBw*`J}~1cpekIvhnN<_ zPyC7gVUnWFRxEEEz+QRztLe2z{|t zwbxS)G2|SSj9r!-Q@(eacP~>~%2=d`t%VE<2`Rso%zmec#?oM-Aob;)1C_yLLuvho z-y~&B$T9SIn@Ed2VgnP@kjKAN?7x~>A`E0QMG5sZ95C z)%Y?FKo?)uRoY)SM$=NJs&!lJ=U1?@uPP=tog4`BPyH;63N}Mrh$2q4gJULdQGV|G zHWKS76oq`)54B9p%zv5w-44XQtOwzMcL60l+;Jwf69P^r&MJ|AawSQ+7E!*fjAbuM z*buhAnsUac1cz|W>$MjVtJsw-7hiq+0&@`}Ex2K{{E=Q;v<;i%>>gPbnlaHrdYLwN zMR?p@P=w=J6KbDIGd`R*kv87wMhpSE!A_>Hlb2NRjrIBWrQZ0@&u4zXK<(KnGZpR? z9oo={0XNOdWEyPE4uvu3MH0BQ8!g+>ljD?)lN&Sq@gm2xX zGK7qOIe3;bz&%*tndHWrE$XjEtj!K9G-=syLqyGn*E4^{BNp=Bc{)FB2&@JOV)l96 zELb`)3986uqDP}R*>b#>fY-Mr`V88oRoc^s7On5R9jY3{SXV--wF2xm;YJG58gQ=Z zR@T~~*EO$VqK{0K3<1$^^}|Ua5#l(spBZgF>6HXJVNzaaR&j>k%Kz|p?K9>A)4@G0 z?ZpxS0jJvVgZoAJ3l-n|>23Pt;y&YtqXe1#=KCQGtZOP^_@wo3*P z?^}q+D}1jspUaErXPR8FP(1HD(^wJN-x}5t)}voG9@y(1%k2y)siE9VY%Yuskt2b( zNH)m0ZM9xk)wbT=3kQpA8kCWqT_%WT?AwU^^x*LCo~Qt5dd41by)gFv4^%SP-WfTPBuCV8YA318f#-!f68Bq7mHf)(AE#BEJXt%rxi1eF44z%g| zH^P;|p`?&jwxLLBobFw+m$eur}G!5=Ikiv>)#_Y^$ ziargis={49)Q1T*MQ)>#yglDMRp6{xDg6ZPe)81h`t<_eUz!!pmzk0sDtFC9k_M&h zQlMf#iz2>$bpHlhpo`d)XJ^}-xlT|t5OXK5#@=!=`FXTIzV+(cdfUBCkv73U>p-8! zvB7qKJ$^d!DrY%psXo5+FwC_Po*ZLqY12chJ_!Hvo z>bt(J<7d~Wk0yAdqCuV!x3m55i_`1?+^{5W3kchk|F8bgpZxP5jKJ3yApJHq!+s(XFvhnV1?Dt5?kB{>wX zq>lh&>Qs_$dk~D|C>SHlon<&*Nyt z#4FoGic3fMhkQ$h=NNb@DlbeN)WS3XjxBpR^FQeo&-p3X+rSXX469sK8ms!lXWT-b zg_FB7a~zTp%RgcZN~j7LE$$59#K8l+=rN~lYF(6QwN z?OXX8Z<3q1zkTU0~T2U=gHOOQwO4sOHnj^l5Yuf#Hlr6V1d|RIc_-umNlvx>2 zX-7`x7ZB2wKu7gA$>mG}hsyKi#xnEy(Z!^^v-UF-&;rzdwWgI-Cp$j}9>lGa@HTZV z=}J!A(nq`K*pu++aNH$znuAs%xv(K|U1_B)AzKuBjt}sB=})8*r;D9ggViBIRX{eURNn2Qqo>EHf#ndG(;#xNns6@blThz0?RoWdS_6v1U1>4}nQKv>=XCqNhc=uiM4->LDeBzOpP@ ztFkxgk2g=G(v~+yTN9QMI7?Mr88i5Ki+HrvD|q6g`ggBD(f6!q-@v&QxX~-HMCa*> zl|^^Q?|C$U_Ny&o0FpbtRgvsv!|abbzrawuj2aF`CaxCR6|<$hu)~VMq^=eYzE~IK z?Tt>dVJXp`6FrMsNr*k*YZ+$E)?h}UD>k4x1bi21@fReUbUcJl00={fyOS7|M|oWw%ePp4eEI8`|*{ zEdR;-ABtGZF2EF|@7__$aP1l5fTuyV@J0o`nuoOK{c}1eys<=RzTPNY!3OI(q!c-% z8(^|`{Xu8G*CP5xM+W=*eVV{U)nSLPBi+E4srM$9O`v7u*w*C@zc#wb(A7WId^PrT zb?oba6zuroVe|KPJ;#|!sO7{rUGYBzC;$&JRi$U9dIL5V7CI2&Q*}NP5=nApHDl>b zVz(3c9X3UPY+IGZ?a7W8NiSHI3-$2*&rxM!S>HaQkaRR`QJm8|W?sC(fNz4XLn*z;3x;f5cr$Ysqvxm1`V%kTJl=MD! zJr2CMtq^{F7Kt`R!WRPuc$Y+k1`b)f)MMrQ)ucn7K(^aiiB+65*g_jLp}cR`@o^YG zn^Fkcg15A^aY9$uadO<=;!+3ZNxT};U#jXEPSTFi65scq8D>a1g%l4SE2d{xg~Q`8 zZIKqf-7i8f|C#|LzUx|9|G+SvM7rq#GJ>4Q%YbX((6;C6?+qzi2U}2g->sBx={vcm zmhv17)QuUULz}UMBHYN(x8MntgA=un=uzXZungZ+s|INGTw`gYWXL+?X4ewx4i(>_ zjp9YDrP~fjL7vUu*xtX}M#%%L@-q=rNmOU`LvCe1d7uU@&*i(im117}GkEc!+X)2= z<2C-l5UXW+Dt?U{UM$ej_5@OSugu-GDz|Axcf96?d3Hp_++3;Z7=6@vp)|DESqa6r z4a#Is2K0?P)zMfStq(R`Z&7_$D>}>*xG1 zKG1BjH;vP!MTs|dJLO;e%)y_UWkMoZa_o!fsAw#h)W|)M7gApK^51iv`Oh4a^Kg$s zExT@(u}LwWv5UJE!BIO4!e{^-czCi>EqkVc14~+7JYlE(e7|la8~$))J|^U}dvKPI zv_+mg@BfYQEoqFW6c7c{3S?Uj@oYx%1x+zO)AVscL@f32ednYNeJx@W^^Zkve4>Kz#JacK%JWmj3lqhaUu9~aH z8GTa7a;L_G(F7e=%Zg zW{@!s8rCpo@)VzU3wKm?>*cj#A-z)Fsf1H;ySM?HGhEgWn@B73Ukr)BEa)*!6mT^_ zkF$4Ad*O+0Y9tbQRmL6xzYYt9{v*X0?%RbK(;L!LTEQWo_p*FC^Syf9V@4M0(5kmI zpx5e)MGb!Zvi8amPg0ocN;Qs^n67reQ?01k85uWzji4o#Wj^>l(z9X_8)O6o$V;cc zHNc`XAV9rNx;is=GU!Inz^h}Y{{mNyjujW)d_TQ`I322iW=I^Va^j5I23ZN8OMA}L zTrV>s@(7=jp$rK2)dkQuZP?0D58*DxcH>Qv(d>^k`Brdcl`tR)RUB>_TP>|Ab6d^wA(wReE8yUuC3W+e53u}5LNtf@9l0GwC}=# zO{N!l2F9ZCn)Zgc;}nU_9a&tbTRPH`_o96t+d>byz|_*h6t1|WzRQETlo#?OGZE0s ziR`U-P5z7K+9vkqYR@u>}E;WNPkPf$J5mte%iIop2$W^P4ic7wl4u_p$`MVmkXqXKW^)y z7MMs?{CGZwirR(xq)uA!eYZmY{1QNh`t{pzJdz$E?$5#UwORpu&JQxBmY*4xd`rzN zY4!DY*yi?!KpsbwUcPZI3DJ6JbMWr?B(1ZzcxOXrR5eX(|Eb%yYJ1%;jC~QZOpAQ%55?o zpK@6c#iAb_uG8H>_e5`<6J4sY21A}ef9LB)^@vqQd(KK$YW?axoY>N>fiAZjiAM-k zYUHz=2%-QF01o9Ft_yo+pTIo*N3KMDM|H6&n!jS;*Ao&HRh$Qigak-q0LXa!0je5n z9mlh7Jd>vN2MxpxVb~i2DwRZ$10zamCSZt=P804^prJ^)4Om%3;d7wjCw})E)d{C! z0?Tt$1GaCHSRh#iTy}YWaHB&K5`%6Kp|Tj>FO8VMztcr8(jd=#HCUAG*Fd*0I7KaE z01~|z&Y2}!jJ8mNJYz%8qsYuVz~&hQP#Vl#bZ}}oA*mee8GA9HYiUAst5>7otLkGB z)|q5VI=Q)ALiy!Z!suj@e~-^sJZppy-;F1E-=c}GE=ZT$tU2W~97mf)Va6n!j&DEB95xl>S z(1mtX5L#adY}o2gkBD@^ighF6bq>o(RINkMHc8q^YBpora4NsN$)*_};@Qr?a6EXee|2H^*uKg4}hb z8RiEjZ|kaJN_2ooG!g|dFR%FMDoh12G^t6ri^~l zHCZjyt%BJM76*BuqL|qdOO4D1i;oi`nk@>`ykZEQ#tKXBwwsZmjRVQaf77pR_W!Mw zLLAastM(JVn_Vr-T|{on!fCah%ldujt%*t#R>7Mk84_LYaSOjPJXVl!AJ8AIfc6t> z`4RyMF2I8rf83N2h0J%e%$huN-9AW&l%Cm=q~O*^eIs+|1WJ3&Xh+gX`I%D=U3(y{ zaw^QViS-lU=6t?=OjSlJyagN;w8Ii005j{QM9Y6}IoFMc`lV%GnJO-?L*W>oH_%Pt z@%WO&KW2s;;!MbfK8pAuvAg9m0+#E`&S`clgxmpzr|w1{|28xf1k(M3sn_skBbwLR z$1E7};mQZ)FqfE#+_pKwP6}X&UURQi4e+Aa`20{R8vDmC^JIm7N(|M|JP}L26 zbZvy2xF5`aj~bZU$pzY#G6GEuc|>D5t}3W0X|#BQfmm{7H0B6XvCDX>KaA1O+O?b! z{F`3P7~WO0` zD@5Wpqwv(5F+erV{K}S?GjP}C9=?T=haV+Gu5x3Czh{d}Fhqr#>LWf<4lw1;3=@VDp0 zdHG$XEM9CjobNGnrB9>qw*M~+o{fAK{`aKgC5roO%_++~m>DB!C*qQ4!5hnqWuIo^ z*k8L)cGaDoP{#KF0+%6Iwy<8^{Kgv#muTZgGhb;8hmg72?$BS!Ns7gRv8Ns?>;$R; z0SKJFVI!bwG)pW80bhU;%d9&x7W;f8$q=0Z`c5Idh&D#Qn;knM8Y{LkC7v$p5qTdq zAN`cf>%0Sg8^g-%vTQw3PmRlkRXgWx7B|<>)Qbx|wn2>e#<0%nCfnrw--Dy|ISngf zE&ggaErPK0t~#NVgL&XjR2~Q8=<4XzRQaVU+y5y#F2!|mCjP@QDeW4zgi>2af~V@|_qa-%q~_H=R?R>GLsBX?g@e zyTg^JdGXL3hT=_o``IX!`(Dll2imgqizH7`$^YK&PU2BEFsnf5gs1gU4C9Q8ZTCdb zd)o=a&Eozo7PY>R9UbtS7+UTB@~2Ow`jsJ4O7dt>AEj=4wggp3O`vV8GqC#%0LktY zo!>nycjE7C?m*jth{ih#*>#5@40mna#6Yt^anA?+Rl>6)@X-q&VPUn$a1oKWrKG`+ zkqR^xAQylT)QEy?l}x8{;a&6fx&y}z#0JbWo<>S&XE=Tn*;|XT3#1{DEVZ$FG+o}A zQ2L+42tY@D6$Cp$H&bodRkU7et+R-QEPxZzxwa9~D0_!vFU=O&Jqi8+KuctKnmWVa#=NI**H_Z*RE0(=$-JN;}kp~qz31mi_XuM z4YcMSbAfl=7~w|pXbNnBn`jBwqHh^@O0L~LTYt5>D6?#7=jU*i*E?o7E3^^G(|=S1 zeQvc6d=e~$ji9Y5h&Fk4#>j+rKbzQFJu=5t7Az<_G_T*#RG_-UIy2IelcoAHq|QrN zRYInx=>PKaX&>%tA^-bRyWosj01;7-Y-r{uHk!6xQY2$0tSO#UwCSlbmBp8E^Y30y z4z`^QJ+rj1myt)_8ufng>a>tY=Hml5cE$H#!K&IYEPg&uT9N?nt9kPS7fW7i6ynE! z+f%C_p4H_?N@2v_$2ISMUN6GKk@{PPGm!n-BeJ5|MVpFW(VM7pnZ_ipGE5>}1)74|=CWUgdFQaxR1G_?M!tUcQR8qZ;y zvKzZlY2j||!X5{#pRh8BJivI}&NXeNkO6g^qGa}qIz-Hg{&Xf&mBsI~cd3>)aAl(7 z`LIcV!v?8%UDi$KoQ*Ih7YybRJXvlG>ODGxGLilnOU^UK;-++z+6}dXr;YL5kHyBB zRI(ANkW$lqiz7IO5M1T{*pK0AL<`nybjg=%;e*KPk?TCU-SG0|+clW>InG226hIKY5SFtX*<&7B4zWPn~Ct zdtp)$bix>)!BB>#5yUS-D>_IZ6+gp)q_OV{v8Vn0mH9d9q1N`KFDGpz}_#yHjB zsyTn2UA}F>jlS+J^o|597MDbgW(T9;gU3pZx(8#}E4k^5bmn?9YblQ#?u9G0(r;ZO z=cni(G1V>>loRVdR~D5~fP3p(wJK#;x`1r7#%P6p!Qv4`7?-QBWx8+DFOkYKbi-UT zy|uftl26YH65V$f=aqmfg(@r#-T_83MqPx_(lXhgIN;kwKFO(yniF{U0|{rpYL z;uG9$4`XYyx{~^E5@5y51i+F!$!$dY0yqxtyOKT+`8J7BXwfZ5^?VamXL}e}(n|{B zEosvhy(h^3I>`35_54>HT4S{xyDk}EXp_*dGt z=`O$mv6H^1%Srz6$L{_dOO3_76k`AP&~);jAV^@6 zB9mNr58=K6UU!u$1p_S9GbMRW=Gac}#uM0_TDaBhC>_gSts4;=6NH##WIt))uOUt+ zUC7E+%6W>_^_4FmdExhT4D~!8+ORH;9yXBVKTl<{(Scl@8P~2;z>mf7oUrCfly+P@ zT)z?bi#hUdM*z4H_5A9eONd;TfYM2f^)WtI;?J_SY~3!tf>p=C@1E&2 zC8mChJS8^WK{xy5Ev?Zrrwx^~Jsr2kpxNw7Ys+gdR8Z1?H&-83%opAbuU6r%r73^z zd5zNxY_kBmrXlXD<;p~`*Lp#f0p$Dg^-%LWXwFOA`1Tl~9#m$(eh zX5?1k6kBT`5<<1Cv~d&|!EnXpBUg{})=T0?wCJw)G8Vj6AyCKY<#`z`hp4l?9mnS+ z?dxx9i{H%X2%*fy-b9>V1leQkt9IpsdlpxuHlV^-rF55!iux_M8}4yCv(CiB)Be}- zo?N+Us_`a-vqtA_U>XCsHxx?ZW^}RLbK6#ZRlI(+GdjLQBaR zRz6X7cH+(Y$G5Ky`+#&EPH7jz)!=7>Ed}VcWE$7lt!dyB{(fU~i0`><=;@j#q@+gy zPfoiNA&LIhQqb^DE6;S+|Jh$RU&irI^C6My^iSj4I$Y$jwLx|oZjMWIx?o*g?`xTJ zxtQ-}B4fE%`9`9w<5~OtEwgkDLu}l6Ho58)3iB5aG9TN1I}GMO6Q6AqwPntc`hVzS zk@8W=|0%_177d~d_Vv;8ojw~-ndavG+BS?19kyTob)x4DhsF=KjQ;^5lrf4T}%%*p%<&XKli#Pdb+4O!rAR@ZXwyhy8LF2+z zTOp2HwqYxQ*kfjGb{89C>}X@GUiL>L=v8%N>V1zWG4Hp0^qe(&6vmqFkfFfiwK5AvARC6DuKFA$USNQ_JIkQ- zqa{HwcrPgZn;r8%8`g7_mpm0hI)Sd7q=;4ST-f2cxF zCl`bGGfm2{`mTGH^J2!aQTPh{dO>+PAtS@wxhv_|qB|nxQg5%yNrLfILb!lBjvx{{b40V3eek57kD9WD5Jhofq0<;{bMHUmPhWshk7CD z2fk#;LObiY3^|oBbaSE|SxNeux{tD(j&~7KJQ8V&c)eK9rw;9Md;>S9O{p0Ipnlr_ zgV(yr*fU*e8Th7@_*!fc5$KR}IFpKm>tIW7 zD1R(@3q@6H4C&TeFhCWKIP%Tm9`jpCR)(ua#kL~-buExq{l$=U>yE{DaF$|c;?Z@$ zD$D&^HYUg1pIEh}$Igl;fDVaw(NPm8~?5}y4 zeZNf4QIz!v)vABb{v9DFOX%RK_vlVEuffy7{^V5nW{d6^k?e+huC3X)c4VPhpKDO~ z-ggt(PiCSx%^Erv+4KyXp~u}k8O(kEtG@S?n<;3;%J1YiWZq3FU;(<=EwA3m5kjNB z)!IB;^9JLr)H3gM(}bclz^B~z2mUruw$iqT93&)VJkuz^yVK99nQFY2slJGugA-d$ zjSo30HhEVQ-^O%AF0Kz%s9F4@7lgMHZ8cP{QX7J+s9b-iLwnFb{r`p1vUw-;jwlX% z<1pU;?_W+UwQ_+kX&Bsg{M~qcQomy#mrF|Khl_6k@16Dj+^Gv&3RnGhHU|I)xvVa% zce*ODvO|v$;fvI9?crfh;m2+M4$JOw-}zm%@53W>2HN0x0`UKta%rm#R!bQwJ0Lrm zihOM^@K6y6oSdqDjjhL(2(`##oxgE$iL-Wup~%(RtzPL$H5!`sZ*L*#GnN0O7}V-` zxBNOTt4Sti*5enFitc%EOb!7_n1xlp7J- zKkY2802d0{*c%iSN&5V{ifyrO)F(-~axs}b{t3g*hEXe$@^`@bakKh6)p)NoZ4|QC zSAozp24eL?7nKcXqqjv;yIe$u@<0ILmR~1?fbU#TXW$5Ph|Tgu=KGN6En|ESEg?`6 zd~-=RgPBHp+uq+x8xfrsFhfuwO3&ym?`5KZaD81w3E7n+e_htRTJ1;`f(ME&p14ac zhv-I?yjodIK}+bRWS}Uo`RLW!rJ3T4tTXj2K7V5S#~WRRzp$j>LadU{|9s%SV0AZ( zcFk$Xj`mi@-V;fyv(v9?L?Bau^=jb_onk~=~!dFBz?!?p19rO6+ z8?0bCjEt^oA4jttnlFkL(cX-q_sL^Yfe=k;N+TJ2ppa7BWf=X!iSRLQP_rC$7+{E1 z4z61uvrGlUXHkhDdr$H&;g*?!S2G_2?W)D zTwq|xpmmKT!*o7Epylq7`Yi(}h6Zo>c>%!ow8xx@(M8&r?gFpF+v0R!aR+QaMADeL z$V3IJ6temY_p#?0D3CV0LJTt}1nOhhBn*C6Z0fXg)pr;*W0^d3!+l~|nIND#(lc>S zEg&=31>IM7g*GGtzUcB4Q2eTW|IJsU(M7hd@^4BA{P6Q5{XPd4^+ufd0j43HvXmt( z*e;n=NZZofMRftg{v(>WaJzj(79Amp{oG^s!uhj4g(a~b5C5MWR&_lerq+|UArHM^ zMhnuqnpuOuP&KXbn`B^M-5GehD>|yJd^cqt44A?bMNbm_zMZw>=ZJu^pLS~72SaYv z80PZ_blOJP!w_8>V99s6!a&vuEn~A4GXx6;$Y!^Wn0H2WH1$1j%3G)cce?2@W*=l5 zk042IoJfY)f~>xYP@_Y|9TMATnkihQXTv+nAZln&VQxvoY~zs1?R9<0gf;S;36}sK z4viM&KslUvpudMHJG3)G(y(F2?{pj2_|fF{UD)-3!ZnvPOj|+}Ks~e^qGC8WFro=E z{jRVLOSrJnh{$qJ*zxCQhTY5>*GV>E>6@M_T^po07BAXEX^=#!L(Tb+LeK0IrSp}c zF(1@kB`h;j!hgdS&4{38rf6`6b%n}v8K1z)qn14guTD{zC%GGz!y-GS=}yPR&a!V8 zgAPa80V@1gh@6kN86->sR3D`OFJK=RNrzCG4Lh?{TMJlKA1ha9WNh86cU65AE~4nM zC0<~Tm5K5Q$Ld~0c#?V=>lDXuAHbyEb&O)^<{}hC}Gm@O_ zcXbq?jk}>~we@33&qS#Ie3c7|Dij%?@6-sHYeb@qS6v=E^)-Zndk4Elbl+ zXnQa%Y>Fp<*DI8h0fatOz5!xN_(AdSK}K}}uJY9dZod_g$`OFH=%2prXVcL49_w6d zf+5uDqs!;uHIHo||QcYNf8|VZm4@jxgQ$nQ7A}}(0&tE^7jWCO^d)--5;|QB;r?1 z#@e&fOfN;% z#i7%znZ`6ih6RlzS}!4Bns;>1OpuDExL}jZ*gY~HZ+zQ>X0V$<%3r7Jxw`o}8xHpi3U0-x%?SV)cJpL(BAEHmC8M<^S$qA=#@%_a zJjD+cNtULeKS>np;2Y<#`z8M`4T)cN~ne%PR`aO z8v|cD;d=gbqdCA}1OWMV#P_vZ(Oh%&jGnlf^GXSG3k*R>;HLNJz4fe9(opvg+)q;> zmChFt_p6nLtYzn%%l?=Cc6Ohg(y|U3ixk|FO&l&W1N|r1SGdMGpAlU(iW>{aj2$!p zd5lKk)sz$Tq#bJOFCnqC?{O8it^1}j-Cp)BUIO%P(Bx<${?rs=R z`y}};6Ub{0!)0v{Rs423jHpEKHffq(eE$RtxZga9r(btWQR9?r)U@FSaC|7{Ysv<; zz;LT_+koaCzfEQDJQ&$s7i=+8OGg|ke0Y({*$U~dpm8{1uXV_ILghI+L*>27*tBZS zhTb^*>l&{ZC{Fkkp-D4UpzjtU@_V4kX3STNjF#C{I?0B(euOFrhxMgxxst~mKkKX4 z9vs?oO&_mr4*l!7TA9hY@&OJo!C(0vRxIy7xv@WPWkV)zT|CA~QygR2go=58yCgHV za;QPPqc+=zzc5ztcNZqN0}cOc@$7!5rp&2mZ;}%V3S0Y*v)1l)GwMO$#42UvK2GC5 z5L$$}-PCikx_A36(&Y=_-5auZt9oc| zkoW`gdo?QG08!pO1z5ak)I?mJ8ve#$fUpZ2(;C1~!oGvLgjd-f}ME z#TN#Q7YHi=(Lls8&j0L1LI_YGp|*p*9xJLTIyD;%5fmWP8!`7g%5433di@7N@tlWv zl%0x2oe5u?EiaagW3Dgf%Fnxayv3LE@;QBkT!h=BiVaCeKVvgpLbv{#A3&$hU}{%E z@ zjH*FG%;|?+m#Cb;A2tl9y&moW#AyNdAYDga+n}-&%3LMd*quUAV{;v65Go(J!&ENg zSJW2w*c^+VJnu-pv*?}d3IDTic7=cdMFdY~febAu*%8e2LQmB~jb5^hNq;tu)avM& z2xrgnM=k`M6NeF=q^Jd%0s-rLc}^uX`(e=dtx(nJorjU?%y{k}yYBp~cNrpf4z_hr zS_$EIH+Os_*w-HYTVo#&2%OZ9TU}kD-MwD%p_{1d5>ZYyO%A?VqZqdPgEj)zKGCX- zK0Y%FJntI+TcWjQ%eV3UzC9?9JVg10uOzM0#P;|uTtOU(JKfZQ@dZFD{DQ`$8@0T)} z`A2P!LX7Wec4N6e&TH>>@;pwvOg0VqE}aa%UIaOB10u65&BZX26s}cpn>}7#-3qc8 zeW7>azOP(g?iaDw{X*&zr2;*12>Cru&@OGM9bH|5=1P)-)9Z52y?*R!;wywj7eJS8 z|8aF*NHb&^Ve(g_yf|g zzJ%R2h|72maCrw^K5tyjt106r8BlMH5|z~Rkob!CZ($fmbH4fr)ryaEr}Xv25n|ZE zZ+Naz#r@Aejl(;)Oe)#R>C>pRc^@Fj!BBq3p{h$AMrJ6F0}qBu2Q4~=;1}<~YK}}r zj@Nt}T+FCz30zE`6{Vj8He)!@>Wcik+_4OsxBSnki!a7(tVrOA=HG#x-i@3P&3C}# zLf)Qt8>$E*v-E22gd=oJLkSrEnTViKhlbo{#Sjf=EG%#`(liaW#5}097zLG+(lQW% zH_M5>`eS!KQWTwe;&Z|9RNo;QV;{x6H`Mu@2j<#LIs#H{4yWD1MQtSkU2vsNi&5T^ z2RD#|DC)dp2wK?#U7w>deXQd!45Z+Mk^}4y_8vAhlgeF~v8aU!GV%U;s^}O&*9{jw zOJN?X2P9{_oN$26)34{V&e=l%%qtXQ??DLH*})2n$00}Es3C6%?OE%=vRy?A@quMI$;EOMqp7GzJ8|>mybi+VwtG0ob~irLq5)3oVNNb% z0h57|HRM}LP9IyBV{_Sv>vX9#s`Y%OGd8~OF+5h2^Uy!$&MC}X7mKfl!R*~hk zv##Y}Wk`pV;2XOWotE}-yO(X4*c`Ci(SbH&KHC(KeF#!XZA+8W8#|wrj@wM}T9*AtQ6 z{#pK&-0I4_`^a~c)I6{3L^#5&(Tw3h*K`STJSUJB8BeC9GLqxNLeuNu<2zRU@-J*N zb)Q9#s7Ef>By7;hFeL7u18*{-&{Ml?ehp4j&K1gnUxDAqr>m8F_dZj&8#g5rr)ZMG z$Y#&TQdVc)J3?=nMNT@oyKwqL9+GFo#us<+<>t1m#-vxpV)E`h&%7LLiI36hs$ddX zeS8SBw&luYIZRY`@*gWF3VLoCd%t&TO;op>Hv$^#hy3g`hGW4|S^Vbl?zJ3qi0SI5 zJ9_n=XZi-l%w|bT$B_*dALo9QQz$zO@qlX|_7I80TJYnmt5g_;4NKj&{N5fcGWCwB zTG>tt$QS{eI^sNuIbX_3*dB?c$q6IRa_iY_#}kY-DTdbIza0YJanO=`O9wp9#_Xid z{Sc%n83i{6mHO}KO%$!P;d{s6QRt~QHPHE+Fr2?`^X(6>(|)>`d77~)Iez>x8Qo<) zCw~lZBYBejR{s7f@~vie`wX1Rg#fGQ{ie-V{M>z{*C>sD-xK+c>bTNfz&3Tt;3j{0 zN+L!(QbqIBx|SBNaiP4Q#NV}SIj`;KZ1Z@&9s3D5@Av5yJT~?JqD80GPSmuy-DmK>@EiuO%_@hzRkgb+a5ek|Z-?hI zan8fO*_Yf_tbs__MJ0D+MoAy2Dig+DVB?C3aeQgW;|Rj#lL9uXF=3l}GIo3pC~x1a z<0no$0(w>becB~=Zp9&)*PBOm$P~jT{jmU)_BaD z0{dgf(WY)DL!st|9r_;X1SI6}jCH})I6$T{heS@WkB{wtImNm)WB?84 zvOMZJ4Z97-?7_^Sif{odX)U(!)!k~YJD`BWiWYysEpULN?OoJyOB?Dv6YzbT z2%a!b(+^lk+*}0F1jE<_;oR8FHW*-{rxp(43)<8i={9kuV3g^#X~qFDOcRm%U|f%- z>k7fxBPNC&u)+T{6v+6y@LEHDz>E|gvQ(tm_tcxWmoIAa|=?Z$2*H#+pG#6%ywtlvz+8+w7&IvzX zk>dRlqVrZ2pAG?sz+<_NGqCplPRmf*&*<(_3Xvj1CUy?-8L$syaGxtf$?N?atgzj^ zG#MIxY{9)wQ#r`sSgk?*hidBIzToMbkaPvN#LBZK!Ttv-r=WW2Kx zdb>A{p0blCu(8y4AF-NpRh)Ktq)p?oIlJGD)(zQ>&wTtVPKjCRsvOBKB^?ZRUh>V8 zCOv*Tl&}?RlhsB)8>;J6zX8Wq;kg&%JsZ$KwQF3B#e9YcT|WrT|8Vq9{7<@h+m`z& z;wi%P!6f2cveCWZugpsCmH20`Lvw`5HU$v=>Q{deza?(>-%8h47``mZ?l(ua(1Drf zw{iOGk6tSq=<3M?K|_g0U1fGWyn^8cyv zC(W#(!YL3q2|ME>TAItzQvJj}N2)llX(x=YW*)sR_83AT6?4Sc6$ENTHp@<~>M4QET?+`G}=``oc1EwlmB7JR9rz>i?k<;4hd^Xn@>1H*Hg4bibSz z&3_9U5u~1S<+)T^>wEn#V9Ae+T0!P!{f;dozu|b$-eKR7lHL~6cRz(azm9WfA0e+P z841dR$6@?k`ujIko{wzj_Faf=PLFHJ7bGW#&1u%E=({{Bf9W3QytM4?mh5OaGar|d zH>NfH0jCA4L+M{Q0o3M-^`okYntC0D#?(8ImH$bT=e5@a>ZfRzO(k>`3`SB)+{$5k zPjkC*{hjnoZ;MVy$36! zUI_6#-GZhCAh5s0Lc};+9mBG7`_Ighm{MaaS%PlNKL2m@)Q9AANZ_Y>NlC$k`9NW+ zx4}EV^GZ)NbDrLR=*7|ayE$=+YAPN9nWQoN@>;TeU*M*y(T*1OLk5| zr_mg*SwQ$swefVL3;m8Bg36}*>*Q%H@t@J#e!87G?sSv2@@}lP4a{aqCN;p*54ct9 z&?L#95M0n`=d6FfdQ8P1Nf~v9U3bm;cHGkb)*2eq{@Dxc3eiwqFF`H(>uz5^V2l75 zC*SL3`N`aK4*MwBJ5djWb`|0J|JRGxc{NmD0C)sN|KK*1Eo<+iy%i@~z4g4bzSJb>Alm+vbTB46;$bdklWoxs;;pE(x!p(EwNe0&aXq6*#6x~LGlKD4Yx2+(u=Oi zu8jY&8Lz~LqwB)aco;X`a&+hgrCx9@6DPGEQuPZfCi%R$#T0D|XcJS#`E64odZ~RD z{ZmtO2g9xcv$a)K85&Py zF!w=8>6+NiSh+}hM4hg8ju(agHz~_UXVG=D;det=GOOQhrU##wY?} z_k(5|+(HI6l!xJ{vi2$D8-KR%Yu%sYAig$hT@COuvZ&-&OxqGVd~o#@z-jG)5UKBY@{{43!N~kJ68wkDqouwtBbmP zSxxi>m|2xCa~qY+nsFGc3N2-aCP7TSoKJ^Sk79&rDz=rIK3a7m7ZZrY<^>OJaO(*= zjx2hR4DkYPLu>js?!o|GO=-lP`cc_ zH$2x`nZAiz%BFwlH?cFBtVc?j$>kJ*nZe1hdt`-0!rXe;wDC1gF=^`bdYCN#4d>=; zv5}{X^*ftHdR^0+qk#Zi-e(HM56aE;TcDD&O|a)aCq9~4v^s1FZ)F2d70%I$>dy>Q z3*5W&3@RoLg}kxqw1z$5|CXYjDHD^)#dXNjr2oaqYa4}n)x;`SSb4^~{NF~0$G-pv zTBKXVJnBXSUe+L|K;I zi)^DmH=xFR3BRuOn^!fWLYl4?eOfFs@bvite+X&K)^IWZ!yoS*HaJKFreieJd#**K zEM0N%kXzf%zIU1b7>uEDW~LnQ^RBk^vvhv@9+ouv8tE=mg#dO?0}W#gmCknJ8Fe2Y zSR-!I;MP&Mh;dd|RVyuzQiY|0!T+gz6=@bDktb@;VrX4e?vh(g{HSKbK)eYGDT7*V zX)zqu5+@ZR%p^P=n>UHO6#$YJu+f?kj^+}$oo{jiy>G~PNlE$U?FIPMVRs{f+||Rp zsHnqQ-o(-=t`b=NRQ%x(XTbc1uylDJ&2is?!KC?rv#;rA)NPo>Q?@OV(l#N1b%!rg`E1rh_xLW6?3wUfi z7Yo*5s6=2kiE=W0o$PdKB0{$W6ci#On$!s4^D!G}76h3m2tb6S{*hB%B`4+N69|WV z%8hU+0qhXdAc0@b9(k0CS?@bkS{AZOChb(eKMOOAeGakWR#gT^UiL(P4oaJoFuxnP z<723mdC(W#2SNbs5-XG~99JwF0o=vg)W9}i#^8g8hqS`)vW_ceH-hdR%t%q5mWS~B zAN2|)8dOMc2|FbIxM7jSyCFToi|DL}e+N~3%iEv3xn4J`sy|Dv++BPPoJsxoCW`4# zu}C;JM5KB|2Bx<<#EK@6N~i3nK#&>$xYN422iaO?-2>$lXcOxoqADm=ZvqG{VEFrQ z4w(|DV}JtfX{&%I&^A(YRJDS+2Ub71eSG zQG`a85Fy7U?#6qT5Ha7=hYZ<1B37-r4?qcGg3ds~ z_-6$lpw4{=alnWw(x1e3#}@iTjU1s14J=S($Bg#1O_lk(Cc(INxY)yZb*!u4c-JV- zVYEmXi3FF6KU&|xJl(LWpcaayjn;`&^U&CvHb#&f`BKaIk7lZ3-w24-P$dIk@xXSP z*6U5M7ZEeDk}2OoA0%=G087vBE0-7N^s~-Fp$^<(XAL1B16@z{8zP!%^tlsIl#>N%30!z*#XEd-2)072UJ4uGCWNc@r@q7hc@$It1S1%H*%0L z9eEw16x$jcoi&GFMWa%YeA@apj7T#4QkU#QgrO5cCUin#irxSZ1KAW!zm5&^nA=0g z?`4R5LlL>Ufo0;l=Or~r2);%~eh8mMD`2qfyuC1$V0J|McqT-~Oqr?S!s}*4wvOnq zsbbX>GS`&{Af1NsTG!qX{=_S_i!->E2qiAzi6aSaU^C{u`D&&h1_5Yy`VJq(?S^}@ z+W)I~l2k*s`5xXDIWa%5B@!TF8!GO4-us4GFCBnTfjP0(T=3IE($P-{3eq!PfU;LY zQL&|;r^mn^S=LFm+Tp!K0QXgt3PBwAl_MeD#(df1fXtNR16gwq`a!sJ#ihOPy#r2X zkjf0t>}q~#BtnT<*bx=LxoJA#+GTRlU>`UD0j>*;?R(RGBRpm|6#uyYwhG{2z8!Dd zZ^7EupwFF$hv3!qqhBiJQH;Vm>cjAb(+$IA}u7{`+Fl00@GFAm5iORc8tDy31mh4@xciQf>Hp2_qI1* z!W&x8q8Kc9pSe+Z$gCC)?u3No@83aig&eSI<&IL0Rue)8(R+G?^IgnsXEDm}rWGf} ztv5bPU5L~`jsvaj+_3)OB)dX0=@8ra$HS=|?9c--2?HSskr)J%68*iRl_P+hLX$ks zA>IEJndA=~=^)$-c0-eWrtH05JLF4(;k|$UdG`Kv(TaUy3nCg95N#Sp#OrQ9q^eih zi-_MarrjSrFes_vDE{sGBx7aB9Sqz6E|bk7I@lB5k+g_P09HDt%lOoAfh8^s{`QyC z8nl_tF-T)=afA=~ZpljO7ySFgF4b2x+gs1yz|_t7DWfj%b^k^!SS>-b^L$L3*gdX1 zv4t#Ji!Zr#E$y@@JuP)7m08b;AFU5lfL);d7`ohqYPN2gB;tywyY>r0|L=@5aEK>LJEDQWM6Kq zr~B?hPIC;nElz+#za2ZB?W(2E4W;}(1B&VTWMxB~ljjVB^;QrrcbnC=`*=Ns{s5}Ivuw9B-e#~%g7QfO6v%Cciops+5P~8 z7&&bqnfdV2!XQ!oQgH;#jXFip$)Y8)nt(n(@)nPdNBQbfdo?YFc01%3U^2rd5hN+d zPb#B(PAu^(l*tR{bxaFJyxlk=K11LKv&7J)yKQ(a&C!>Tzf7kgj(aMyzr0;lWe3Tab$I0{h0I_4p_XLVG$G}VJWP=l zhop=#hcq_#d}s{e2#XLH8i~;zUzAF$2%reQiR>#N8ez+$_=H=Ni@r>4%ALnu%R|HO zG*o_n5Y6bKa*k|uoPAI3gOZV~UI28N+Lg{K9U+Y|ssad*PbxA291a1z0YU&5(z(y- z403&WE^R*EhWP@^M6U+s)*fq#$D30e5xDf3boSwo8t?<)5DW@wDxDb8)(e)q``J76R$86X~b0IpDpYGS@*{l5$SSwZAmVnY0FMt0qG?@k`I0B4(`AQ-J_m8pi=w90*%_r^88 z0ke2jf`nvWnYEUgM-{Wd#=i(~@KBdSOV($40F%Oy46}0mxMT#or%jx9C^Xx7|jFZ?Iz^S@oi&npP7-L-k-7p z3E)%BZQYvTuax`Q;iAtkY^#;T7NvunYKhmN=r~az+rL=#f`xLkOT76P?2k>{-3*bI z#0q6#Lec@MZrBIOHqWQ*$d)N<(u;87e_4OWsE0Rwv z=h7e&%rMM0b`oI(FmQn}45F8@YYI2CFJU(rENLemtZ%NqZ?3oXl*4wlmt^1U_1O{7 zlHTc+4+;r^at}|j-?J+~C~oN~11zCpU)q29v(aWLnvdmVAT}Rc*=invPXtgM0eFbsElD_smJK!-xS;E` z|EG@?$nuP47y%L7mj}A?vQa#K~^_ATD27 zH8Jw1s&!4W*EelupwHk{x3s02>mlL@GQx?|3?T1mzbX{c@&>#>xbB8$Fv2Qwvo9j> z!Khwi8>P5tmRYJ*enIBKwOHA_fP~E%S>M)s!?j-6l>1GH8-Ase*OaWL%*U5<6`So@ zCmna*A#G-`Q&$peQLb6=3U+!G!Y#Kj3+_KzGt!+B%V`6iSJ9gA;|E_8S8{`yF^RK# zLdC@AyoT{M|5+`mN9q1)oc6*4PC4-A!iG!R`*a%QGZVbWTxva4#<{X?0jU7#K8ZeT zC&M9i5dr6HBb+?Wp+DA{_52@Kk*@!`mQT3baCxBcfgt9Idr>s#ehEP?&#vKYMEpbL zIZy?%)YL3APU=|ASm4Btd;;@=H!8c6WG(UNez_U!+!-b=C;W~auUqUClsvUY*)B&; z7T2Z9vAd=5JO4sk(1gFI>+Dk1p|zB{v2cB6J8Fm-_TK|;V_@$7NsUmLd0v#!hmWN% zk1^2?iZ3c#Q`3W+6f2)JeO(AyS{}`Izc`6usW1Ww(2~S$B0mU z<4b#$Dt^r;d-I@w%E4j8@@Zq=y&vKht)6iIJRzfTG=gfAT+zqnkt>0_FHxA>otwvl zpLP5#P{>@O?<$`m``vy_h5v2KT!$cIhV`^AAWBi|aKP{XhU@0?*Mq7n$4h6ai7zGU zZWeA6duf*8=*S@FHV_k3-7o72&8HG@k19-mc*JFtQYj!B03d%c$OLyb+427X3?TR zYVd*YrjIPambUnFu5WV4rT{InFWH~_?^}E6!bBb3N&)fE62;OwU;V;(vEd0Eg#(-2 zd%#FS0D&aLAc$?6?O+2fqs+xWI*vLyl6_M}aUQ@TO%RyGX~ z!<&8*jWcxj0^_d$|IoBrzE>yFJ76?C$V}XN_4wu)oqK_pOAc6?ZXuNrFbMn?qlQh% zGoS9wt!O0spy1_#6s{s`TP%~kW=y+(Srgn(bWa&6a&u9g2!)ft^Q8@HINe%Lw%X78 z?2G<>my-pf3p!&$0Z@`yjy6!1xnXh>ES3V$Lkr#`?BC6&M@pt#E6?t*yo!0^i`b@< zKsLJ`sTX3$@4)M#M*8%)JAxgEV{o?usCRPJu=!HCaC3sa3{#XBMS-N;^lN5Yvf>0`ny2?tkIm zq|FD)5xd%Ds`Uw$P~0%9WQp*}w^E=_Fei|gV_EtVBFKksex7}A#M4y%pTF51$*77ds~6!!VQ0AF9}kXk&3ad`^bJUxUMAK}E%t#42b5h+Fjv>Is8ZJ@4~7 z+Qu9(%H(KjQ??&ne|w_O#)i?xMw!wVXJYEm!6DVc_T2SkO-K$=f1r3y;i}5&>iG5NKCBtZN{DiT(Lefy=2+%Nnc532-N5 zCIbQ72INO|a`*I2fq)MNz|a~K*q`7qWXokN3REx0+EcZCXwQ`|?IfXzV?ti@OwY?f zm(cqM;8u&)Qfb?Tn`czim;c^R6Meb78wTGH*xVk~0@BzSm`(~v2!J9|7aAwZ;zkG^ znT-y=s5eo*?k$Eu7>w?{bLX7>j_i{?3F0rZcHx877AgmP{QWj~km}Mh!^ob;`;x~b zvKKq1vcFxQhhxk5@xIl)Mbh$(n8S(NEMrk4GC9w3<}G?4cxaO%UueCJ9dZGkknp0} zMUijpXAz&9Jqp(jgOrvaqylENP5Uwv&KGZK$SxtH25BfZr_Z^#@wt@^)Vx1ZfP@mj z*u|PF_114*#VD^klKN_#`(pgR1adK(s@8WXdaJYy$>3_!@%fiNG+39_Pczb#>zYuu zVq75Ph`xYEF8ie`W57d{*O;)ClR))0m!q>heufk zs8`&Q#p6dy$9MHcTfdf90&*NS{}HKy-L|MRq?=c#>3p1974{urp1*kx%+Ge?U#$*$ z_xp=@yFo6+7$E+BxY-C-Nem2U^ z3;l-;#hh&g)W>0f+%B`Dg$=9~u&=Og7-y?XayGPVf3tGNv!nk`Qc%}ohYn$*Tp4Em zaONj@F70=FxygROP*Xg3nL@3GdKftqs8jylj>Hi30Q`3kd-Q?oVf9Ofdv)Jr|Idp8 zmz|~tfT+5uuhc7Ck|xr%-s}2g3=dbbM!tv8!yA zqX7Y<#$+G&;0LW#&pzcr4Oq+{$fiK>QjvSHn!2>REXxr$myZnr$9chhz@g2R_&vSf z^X(y*1**^t0N&srFH63Qc7bk6_Wwf~hVKXcK%Y6;2nbqyc72MVsD<4kD-x<#fW4|I zQb7~im4H=zN7Vvi6D2@NJVik`vU-!Ub|it}mZ+o}4{y%;$CqM#5(V+ExeC?#=}I*b z99zueF>c;-q0iT{v0#KF;K4Ki(CR$|biuD;-b5!GG)PTlV$-A!3xx1%o@Q$rHbr04h7{}aQ`(L`7rpxF~|M$x8gEa!{men z0dLXW7xBoOH5WuC29d&=5%0&a_bn+Y11QBkaV7n+QgDd${KN@=_flyX@r zzsudqrq*rMmv&>mi`}Rt(diBluFiJFs_Xgnp3_$n3UPIgM8sip8q0br#=H`D(F?%E zxne>q@&^(7BmfWxC35xBG_QHbjUUY4aU8&}R;celSrwEshzSxcY9ABM=p`7U+AW7@ z%`&9ZUY2JvyRd`7KeGl3D;^y2_Y3wnTY@fekhCRpEG9`p1^d&NG*TOX`8Zhm4MdDt zkJj$p7%9~^{dwg8piA+e^~Z`r&}e}!pI^cWvK@8B1yKp8`v9cc_HetoaVmLA?8{Rk zwcrV0TRPSQI~r=~gE6=8DF96CI_ky6q7U&D&F`$){S9#u`SVIy>RQF2eBeooNlVn* zi>$A-tQfy>jP-UqHZi-f4a4rwrt+NFpr9bop1s>pJV%OA*$Cq#>)#;m-ZxK4$c3Z( zHBpV&Cz{CTpU-CBrOn>sWIv8?(|N@0&R4g_i_%&;(UnzOA{NKLM_-TP;N=cD9Y!ad zy;Dm*|CaA6)^lg>xz0Lf%%wGAqz(~JPz^8n9$5Nc)mvl11nXzu8Z=Vu5;R+5BGCM^ zvAUOlPo_1e?UG>byp^`T-w42%e)yfgmLU0nGy#0xV(z2R%@r<;x2!O5Kry)iJyn#& zr%~E{*+I@zJtI=>n2plX?qgwgkAM!wzWhdrbf(BI?n(H(D!qRAA{usf3}y0X@bYBy z_k7UW{oxgl>Mip1`Yq-C1j)+6E5AWxGV$)Fjaz*|Dsj;2^IJKh32COE(#75HRbSrz zi8~qsaaX$EHV#a6T~8Lsx8EQ`QmG5?Ye0y|F&G>r=((oQAvi;7aha_BhMoQhBqZnWRD`q!}tlKc`qoDJtY*C z#@6}2Y4Sc|jRkY_tiU?FBUOo~+4jpEQ-}eZ7uKv#7eXEWn$qUeCZHxg!6s;gw$j!x zSXE$9+Z#_u^ts<27EBcn*mBPPEwq*!v zu~q`qw&gSia}j0F$j&Nc3M3HM6Gu1Cx@4XPPyc-RM+V$FC}CX{QUSyyaUKdBlhBNJ zYkEDVe$vBk;BrAwnj+loLMY&ugjMLffQ_jaAA9T)Hh1E@hAmasFh^gwdR$tLvsn|s( z%?WHxRIRBw0|^#UfRuosQ{9@G0%kA!n%9YW=2bcUqh!oV7XmasvgK|P(hin2=(#Ak z7evp<(@R$tvuF$q%fK6zLnP*Z)E7eemQF#arX?Z+%f}Hn=V!qW@qcKix;h3H-XuCC zkP!ylvWGeq%E9(899ZmmR72@wetOPnt3?Uqyqju(KWM){xI2SAGT=uRT@pFN^A8*V zXGV!*$d=<0Mo;0&B)O*=|D$Z{$}z#ad~c#qDGAEx6HZmI*$8Gzls8AC!$Uk^CRyg}Su+e~GoGJ$txGS#o<(apd_6MM@lLeN8G#uo*l^xV1p=Xhd z$j1?YWIFMUl0jfaTtLPi2qYv3@odOvhWqj3{`8wo4dxo`1+sF({llF>swxxbXz7qz zOz!|1Iw`=J!AF%0~b>PxM(+ z+0tl|2V*SuE}<}JEem>U6x2x>emA7Sx>zPgmAwf{BJ%%W}Wl$I-%& z+@i~~>0>d8CAtsz!uf*9$D&q20&3cJ>9GaK1NH)&JB&_~5d0mqCzI3;qB3Nh75!NH$WtiP%+a-mJF5S0r24^kcQVt4XG;@5FWEDmCAIKfr=jWaJj6( zt&{<{8ex*+(?L`L6+M!@9xQ?3Nd-E$WvPca!3gH*QxJ(stAWKXf@h*P#zaWOtxTM5 zf_KP)d$e!!J>r${`g{E^y>7ADs)X<`*H1EaUjf@RVwyR`zw&`NP^W_e2eawi%Xec_ zdjOH9!L3I{*IRRW`Bls?{UVct-~8HH6m zW#LB3blr_5Ok+qFsbkzpWqrua_r2h$+vmcCS>6Jed0G~JY|vS@m$Fb1xiCnDYTdGc zKyP%N4+=tglr1{*C3z0uU*g@p0VaQG5T%0$9j!sWHf~WZ#WHw_+X?)gC-2(Gktaer zTNsqTMC@Mr(mJ6()J?e=970rl#t>1y zR|=XcMi8Xlzx0zp?-%2TSNWr!jhKKz*bZxtlSxc# zfY&xYUHZHB^v01-uZK{`pP<&5$x#-a+Iz-Fw<1F@a6KVgp-+Ke$iRXtMg{m(vrlao z!#y~_qWlSen{I}$LJm`S_l@@k!}I z*|46p?cXMC4sm8P_E!gb44@Mj^Lw7c(|1ZC=j`6jvD;LjvjZQg}l=_ z5v8?hws$n+k8Mx!Cs$s@@7uv8;Z(Cru=o&^>vghbWrI|CTmBgEZzyN_;#4Hdg4@zi zs|h8#E(S|J(>#H`hi!wi=X@MaICmS`pYc5Xz@Ty0HLbr|4s~O$zK1%pe=cI*Lk&;aW zlMl4uMJ&BpM@?lzmmm4GyH5_fCgs`7rkil;w({Qp#ta+?5CH`kfd~Ox3T>hCn+?>@ z=z4)kGlOG7%Q(ounXATds#lY$lFqzLi!9rBjQL%lk=8MxLx;^X>)kDhRj)E(V)%G0 zFH{~N&gQ&oD@@3bWbjc%{N#_px8b1{K=IV-9Z%~pAcXz)$AL{*J5D|kFM1++{Kjk; z{=FUreN=^_pnGV3jNKuNovrd2-Krq zIP2R{>WX6Tth3%n!u>msp0qgym?U!3{}Y(hcn?*HjtEZvv3&s0Ic^_>2ghoZ!A#d^ zS-vjQqA+%Ssj~jGto7JhS`!TqiQblq861b{Eq9)kFqiINSpSUGshDhDToF z_#nVt?d4{6ELp}wtoG&S4?kliAPvt0%Y-JrWXPL#CBl&4%JNan^(6Ol$A|Fti3TT; z`8kfH8XY|)S-zQ+atO3$G^h|zVxOSX?Mic&!exeqnr!pj-)BJ#Ko0v}u#hJYib!sMEUmrOp#i|MQ9 z;%R(1W$3Pzgx9!2m9+5)@cd?310sitI85K>d}|UqDPgukgvGLc(HjIP?c$Gu3CcnWm0)O^aQk`!?RR15wTBvRVVcG~1F5#eV7$=#--a zt$v^%;nj|eLA%L#bc$@(zqIMt;?+T3jJND7*#r^5hcpW|rCKotf6-uEj4f+OEYxvT zoqn^PLlDfv+o8PhQhrmAnM)syW{jq>VeToYfXbq<89w`)ssD2I6!P3vM z06h&X^$zTMdwfpihVP-Qfcb-*M4>XN`}O`;oTII?Jjs29fs-L8_J7S5_NvXPabx<3 zB^oNnfLL`9rVu~SB(o7%g+Kefn3Z)g4nzjTBf|r`JAji;OKQ%3D;FS;4-qMB$QO)% zB2Wxu2B#wv4G;F0yjX)U#F?p7dJm7~GY=fI`Qik%j?vr0qL6l7?c|*n?Xa=(Q5M^Y zQ-kI0Jq-@c1VKU2QfrSTbDK}GKH>++el2J~4C*NyG>bqA^g(?IBuiaeGDKcIFK+`= zMq74?A+m;HK}$_6qzO^@PN@yO^${r-Kx_Px zYAiKD$y=Z7@$S(d#&6H2N^z{qp{aSt?#_p`S{*i5LLQ0Ap7s7MZGrtc=iURzjidDD zO)NeQ>X9wOeIvpI@bE)3gdOncjTlnGQ5ZHF!49Y2K_T4IE4=)WPh0d`^IA}#yjE+L zP^&ak!aLTGhNBXY0_qV_Oi@4xVnY&b(2r8*EYujZO>-CRBs2c(xsYe=J&NNg3a66Q zX6wmgE}3M~dJ}0p?g#viS%_wF%@VOd|I7iXP$yAi+AOC1)_5@ldbj3^A;3fWgeQ+{ z*yM;fIs`OfqGyG{`-3tt;ehFKp<;e902g-K&*>i{QCwhca<#2CPEjAzrHo5)d08J5 z9mU7o1rOM7*`~WEd(+Rx68NwmE+@z^aGpGu3MfE>f$#UQ2sKAu)4DrTE{J_i5dn}i z3Vc2ZJOS$A`{%`Hx5xlzmVWn^@tj^=cmQLAuv6VBxOkFZs_>L-Gvi7%2Gg+90z?Qn z{#KTB{9vKnf)BJ;;T0}(Oj}Z(YU}plSeKp=BnOP{8k(rCv}390$i}P zFkdYTs#QKRfGhwf0)Nffl0e?xo4a)CW~poMmZPpbD)^U8$R`DFh99NBzakz(KL&}c zkS_)w&$}ja|6!387tR_@Z7;m%($k<9xw*B}Zqj(g1OL`Mt;8FoL{YZZ8}mVCf+mMA zbWR}C2K(vW}`4Ux@qzMU^0WSexOSftUgcq7JGZ_hYMvX*LNRy zNF|!W0HZYd$j62k0G<_4&LEMX4ryBChQ#lYcnLMy))PKl*9Y$|Kgd(~(#NShabor< zT>p%)9ngErg49m`f_?G`6}OS}^kG}a70oCR5TN6W_h`)dW5lfSdkNW*07xWa&Lj?r zuH5)gLR=Y~hK=5Z0rgZdB`rYss?!yGd^k2|Urq@~Gl_ zK^UAAI_(L+Ky(6Q5YksTjR|6O({RT|d2r^Ei1o0wHJ3p9Oq>?5wq$bMpTEp_-AEs= z$?aqDwn=F0gTu6IE@(r^v2p~?2q7mZ6cztLR%Wa`yFQ#03VAUaitebH&D#W^2TTwg ztSVF?0(Ktm()Ota1BVAeo|9xtu%H4j*lh+~=nQh8+@Xb;T{;--??L&P#gJq|ZhRUl zRmlJvDXbyyDUhAK3Vz7{q4p2J)%HKThAfnB(PkbGBJ@xN?B^AUN-LZXu+tCbOe`j6 zD+~rS6U35Nt4GWOpEGSf!}+mF%l|Tz05CeOa15P@GxoiThad(Zi@;x^e`wgcVA;vi zm&U_YP%&)9l#cz~QWG`4luNU>e(J%nnh&7@)F2{Adttzyr0P&Ki)MfQM&|Pn zB68CWR$0jTXx#v;K|}LTH6)r4*6^Ehzt>cjdelHLQyy8JyMxb4nH>i8oykSy&US$^ zXeRyBx@=3g9lbiN(TvIzr8Q8FTLlU6$ka03mub;Wc|uWBpXy(>j4Kcy``PiZ`mlFV zCXA|%^(k>D^HSGkJ!Psq>z6uUoUE_ZNO@jxlG4T<6K zoUR<4NMExBioIyE%HWGcu9XHXyh;Tk!hgxd?PSh1HNQN7)=cw$#8 z`X279n2;5S?G(({2D}OdlqFP8OsMmnF+*P&3?NDtK%nmLx0ez&yN}&@O@s6gS%#;?#E_C!{np7Ts-JDgg?WY8TkccMPnN8AmIbh<#lHb~2Y|=d zr<^cTY+TG$jaEqkVFq`%N`%@s@Wgz5jZON z*~PuT2Mx>g8Y}qnb!G46G;J%<&E|X%lmrO zey^2Urb+VYVA{{dOFY#gLX?6s_!YD;h-+Y^(vEZ##pRGm z+AqgV_Vtx9e4gsSmKy2h-oO)dH`>{ThcZQJ&yLs-D0+o6JjH3?5*dHdn4+PyFMry*Da9fotZ= zH96_j`ixEkcf4But|suWgxqjlx)1UFs@O?!gd=p#Ovu!KeCSWT`(8kbLNknepC!I3 zadD{dgs`nu=q|VKLgu?k-{S;lm^pTlzblSrMBlY9=+pln4z*P`yi$$(48)o}#V1?G zH^!NVK0>sDLVFIwbJiT(nKyiBkeSm&gw_5Nh#wpzx1Z3n*w$;Fyt24vqynB<>^2T{ zzuMGy@UnJDT9+@RGbX<}%PA&DUESrhMX}hoE&u{3N%n~_M`wUGIx1e08I#1*Xke)& zLbssLJ}H&HS9!!WmNQ zb;wPsRWy$us_X9_dIl^f^v>gm5){3u?o!qi_!qQcFj7=bX?%6&ikBOBz&i)T@I+n5 z@=(yc!7E=81yW6Zbi_xDAp^v%}>_w216#7d47(&JZQraH) zq>oqA5;HuqsF*ngg{5Ot=(Bj=jNWSe{`#S!dUieQ@^J*Z7&2Ns<3lX2K3S3bO?jnsD@%&R)?g904WWj6Eo@ePrdZf@7GDw*(55t*tM1{rM+BkfvJqUPB>Uw+Jg>>a z+18L>I_1Ilt~dR#o);_ji^ch>gBtpDPu`N;j#!~2$&5uW0&LK3fEeJa3JnNO-!=$W8rM`0 zqD6BWMs?P~7Ky~^6(-RiTNe^!3gOlV6kCa=(;AnNC#pJ>ncApN@X}t*suEQQ{EG|c zz+uR-w#8&|Fjn82*AAQG=GTZGvCwm~^BJCQfDWLL%IokSvP=e(CY~mFrp^6AVGBm9 zUjLrs!)%>K0h7o1FJh*S9$vfATbwN6;$bOHMo%A@_0@A8JE4NI?>#&G!QA8%ykMrO z&;1x+-zWakyL=NA2JhO?Ya5eols6XIg9P{8VVuBl|$^;U`fv84H@C znD~Tax%-%YYeS$ia@BrQj{*QYs{%9**CQT)ySM~w5563SxM(?Pkn#dOU8ZQ)he7pM zG^6<>d*o_&dAE4A)SOew^>?1ljR$a0_H)oFsFoN5wV$XPY2y}hDve6!n8%($mZ@q| z=B0!&T9GT_0if4kxN@1Jfv6pf#*ymCjbBg3G$TkUnE~spChB6~RM)Hd7U`@!M;Ns{ zdmzk&T;`PjR{Vt&d85Fmu(K~Z!?;`k!|xJT@*drZR!I;89nm9lE##jS1pY9>*LYM) zd?p57x6+Hip8cQtXDUZlbOHP>ALV?7=wD9lqlGgj&G*tbP>4}}-a$eDJV5PItIb$% zB-&!tY`v}xm7ZavI6ZfF7A|%+xAYRIV&BnUQH}S94|((Qtyh}i(ld*UvDGGaGy}Vh zp%j*#fmP*CFv}|Kje+%yNA+=gS)Jl)aM!@0ShP#RNByv6-oTf|$QzXSnXckzxKKm9>Km$=HKudlry~LTz;AD0e792)qQ}LxNI^F*d^jpK?d{qLmOm5 zhuQyr;g^yx1iY3uL9X~BXXa;#+VE0to$daMw@(UrD(|L;yGCe6IYPps7@g9XlED+NTKj&LH__G_`g!o3ZtdK{}SD5WE|x zO;bQf@r;@@K51l56}B<(Vcn3x$r6$fzYT=tm1USx8=K-}d+6u-)jJB_!ejXrgB-iF zlj9=Tij$55ZBRHEZF<<|X=1Khvi@7Uzj7#jg?#>;Jh=L{BdAXg!N#H75Lr5$Wx$c` z)r@i8K6&I+t$VRDFRMK=B(Nk;JMag9K9*?~_0U5sbd!BIL#qj_6?EdKB7EFjVZ0&2 z;A5}QCF735B*L04TtIq;G(=lfa8s>CwDZXlgST;HOWercElc)LEz4+e*IzH@dyTXr z0VQ&YjhEwPm>zTNUWNiSzt#}!rlZ!6sYD#osf(j;6)kd#D?Wh#sDW}L@x^BDQBFz~WPiU*3Nb-l$8_fKBWQrH+2vHegJ+D~Q;^EO1 zZZz+*+;~nEJ&)M-*TvXZMc?rxHne za<4TL_PonPisrwS|4um?pNDq0T7GJ``eXcQOfk7`y$RUGEK!rk8K@YI!sNT)5R3Z< z{2ceJDza4%mI>04>f~&|FB?m~_-(-s1iw#OJ(ASE9O#F;#|I{yFkBP!Hr?D=TxG{p z5cG|FRA-D<>+|JSD)DM+udyyvV>c&+r|-S7Y0=j|*`=S+Dqt9OJT5H!oDuy{Zm84D zP^7QlYF=*>-PqgK{l5>|C7BFg4`}iYz{1{Hy2Xe0muXyK-Pu=b27yXdOWSip_%8Jf zY%;cMqyQ^HRRR93qw5^s6btrh#sv{`wKeg?ps-fG^8%0NkM|$9jUqIn8^krnk&1I+ zdtwUyLGEtA(L-w$dP54d)G27$-5mWqjwkN90DcDFr9Hm+v=2;&GE9W>+8w+$n1eY@zd0e@LHhg_0ZDHE`0h~1HuZ{`yZsyDVl z#-Mts`cEaa8oCP?>i-G`2c;zbH7rKbIxfYB35Wjk;U=K{?%rz?=k(a`t23;ukGsN< zy%T@}uH}}Y zv)7LGm>R z(Ad)d4IT*|DKIH0X$vJ@NrCu=cLJ@2GaSgbx#ND4=Y;yZE=2Li)!|v{58Gt!QhEXJ zW{3b>SC0O%K*21;jz#gSjXjDeKq>s2Oa~2MkUs4d4liQK9bQJqw?ZisL}kZavO8m} zKiRUd)lQ|G^Y5Iml3TI)($?R{{EvC~$=ZB*{BRmNyZ&UE#UDEkW72u3>M2X54vcg? zX=*ExIImReL!)^%(0@0$b2X*lXJ1xq<8Kr98a3mEjHxm%?^dR@7(9}2i4ar7Dz*Ba zYi|JQkwZq=%LM!fmWdHJ4?Z}=`7O6ub`>>{kdlCRecXxY%jP@*gaAi9!Ld9+zyvs1z6yu?9@Ox6!ISuCjc7 z<%v3ctkqG&v3vYOP*bL!x`SrcxC~yd*aZ#y%@ z(=X7ZS||xi>hMOx=v1ma?bboZzuMI*r6e`MOWT`%m{9h@=0gQ849JnLVXnm80NA*h zz+HId$#*Sd)hTi}&VcF2)+Y&J!6JDtSfRCNAMzbV=XtpVS^s->U0|Nph^x5U^4)KS zb9L*Q+Fs|Nk77?hLyEz%)W>T0=DlR^L|EVp$m^Yi8IxVMX?$i2&PlwaN%^9ws3=Ob zxC5DJiWyOj-6K%Z@7HnY^QFA?@alXdt0q?(N@w?a)S$gyqW9q=4R~3_EowQJx+1GH zDC_qZ+ujVF;E|fL{!MZep9BMNwgJZUQ>{}t1KR`QmP*6mJQ|Z~b8bi>rD$5v_(V@5v~AuquV722{o}|#c7;g3+-064JFy+z6wi7Xw~*z4*gP}wXJK$ zLCF**3GA`knDRlwqGs_SF&;dF*Q)j0m=WMuwC{1~$g#V4TNG}#sGHJd6_HML9)q;Wh0ER%#E}R)ImF<#8sH|RBtBr1< zp`7E`bWHBg7mGl6#SMM2_{hNh#syNwSQec+w&|n+9jQ-hbZPZu=aROlY)^5o`j+|> zwV1HnPR$BLf&A3w$)PwSYSLo?ZET@pso&=}q63dzl^oY8H)DxlZD#*t{5b{WufD2e zq^o?FWB{k@hP$ul&LF4(O%~d(EA4q-N2u50xE)1I*|}omH?8#djmv8hG5g9jVIkIL zx!KE+?RY!~wB+O3ZEo}$Y9%VxGk6;Qp#%{3IJ+I-SWUvKij3-8U9!Na)Njs)?g=Mj zEZo490%kx+$dfu@nZXtG$ZHL!T4$Q5ILj{%55~OOAcx4uF3AhH)Q0Ldue@!}k+{AnA8uctN%Z{g=C=u~DuQp7D>kfIPWRS)1Yt>h);^HI&}j1vjt{-W)i zu|h7m$S+^3JS|xFe<=wtZjbG)uo1K!Q@VmWN!3ky%_z>b{t5#QvjtsiU%Mc@3_j}5 z(e7-_{?6~xGK0{HQ7z_v?Fe@Pz0}SpNTV4XlIBwtoMz%BZcM-ezC{ok+EgpaAcbTD-=R&f4^RdJGY%D7y>i&{s6?dG`@Az!iA&pqTB3Wyf) zpPFUplRSVZQ4>&5LmAOW)H4MK1(@a0Q%}k&l)t(#yq-^Hitx`STgN+!+fPTy{~4Du z8CUPNp?^g_SU)|D*q=kRp#_IoH@FH8WjsclRYx*!kAA859{<>K7$9gtlw4AhZ*ZLL zI&i9hE(OOi6q6U*8*b7~*IB(=j9iWpryQEIyrgUP`?J6r4C!n**Qoq?8b8>(D$?hyD3;a60#Td2R;_7rZBy5I z`{u*UfRU*e>4~^802VD5EX;5BT_=nc@toj(d1gE;?eb!{%MOr#@=d{z5+SvkpivwO zW@$wAx?!hZOwMWDU?-mN_D_#K%y=TS29bl9s??FcjavuhO0Fg0mz^<-Ug2=&mqrk3 zZc6$g%;R^%i?gE+MRfSDG_Lvj_1huLzA_nU4jMRCq#9}K?J1#JnI_}&lhlM!rx&5- zY6~!er!xS1LXU$<9?j-pHjt|qtu<3t16n8^fjZl^n*lG%Zz&NcT?S4pvaBC9hhB+= z>iiSp(U9H-U;qJ(5kV?Ot}c0$31AYw_B2MQfrW-Nf|>+i4)BiuYQmb;KN5^6M%`_s zD}cT}&`qjRoM`QPWv|fdv+XG6DEd&$ zdg!a52{xy`kk>@3VY~UL<^9j^I1kL`1vjXSi|Z|6T>;fQ!EOPWdPxUBO$%toZDJY0 zB635f94%ZTL*!TId7B#rbQ6UxGR0XPlVy+S$4YA@o1-v=_{{zP1{#Jl=6X;wG9C?`G-an)WnCUa-q-Q0{m?Gx&UzC`4=q?J zt}?6J-Pg-pdfg*7hdV~h^r;G`67`8n9!oEpzi<|aIv}*hmxiIU(8y_I(6DN&eM)tc z7g)-2<}Ni2jPb2}wL8kB6&A(ppI-WsT`_SQgj;ahiad79#_p@zQJn>*y(50jk|z`l zP@~j|o9x;4yFJHJFI>{n4g|$wvP!po*4GeQIja^^S?9%H8pg(^okw|g z5sI_hR_l~t-_e_J`CKH{PQ2JWm3uAnuHo&FDwx`<%iy<>pdQz-VO%(`My7jZJ(roz zSiE4fGyx)r3E^zr9CUh>&1Y%9f!OJw}#cOHIK z>YiFssjIkX2SQ~(^I&#(0)`lz%p?f(V_C&(J*M+=tuuEDu&6+<03?B~Cm4qr7MV{< zPAUxEV%Db$sgT`Ur#_SfUD>jqpMQyq^}a@sF(F}~0tPiNJ*+%LB_eu{mBz6^>B%qs zc4QQPX63;w++XT`k}J`>{#)v;cf zJlG>-@-T{46IaltV!(ybaR086Gjfj7S$o3u4d=TS*guy3c<+k{7sfUUJO}3q)2zY? zO(I_XmM=u4R1;o+VdzC=)x@>l6-KM?Q_ilob>UAwqup*gT@i|80Q@R-*1?F)_f!-| zAJyvV?5!}h_3W%Ze_A?(b=*UdMFdq~DgAX>e@Aw?Hw%9fun09UsQo`@66?cL&<8CV z6*A*jhqe_QRxXzGd{}|7t3**H4P}?<>M0;=Cc+OC=I!BYF>+iQ**oRs`z9n>tWmbb ztgz<#ULi&OB}36g*cS@@nfyH9d*7(Y?7@5Ep*yQ&dI1@R5!cQ|L!`gp4 z=80YSFu^FYe8w;ZcCmmPXr?zMonp|65(C$M;O1Dsa^nUX+5UEk?PB#MUCm+3x|3p* z%dBB3H9e_@?z8&tIEXu)Jb>%H`CJ)`9JI6qC|Ob*!4dV#uL`^~|CH7jLu*!=;fgYRq3YkL(SE&6D!Mv; zljc661$O9c)`7H66%wr;DEA<*w=5=_zgBoqI}N9^@iDv2 z2n2w=X8J;?7&#(j480`CC;)6Rvh_*xgWuSPCcYPUO5N&oq_F7N=BAjC!#ox>U4%@P z8(o<2bEac&b<|!%?Gv-N4(T7923BnuGhnr21Z8xF(jr+0w)A(T=-vWC8meIiFcB=X z-5J{wTcgWAH2q<8%%2lh>S@ zAl}kaf$ADbb_U!a7|f#jP3|cy^O@Gsj6jIwqcasQ&%MnLd1Ra?NW$&227AMwD2WN8 zOl?Q$U_JDPHA!ZQl<1-HsnJ+Vr#}dY0t!x0z#(3ws{;ebjdyx`W z!#u>1yhk>NZ-uF4%_Qe!YXR~m>#{9xBWz^0GU|+fq);xbCRpnFc9mzF>S18~0g47( zEgP@7Mn0i0KuaW|+Mvh%1O=_oO`L4ek^%pkON)bCHnbXG9P8<(FEz~6K;#visLAIe z|B7((6E5n|$RvD==MV0{X=P-gHTn)oK{A(#9_)7=bm4&QZ3im2(@N@wHxCaqvS+AJ z3SB=!A%JU#O!M4H^3brAu%X9GK<=&(BsBaU0QuuA0(ekbwGdKPUzx^ZZVf<@3_3;p=^-ac>o)iH+>1?Vb9w1$}eUKG%xaoKQMIB3-rc-91X% z85G&;cXaW3$)lfeH44*64j7?|L+)yFxhuz_WMjwc)(GsG>1rgP7iT4JvSl#MyCPni z*WpgJhpbD6eVpMv8BTQeN4TTN%_~2)Tm}eWlDicM(jzHi|HWIZeg}UuJ&r-Sr9kql zm#Uz7B1%A6!l?Nzio8N=SSvn4#$=lYvM@6%kJPzl^4Pq}mNQ}3!Sw1d6*athDy9{I zBe+Q~!Li#+&-Z9#z$&yPdEt<5_{44y*S#-JS0FXE?&zQEm@jXYfBLvJvbiTnnt-Z! zM1&8`Y_J7XiWEIMg&OHbi!)l|V;j~!wOn$$isxBtKXl(6d1unH;{p!3V4JB53{217 ztSXEtU!2B(5_56|)vEWk$&J2Ykw~e1e!>8wymn9?%pXup{HH%#hC=^$ z{IX7Op+hM7TaukDWPgH54Pv(g?xREVNqwY+beg#*8gh~LP2or@eO%8G9g38al!e;T zx=UK)S!42Mi4QrRxm&xnK#L4kQET(37I{yhS#-N;V%pj2;58K^n32$~+uLQ)$fifx zgO}5b_;BY`DH{|J^RqR?o&B{#GZp%83i_u&7KZO2gjnZ-mGHZu+>-8Gxk@dj&|4Vc zonkhCS!&%}hJRE>>VJMHk$9@(sk%<CGVq^<`n0Kn;B3j-l)ri1L}ra1oTukh~~ zJ3QWt(x|#Ka0fp8c{nk?6pE&yRc7u}=sf9xeC-gs!qs87nA9$O_DC~zPf_<2TRm8> zRzaJ=>Ht#tHv{a4s@LwE`o7T3d2uhY=II6|PjLO3ZKJ-S5mHvq!k!7gZ<3*WS2!yd#@ z1Y@4mQAt{TZNB2cnKJRC&Q`#)dzx_&7c1e6ls1S*X?SNHZ7XI6iUOXA!~#A!pj5-U z6_&!oXJ0HzG7vAvsDNWBCX$JnucgF=Y_8noR7w=~MNE<>)#ImPx^j`3feiV;07rZ(#*nSw_a#OTfpm0%T?v^w<{ z^>?!5o>ad%zHH1bgaP6x9oHjARQ?%pz>=&SUodK-m_loddl(|bY&x*)xe!E3tL-7Hd+;k?kM?2bTmnM#=c( zHB97V$eX6v3;7g1F)O#M^U;dA`rO8LWw82uK8sT%yLnGW;45POtvrba9V(q}|0kU? z1Xxs~Y$TpXnLtrOO}y-;&v9LaJis72%W~_(~_K<^@$pnazBcWxN z+P%~cGMzZ$B}7d*m3KgS3#y7kQ_4GJ1H9Go(`v&RWQQZ1rzTF|wSm_pl(L7BsbnUK zhelH0&wrqtW4aqn0srX<)!DbDsI7YWP3De_Lna7zEf-%&GwclYPoQS5Kb779`3PF4 zPafG4@=ej~q=9uzvlzf0@P~&kl=0J55C~xOw80*-8TS^_qOX8?b{RtW59sO_@+*Y( z!hf%DQ!Qzvy}Ct2!Ig?-{=)kH^H3ngVs{SAO_Hq%X;tJ!$MGPvHQ9Sqr7Zo;ggrU)@VNZK8IL%*LtOIbrC3cNNki9@4|>lStay zExLwq=s1XF+ozH7d#Xy=Y&^Fj^s3;Ze(`OzxCyACDh!ZNwE14(5`)iH2V$=LHvOzQ z_X``KSRujgXT(}I+0??bgD?$J8!t{+q79y@gw3m}t8cjy;?QnotgjFcRHi1cCkuY@ zHx-Z_hSk+N4)3Vi{(kktU_MA2q-#wH$#u%>kAudXD(7|s{h`Q4{gy$tS#QexA@cBI z8mEA@{%|UJsV8Jvf1i-bhIrTc5?fHS^rtm9fmF^AITA(lfz`)wm`E zasu7=PUhH}BIrq|=Ak2<0nl5gMo}lnS27dm!8R+~X61Eqfsj6Qc^#ix>BKDJiq_9m z`i!;a78grVHhtEoOw=$X7=enYkqzy_gp8{U zabvey@*et8^U>&v4Vh(DCKmUBV4`+(%7e{J2|!`lm#Fy}=tq*ciu;H`p`E+=2?^4; zA2b>d+VsnR0MG4UV8fE$WrDF3%>`q7ScKj`uTy#g{Q#X8tfQilE<%xEG6j`iHF(y! zZSey`jkK!H90gCr-E_5sONHgVz&O5KD1L3}2YAJn4yA&%S*lVnIutQX zmkJguFq??bg68P;;PZxuqdmwkr$P?GmdjI3utqW$60i z1`gvHU)8V`<(f&liyC7NmNHjR>CGWpg1n0ZZUN}om`KsGEs9}{{gJKSxT2W4=EKjy z3T;3pzUQAAmI5C1>$kC}Dg$=7tB9r;&}gUtdfBEOSx2Vn=B9f^W^1qU8ii@-Tin)) z$lGY>PFLhZqfW2U8|Y~Id~P?&cNZmg($2RCO!YIy-nB37U^crk37%>cR-g@A!u)XKpgkvsSH4XMYR)uR;FdMUE_Rs*(4p_BRGX`&pfI(g4@;>O^~CV^YFOODs+)q@mFGu`Hr} ziV^CIZXGtMz?Oj@vG_c>pm+K!xFE~1Z-xW83(O(}XH6Bu2^m5f2K^g{-b7dJhTy$` z5m}36CvQO?6moz%))ZWmimf;*VYH9El7=ED%AIlaokP;b(DoizDCPcZ{F}7i#YOXF z3gLtM5}GwRX%$i>0>&RwE_I{C(kCY`7v;#jmBX}Q%^5WtRL4o!6ptFvvB|~&$k+RG zNn)hw`vfolV+5FJRI~ywpa!7!%>o8KOiA{<76^NlJIC6v@1|i+rtv;BD+rZCO*v{Gi&W~`?W9SP z)*x4ebA@fGhN4e9RWAgC|{$hZd=SzY@i)9Y+3hEW#@T47C9O(d2^p7*}RA>3^!9r zIFJ3$xvc|6`twh9*n;CemE00y#vDZyPPAUY-h2s#z>0lzWY!B`L`8_3v3?Zb9Jo>t z31xY}zPrg_KO45jj*cnC-W%h1!&Ec~5i|%AWr6KdiKk3~oV?*lCi= zR}fb2F%WQSmo^GKOXz|fO#lm1viMxp42U+0Xbk3>_WuL+di{^z2Oug zeVw;T;aeA2;?;JZFb|0n5G#7ob;u+s=2tA$?Dkgls|{Cd1T)kce!7;Z8LIJ|5f*Y~ znq(NP;>VbZcAh1^a^lV|@ME=&oB;SG&GY;vy~=1D6Q0|S(vGqyiAiUqpRMM(e;;i_ zY?0*$iF}$ojec&>#Y_C>i^`-9g750|sVaeLiG^PRdvt-1P$cN|=S1SnE-dC^q5lhO zK54mm@~01ZyKzgwC|ntYjV4LpPtAx{xL9RZ`Wd$RvL)&dMyg{0?6K$3l%!ei_!69r zG1^{Ha1mBm)8=(@?H5>?m&Zw10ac>F8Tb^?*&&D}*ft3IlGh-62BD?rOB8GTEH0O+ z_t)v0k5t2y9lw^2?d?0m4ON-b=AR)12MyE=qfIsS0NnN85qJqlymR6K$$*b=s9LW> zgzEW_5JzkD?e`*F9U!^8pY@wqt|K|v7*JX}tEmm)pd{?9yx zN2wXe=xOBqx;h@;gcXEx(@i*VSjERqJuEQR z8n8%_h>i_~4(`exsQX9-Xx7y$SNeR&6Q3`&^0I10)iPG8Xcz*xsi&oF$UzCqEu4O` z8j9mQ2IO*dKB=t~^yAMoT2?d4eylb_g#{jtEgQ2+?Un1tpi~&5!wy&Wl#jxZM#P1- z6r+_lVGN?8%M4Hf2~=JAea6bqqa+KK){;-MWF7Hh(gN3{0#<3+AHa46>z~1u!`%}2mQvbMH}3?Bp+A1)IdVfF zwbh2dS%1@DGi||lru+x7}1NC z1rmdxsXrz$VTb{b=Cvn2-UC%hHHJEBuH%Nj>VZST7*yO8V!_kq@)8ORP22ie2B6G0 z4FWyccTBzvW(ldL#`e~J(JXgmbe!?vk#vL3vt*U0uUwt3fG;t=j5PBH(QmK#cG8@; zUU4-N4;SBRWoGLicdC9n+_E$$yo$IFhK*NmuL6Zx7OrZI$G36g`W~HKU%t7}VX6dB z@+;FLl~T)%&Yrn!(8XjODO}~n95m$s26&5njoViZq~a*1AtqAv9&yH8{)xhx|uaBiNe6byAf*A4mtz+nQ zf#Ouui$}Ns;53?Bg9`177#iW33iwOT&TkGIeJm` z`iR1Nyq^Ejc4yxlHOD=x6rYddAv>CUf4nuFrSDwjt*c|4AT_Fqvu>bS!eSqSM_5HI z8rsJ+%1wL6q6S81WhC$;wrS=lT#QB`rjrCamVGl6g5mAG{e2B>_X+yh?UQEA0NfA{ zq;ciRlcJ*OT*$*j#yIm3D)zgHZhzBVPMH42?RmpyP4*o|jx-5~WN0C9-O+W}zDc15 zMsS{&Uet^fS6HIA#9da3hY`u<3BQUt1;fqhc67PPs9ovZ>F8>M)tN*jJQO0mEo$nu zv`Wsbv76RX-IPTtty{vZ^Dd?_XTdfK02@x?^|0w- z8}1F$74ry9XUwOqleM0og3Jm?ZtFNLTM3)+h>|Xwi2+ zQ2*{e=AxRdQL%_f^xF=5(VYl(L1e=~AI|Q8)G=s6it+)f4w%s(%*;z9*S2lJD;)dS z+Z`xsP0=kl`L#w&i0|4*{=@(lm|dWHNavWhEgJb^<%H(2Mg$4E6L(wIwCAzmQquh) zG_`vI#W{GGCR~D+Y@_6LIsu*-@sE<95LHM|Fdwn>@F7pEgJ@Dsz}}0;AU& z!xb|O*H%(gr7TKQG-?OlI7*4L&4ShVAkQf?TE8bOxdKYIZb{-*oOavDGP^9HO5)3-& z54$f7_6YPmya}J&89qwg!5;5SMx|dZ9Dl zGVkQ3qd7*=?G^oMDyirCbLkrf2MH->KyBuEW*9zT`@(Q+ga8UB9c&;6a#?0KJ-F~9 z{yP8%XE69n@9b;dzKqcn)fQXSEZN=K=6r=I7Nn(%+HeM0Gud`*$m2b{f>Jn( z#1oFMOI9EYVdjO-q{Ytl$j2B|oSn%uTf^eWozDT=L8&4*VgB**)h?H^Yn?cSYX;{}*9 zMMHBGB~%I$Z1!$rHrl#rU(3NCqLGt}%?1UEmBq_qmkBj-nm8$&_?cmetfw-9;Hp;Hqa|b1GI#BIcEN#EUp;dgQ$2 zbo*i;J2A*5I^_JP2M)D1ZD8DzUsB7oft1ZMTH~8Wl`L@~I}7*8u3n~riNJ8+%FjoI zpg$)a@+W1kpGdL~Ha_!lJC5Rhn`K)K3jJ#oQyau#Bw-JR>`m3tXqBIHNeh&X@oRJn z+@eUU{26St{d4cBIek)g@(t+ZH#KStUUI=^*&M0z3CXgdvh=Adkf!IXUGw)TwV8*+ zt*iKL^eu1W?WWY6ITbW6%!C_jZ4mlc3N2!CbmLAyS2Hc&v4dJ;#o>`ve!cy*RWjE} zycBqoXotiFv*a2Qmc>pO&0WLWp}>`~S^F zLB;^Dl9?if_0>VjXd#e+-;jN^dso=37MdO**u(C=zE)#e>srCXJmF463H%@aE&^f$ zEei(7v#74p5ou3HfVuDp_31uh#Ufm-wCrN`7zM|R%CvKmKZ^OdGl3Mb19OQdF+;sc zgy*bkT}?+oP+1YW-`cv}O?I_BPmj(%j?rX0aw{{Nio|jVomnsFkBZ>1;Ft?4C{kvO z54nHqjHjJmi_DF+1pO>lKJb8<%lsX3!2V~O7iL~N8UW2Mc|L7^^@|6NXgZ7)JgTYo zlpiV?kyO@V%#S8iX2LJoQ^6ufoeO}ZULy|UYGvX=^(MwkHYtE; zie)Lo#P$$Usa!^zUWrUtR%ifv`)#0-wo-}$HCwE@mv3SY>TlGKRo1Cu8_x9Uvy&h| zkZ}c6zTB7~PJfU>hSm!q{G2P!!(`>Wm%a=B1R7zMF)ha8(@EYsGb@M)h{1b28l>cT zB?b#(9T&TIM}O^zC_gwL(aWLg37TdS4K=rkge}HRqrkl$Gq<;4_i&XD5vCP*fB^Eo zuH9`nd0nBo+87iw&f@n!pXj*DV{tY^ z_yEo2m5Xf?n}dZ3Og(Z))R$-J&3vZcnNKv|5smrwcnz9tze(#WpLd?((4vdV)}w{Q z3yHUmq0>$yrIll32jZvvv&tgknalaw`G}xsn2P95nxH)=-J}j*T-f-4>Wc|m5B)lS zzLaq(vB@r<2NS!Zi-qJCY~o1Et-NtCuTyillv9lq8yo}k#knp8L-Zl`(Q8e&E9U-D z)tZin=b!tP6S|?k-n!mcGt)b&RJ^gepjK^}8}fDd=A@gJ-}abSwIp<5$REC=&u*07 zdtnp1rSXjO-eq|qKXoJ>uq-P$srDlYRv;T$0vVt96;z1io`yqbap_BByf=!>TM(${ zHos``9co6_51>k^$Iqf(2>}X`l~3XvYG4xtF#cU@$5n8*?D2LR%hK@DOymXQT58q1 z8kiIPkG8XzTMdm`cL~JXX(2WFc5Av~w}h6A!#l>ll>x$rB?{mGosDC>(IP!Xwq-eH6(p`~ znlFDTnWB(c53L|2K%3C4dTyKi3}@>XdiYQ#AnE@#?r9k6v$v|cDp1?p=Wh<=0~Ye{ zjpsuLyQd?OT-N)*>vy~Ii-nyit_@gBMaQ;sV2cAc z@OxBwiXY&~_krwFwlpW}-tw#l`Ek7e&E1^mi4Xi^1K?dXR3D|)G>_~M;geW=c>?TWqLR67ElK#cRQ#&c&>Ku)#i#F!#w)egu(!w5)oMl5r+t{h6B2^L&kMm`*D<#U?S zi%2g*zm&7T`!kq~0U7H%@d80-60Ch=&;7?Skt*t*yiou=l0h)7?a7kvkW}zBN2GgM zq&IRruJ%bvo@&Y8;T>cVFOm@C7(G8P7je{L;@&sN54pti-g?!l@duJIPM*U70#Ntc zB!R5L*FC?!%r?ouLvVpvF4*IMvZEJIh5rSVF!`4Sc110ZTdR2#eCj2~Op^;L zxw%?%3PGih*-qWQWgcJS71}~~XA)AvRjNkT#WAF$3|G~mzXEl01`hxX+>JQEG`3>r z9lOq)a&|jRO6^F+g+lL%GYW>uGAcUCe05gdZ-qD!*_Bp3!mcC)s-Z)s{vnw@inAjP z6szqnCNSsRPKz`JVxTWd=>An`vm&Vwua3MUE(3g|f{gOQdHiv{bpRC;qFdNe$)%!cQ_W3zNo9&@5igZvRa7sl#rv5e z_`-5?$vWw=?Tq6z{am_xD#c{h5)lA9`c^%-jB*sK zT67HcGl-JU;1|l~CKbT{jkj0IwRXT-T04OIN8?i!vd&N3SyEU6Q0?t5>D|A#0a4;vbh1*_1J=g$w z&X9F{8w!Ua9p7V>AO_IS+R;gza|gCq3H7R@ z_yXEj2`7TBf-iXe+ssy&Jmn7Y#18Gu;!vcNgoxl2$RXoea6;`8lQ=n-Qv?-0Rs|2^ zQ=)5=q!m4~&FEUg?^_-Ds^WQ6F>uScexDBshr(Lh-C3Odpcu>!vFKq7o>7(sI4s*N7Geh9iY zhTB*5wQH$zRlG~1D6X>y2xT|0X}Y`j>!~>J90)?}AfE36F*p040NcIy_75PHcS59m zmbxy2$7@A2;+`gA8L5N`^w{h81@yUqKKEGj3LbM<5Btl(!eoj+cGqdu)Ho(!P|rKk z+XZBRE^jvtbhx(AOGJ=KN)ZPTBleY}m>)3~iKs6(P-b!n(o zyPVB&V@v}FPqXoHh@eDV?5SqC0D}Cz05F2v;>He)N3Wg(;FOh>ydjT|@*m-P+&ZUHQ^Kpp`H!Z=vk z0a8jm>RNN9;(ZJYVYK*MXKm2{(!g3Cra{Ynar<-tT_XW$^au9#Q%4r^ z>4){wIvY8-Sb~}390}QYbqD}QrsU;=TnZ3C*oX!1GcoC_yhD}p@YTxA5#-9?9_2LB z&=CWKW&B^U>9EWQa9`NIoZjI*i)nM9knQN*k(`#WW8w}b{t-jO9k|fQ>s1lu}DuX@w5pZxDLeul+JAU^2JLh%4VWk}@hCIng$I4GU*O#PhSn<4h}4`$^yFBcMuii)CVFfi27 zw$Y!?rHoIGk6Xq}<}nJ-z4z^+!f1N0oFAtw1n&x-v<;7?}0$f%z*pdQj{4=rUo z1aF!_%+~p!^CAQGRe6ooi+!!(6uC%?n}2H8dfkkE5r(etU}(WMJEUN6o;0-&_GNrq z3QJIN{6A)jLqo;yF6lXLV9H<}HB6KK&8>WKL*^FFWZSolRd$$4P`T8yBLP6L!?U+1 zc^@^#gzt)_MOIPzlo2-$uS2`fL_B-U!J*S-f8=x|xxOj8ij_FsI5* zSB^sU-K}pW6y9ch1`mdJ0b8Ss@ESoa%9Ip90W<@5AiFppw}qvX^yb@2^y+7(eea5z z@2Hlin!q0>;mk!;lN#nbrmZV$l^~zZQrK37t!eMHaY9=8T)W_`&+dD@xDYSF5jQL;uplmC*L zzH=pp0s*vV9}le7u7;6C!sHR+>wT}Nsw9XWKR1Wq+(N?H!qV^|xHdR(lh+SznjtJR z$TD@G_v?BIIu~@*dLB!}J>k+1E0+`C=X<%i{uH$|0;?IBvlL$w(v5yC8tme9m)G6X zt_eVErPfNSw&~lhfxSk@d=nnqgcoE5sUMSr=Yp9TKPNqd+FWQ6I2+cDXMZb7g?LvC zEp$Eom1-)dS`p!aiVgo#O0@R z&Mep1_;c!lfgZi`z5XTqK>z>@|B%7R7z5xBz?0N3ypmOdb)=jVVMvHm5@1g?o-K<5 j2!^xij=fBMKhMRYPy#B8Q{}Zg|Ha&qP81{{t54Q|)AB3| literal 0 HcmV?d00001 diff --git a/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md b/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md new file mode 100644 index 000000000000..854034d5a8f1 --- /dev/null +++ b/worlds/mmbn3/docs/en_MegaMan Battle Network 3.md @@ -0,0 +1,74 @@ +# MegaMan Battle Network 3 + +## Where is the settings page? + +The [player settings page for this game](../player-settings) contains all the options you need to configure and +export a config file. + +## What does randomization do to this game? + +Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game is +always able to be completed, but because of the item shuffle, the player may need to access certain areas before they +would in the vanilla game. + +The game begins in "Open Mode", in a story state just before leaving for the WWW Base. All story progress leading up to +that point has been auto completed. You will be given a Style Change upon starting, but will +have default MegaMan equipped, you can switch to your new style if you want through the Navi menu. + +Higsby's Chip Trader has been replaced with a shop interface where you can pay 500z to access the next item in the +trader sequence. +Dialog has been rewritten for both the Trader and the hat kid next to the trader to explain it. + +The Nut-Wafer Chocolate Stand in the Yoka Metro has been replaced with a system for trading BugFrags for items. +Dialog has been rewritten to give it a story reason for accepting BugFrags. + +The Secret Area is unlocked from the start, since the Library is filled automatically. Jacking Out has been enabled +in the Secret Area. + +## What is the goal of MegaMan Battle Network 3 when randomized? + +Defeat Alpha on the WWW Base. You will not be able to access the Island until you have acquired the item `GigFreez`, +which will be the eight item in the `progressive-undernet` item sequence. You will need to acquire Undernet Ranks +10, 9, 8, 7, 3, 2, and 1 before acquiring `GigFreez` +(Note: The skipping of 6-4 is intentional. They do not exist in the base game.) + +## What items and locations get shuffled? + +Locations in which items can be found: +- All Blue and Purple Mystery Data. +- The rewards from all available Jobs (Note: The four "Tora" jobs are story progress, and therefore have +been already completed). +- All overworld item pick ups, including Trades and Quizzes with NPCs +- 31 Items from the Numberman Lottery Trader (which have been changed to require Zenny instead of lotto numbers) +- 32 Items from the Nut-Wafer Chocolate stand in Yoka Metro Station (which have been changed to require BugFrags +instead of Zenny) + +Items that are shuffled: +- All of the original rewards from above (Note: Certain common chips and low amounts of Zenny have been classified +as "filler" and might be replaced with progression items) +- All four Cybermetro passes, normally obtained through story progression +- Eight Progressive Undernet Ranks, normally obtained through story progression +- Several chips required for specific Jobs or Trades that would normally be unobtainable without RNG +- The NaviCust "Press" program, normally obtained through story progression +- Two ExpMems and ModTools for the Navi Customizer, normally obtained through story progression +- Higsby's `OrderSys`, which will enable access to the Chip Order System + +## What items are _not_ randomized? +Certain Key Items are kept in their original locations: +- All four of the ID Keys in the WWW-Comp machines on the WWW Base +- Items in Undernet 7 locked behind post-game progression gates, such as beating Serenade or gathering all chips + +## 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 Mega Man Battle Network 3? + +Item pickups all retain their original appearance. Text Boxes for accessing an item or given in dialog will mention +what item and what player is receiving the item + +## When the player receives an item, what happens? + +Whenever you have an item pending, the next time you are not in a battle, menu, or dialog box, you will receive a +message on screen notifying you of the item and sender, and the item will be added directly to your inventory. diff --git a/worlds/mmbn3/docs/setup_en.md b/worlds/mmbn3/docs/setup_en.md new file mode 100644 index 000000000000..309c07f5cfc4 --- /dev/null +++ b/worlds/mmbn3/docs/setup_en.md @@ -0,0 +1,79 @@ +# Setup Guide for MegaMan Battle Network 3 Archipelago + +## Important + +As we are using Bizhawk, this guide is only applicable to Windows and Linux systems. + +## Required Software + +- Bizhawk: [Bizhawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory) + - Version 2.7.0 and later are supported. + - Detailed installation instructions for Bizhawk can be found at the above link. + - Windows users must run the prereq installer first, which can also be found at the above link. +- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases) + (select `MegaMan Battle Network 3 Client` during installation). +- A US MegaMan Battle Network 3 Blue Rom + +## Configuring Bizhawk + +Once Bizhawk has been installed, open Bizhawk and change the following settings: + +- Go to Config > Customize. Switch to the Advanced tab, then switch the Lua Core from "NLua+KopiLua" to + "Lua+LuaInterface". This is required for the Lua script to function correctly. + **NOTE: Even if "Lua+LuaInterface" is already selected, toggle between the two options and reselect it. Fresh installs** + **of newer versions of Bizhawk have a tendency to show "Lua+LuaInterface" as the default selected option but still load** + **"NLua+KopiLua" until this step is done.** +- Under Config > Customize > Advanced, make sure the box for AutoSaveRAM is checked, and click the 5s button. + This reduces the possibility of losing save data in emulator crashes. +- Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to + continue playing in the background, even if another window is selected, such as the Client. +- Under Config > Hotkeys, many hotkeys are listed, with many bound to common keys on the keyboard. You will likely want + to disable most of these, which you can do quickly using `Esc`. + +It is strongly recommended to associate GBA rom extensions (\*.gba) to the Bizhawk we've just installed. +To do so, we simply have to search any GBA rom we happened to own, right click and select "Open with...", unfold +the list that appears and select the bottom option "Look for another application", then browse to the Bizhawk folder +and select EmuHawk.exe. + +## Configuring your YAML file + +### What is a YAML file and why do I need one? + +Your YAML file contains a set of configuration options which provide the generator with information about how it should +generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy +an experience customized for their taste, and different players in the same multiworld can all have different options. + +### Where do I get a YAML file? + +You can customize your settings by visiting the +[MegaMan Battle Network 3 Player Settings Page](/games/MegaMan%20Battle%20Network%203/player-settings) + +## Joining a MultiWorld Game + +### Obtain your GBA patch file + +When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done, +the host will provide you with either a link to download your data file, or with a zip file containing everyone's data +files. Your data file should have a `.apbn3` extension. + +Double-click on your `.apbn3` file to start your client and start the ROM patch process. Once the process is finished +(this can take a while), the client and the emulator will be started automatically (if you associated the extension +to the emulator as recommended). + +### Connect to the Multiserver + +Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools" +menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script. + +Navigate to your Archipelago install folder and open `data/lua/connector_mmbn3.lua`. + +To connect the client to the multiserver simply put `
:` on the textfield on top and press enter (if the +server uses password, type in the bottom textfield `/connect
: [password]`) + +Don't forget to start manipulating RNG early by shouting during generation: + +``` +JACK IN! +[Your name]! +EXECUTE! +``` \ No newline at end of file diff --git a/worlds/mmbn3/lz10.py b/worlds/mmbn3/lz10.py new file mode 100644 index 000000000000..82a3f5c90841 --- /dev/null +++ b/worlds/mmbn3/lz10.py @@ -0,0 +1,257 @@ +from collections import defaultdict +from operator import itemgetter +from struct import pack, unpack + +""" +Tweaked version of nlzss modified to work with raw data and return bytes instead of operating on whole files. +LZ11 functionality has been removed since it is not necessary for MMBN3 + +https://github.com/magical/nlzss +""" + +def gba_decompress(data: bytearray): + """Decompress LZSS-compressed bytes. Returns a bytearray.""" + header = data[:4] + if header[0] == 0x10: + decompress_raw = decompress_raw_lzss10 + else: + raise DecompressionError("not as lzss-compressed file") + + decompressed_size, = unpack("B", packflags(flags))) + + for t in tokens: + if type(t) == tuple: + count, disp = t + count -= 3 + disp = (-disp) - 1 + assert 0 <= disp < 4096 + sh = (count << 12) | disp + byteOut.extend(pack(">H", sh)) + else: + byteOut.extend(pack(">B", t)) + + length += 1 + length += sum(2 if f else 1 for f in flags) + + # padding + padding = 4 - (length % 4 or 4) + if padding: + byteOut.extend(b'\xff' * padding) + return byteOut + + +class SlidingWindow: + # The size of the sliding window + size = 4096 + + # The minimum displacement. + disp_min = 2 + + # The hard minimum — a disp less than this can't be represented in the + # compressed stream. + disp_start = 1 + + # The minimum length for a successful match in the window + match_min = 3 + + # The maximum length of a successful match, inclusive. + match_max = 3 + 0xf + + def __init__(self, buf): + self.data = buf + self.hash = defaultdict(list) + self.full = False + + self.start = 0 + self.stop = 0 + #self.index = self.disp_min - 1 + self.index = 0 + + assert self.match_max is not None + + def next(self): + if self.index < self.disp_start - 1: + self.index += 1 + return + + if self.full: + olditem = self.data[self.start] + assert self.hash[olditem][0] == self.start + self.hash[olditem].pop(0) + + item = self.data[self.stop] + self.hash[item].append(self.stop) + self.stop += 1 + self.index += 1 + + if self.full: + self.start += 1 + else: + if self.size <= self.stop: + self.full = True + + def advance(self, n=1): + """Advance the window by n bytes""" + for _ in range(n): + self.next() + + def search(self): + match_max = self.match_max + match_min = self.match_min + + counts = [] + indices = self.hash[self.data[self.index]] + for i in indices: + matchlen = self.match(i, self.index) + if matchlen >= match_min: + disp = self.index - i + if self.disp_min <= disp: + counts.append((matchlen, -disp)) + if matchlen >= match_max: + return counts[-1] + + if counts: + match = max(counts, key=itemgetter(0)) + return match + + return None + + def match(self, start, bufstart): + size = self.index - start + + if size == 0: + return 0 + + matchlen = 0 + it = range(min(len(self.data) - bufstart, self.match_max)) + for i in it: + if self.data[start + (i % size)] == self.data[bufstart + i]: + matchlen += 1 + else: + break + return matchlen + + +def _compress(input, windowclass=SlidingWindow): + """Generates a stream of tokens. Either a byte (int) or a tuple of (count, + displacement).""" + + window = windowclass(input) + + i = 0 + while True: + if len(input) <= i: + break + match = window.search() + if match: + yield match + window.advance(match[0]) + i += match[0] + else: + yield input[i] + window.next() + i += 1 + + +def packflags(flags): + n = 0 + for i in range(8): + n <<= 1 + try: + if flags[i]: + n |= 1 + except IndexError: + pass + return n + + +def chunkit(it, n): + buf = [] + for x in it: + buf.append(x) + if n <= len(buf): + yield buf + buf = [] + if buf: + yield buf + + +def bits(byte): + return ((byte >> 7) & 1, + (byte >> 6) & 1, + (byte >> 5) & 1, + (byte >> 4) & 1, + (byte >> 3) & 1, + (byte >> 2) & 1, + (byte >> 1) & 1, + byte & 1) + + +def decompress_raw_lzss10(indata, decompressed_size, _overlay=False): + """Decompress LZSS-compressed bytes. Returns a bytearray.""" + data = bytearray() + + it = iter(indata) + + if _overlay: + disp_extra = 3 + else: + disp_extra = 1 + + def writebyte(b): + data.append(b) + + def readbyte(): + return next(it) + + def readshort(): + # big-endian + a = next(it) + b = next(it) + return (a << 8) | b + + def copybyte(): + data.append(next(it)) + + while len(data) < decompressed_size: + b = readbyte() + flags = bits(b) + for flag in flags: + if flag == 0: + copybyte() + elif flag == 1: + sh = readshort() + count = (sh >> 0xc) + 3 + disp = (sh & 0xfff) + disp_extra + + for _ in range(count): + writebyte(data[-disp]) + else: + raise ValueError(flag) + + if decompressed_size <= len(data): + break + + if len(data) != decompressed_size: + raise DecompressionError("decompressed size does not match the expected size") + + return data + + +class DecompressionError(ValueError): + pass