diff --git a/MMBN3Client.py b/MMBN3Client.py new file mode 100644 index 00000000..d8ee581b --- /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 44715bd4..1acd5651 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 00000000..e584121a --- /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 26123954..c2647c44 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 bd4d10ea..5e289187 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,6 +464,20 @@ begin '.gb'); end; +function AddGBARomPage(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:', + 'GBA ROM files|*.gba|All files|*.*', + '.gba'); +end; + function AddSMSRomPage(name: string): TInputFileWizardPage; begin Result := @@ -458,7 +486,6 @@ begin '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 b266bef1..c3ae2b04 100644 --- a/worlds/LauncherComponents.py +++ b/worlds/LauncherComponents.py @@ -116,6 +116,9 @@ components: List[Component] = [ 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 00000000..ede86782 --- /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 00000000..2e249ce7 --- /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 00000000..fc591033 --- /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 00000000..441bdc59 --- /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 00000000..36060b12 --- /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 00000000..96a01290 --- /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 00000000..1dc58600 --- /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 00000000..e1b7cedd --- /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 00000000..9c6d11fa --- /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 00000000..d3548b4c Binary files /dev/null and b/worlds/mmbn3/data/bn3-ap-patch.bsdiff differ 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 00000000..854034d5 --- /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 00000000..309c07f5 --- /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 00000000..82a3f5c9 --- /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