diff --git a/Launcher.py b/Launcher.py index 809a8937..53032ea2 100644 --- a/Launcher.py +++ b/Launcher.py @@ -126,7 +126,7 @@ components: Iterable[Component] = ( Component('Text Client', 'CommonClient', 'ArchipelagoTextClient'), # SNI Component('SNI Client', 'SNIClient', - file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3')), + file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3')), Component('LttP Adjuster', 'LttPAdjuster'), # Factorio Component('Factorio Client', 'FactorioClient'), diff --git a/Patch.py b/Patch.py index a2f29fda..f90e3766 100644 --- a/Patch.py +++ b/Patch.py @@ -166,13 +166,15 @@ GAME_ALTTP = "A Link to the Past" GAME_SM = "Super Metroid" GAME_SOE = "Secret of Evermore" GAME_SMZ3 = "SMZ3" -supported_games = {"A Link to the Past", "Super Metroid", "Secret of Evermore", "SMZ3"} +GAME_DKC3 = "Donkey Kong Country 3" +supported_games = {"A Link to the Past", "Super Metroid", "Secret of Evermore", "SMZ3", "Donkey Kong Country 3"} preferred_endings = { GAME_ALTTP: "apbp", GAME_SM: "apm3", GAME_SOE: "apsoe", - GAME_SMZ3: "apsmz" + GAME_SMZ3: "apsmz", + GAME_DKC3: "apdkc3" } @@ -187,6 +189,8 @@ def generate_yaml(patch: bytes, metadata: Optional[dict] = None, game: str = GAM from worlds.alttp.Rom import LTTPJPN10HASH as ALTTPHASH from worlds.sm.Rom import SMJUHASH as SMHASH HASH = ALTTPHASH + SMHASH + elif game == GAME_DKC3: + from worlds.dkc3.Rom import USHASH as HASH else: raise RuntimeError(f"Selected game {game} for base rom not found.") @@ -216,7 +220,10 @@ def create_patch_file(rom_file_to_patch: str, server: str = "", destination: str meta, game) target = destination if destination else os.path.splitext(rom_file_to_patch)[0] + ( - ".apbp" if game == GAME_ALTTP else ".apsmz" if game == GAME_SMZ3 else ".apm3") + ".apbp" if game == GAME_ALTTP + else ".apsmz" if game == GAME_SMZ3 + else ".apdkc3" if game == GAME_DKC3 + else ".apm3") write_lzma(bytes, target) return target @@ -245,6 +252,8 @@ def get_base_rom_data(game: str): get_base_rom_bytes = lambda: bytes(read_rom(open(get_base_rom_path(), "rb"))) elif game == GAME_SMZ3: from worlds.smz3.Rom import get_base_rom_bytes + elif game == GAME_DKC3: + from worlds.dkc3.Rom import get_base_rom_bytes else: raise RuntimeError("Selected game for base rom not found.") return get_base_rom_bytes() @@ -389,6 +398,13 @@ if __name__ == "__main__": if 'server' in data: Utils.persistent_store("servers", data['hash'], data['server']) print(f"Host is {data['server']}") + elif rom.endswith(".apdkc3"): + print(f"Applying patch {rom}") + data, target = create_rom_file(rom) + print(f"Created rom {target}.") + if 'server' in data: + Utils.persistent_store("servers", data['hash'], data['server']) + print(f"Host is {data['server']}") elif rom.endswith(".zip"): print(f"Updating host in patch files contained in {rom}") @@ -396,7 +412,9 @@ if __name__ == "__main__": def _handle_zip_file_entry(zfinfo: zipfile.ZipInfo, server: str): data = zfr.read(zfinfo) - if zfinfo.filename.endswith(".apbp") or zfinfo.filename.endswith(".apm3"): + if zfinfo.filename.endswith(".apbp") or \ + zfinfo.filename.endswith(".apm3") or \ + zfinfo.filename.endswith(".apdkc3"): data = update_patch_data(data, server) with ziplock: zfw.writestr(zfinfo, data) diff --git a/README.md b/README.md index 2b9cde40..a3a06d48 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ Currently, the following games are supported: * The Witness * Sonic Adventure 2: Battle * Starcraft 2: Wings of Liberty +* Donkey Kong Country 3 +* Dark Souls 3 For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/). Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled diff --git a/SNIClient.py b/SNIClient.py index 151a68da..072d04cc 100644 --- a/SNIClient.py +++ b/SNIClient.py @@ -33,7 +33,7 @@ from worlds.sm.Rom import ROM_PLAYER_LIMIT as SM_ROM_PLAYER_LIMIT from worlds.smz3.Rom import ROM_PLAYER_LIMIT as SMZ3_ROM_PLAYER_LIMIT import Utils from CommonClient import CommonContext, server_loop, ClientCommandProcessor, gui_enabled, get_base_parser -from Patch import GAME_ALTTP, GAME_SM, GAME_SMZ3 +from Patch import GAME_ALTTP, GAME_SM, GAME_SMZ3, GAME_DKC3 snes_logger = logging.getLogger("SNES") @@ -251,6 +251,9 @@ async def deathlink_kill_player(ctx: Context): if not gamemode or gamemode[0] in SM_DEATH_MODES or ( ctx.death_link_allow_survive and health is not None and health > 0): ctx.death_state = DeathState.dead + elif ctx.game == GAME_DKC3: + from worlds.dkc3.Client import deathlink_kill_player as dkc3_deathlink_kill_player + await dkc3_deathlink_kill_player(ctx) ctx.last_death_link = time.time() @@ -1034,44 +1037,48 @@ async def game_watcher(ctx: Context): if not ctx.rom: ctx.finished_game = False ctx.death_link_allow_survive = False - game_name = await snes_read(ctx, SM_ROMNAME_START, 5) - if game_name is None: - continue - elif game_name[:2] == b"SM": - ctx.game = GAME_SM - # versions lower than 0.3.0 dont have item handling flag nor remote item support - romVersion = int(game_name[2:5].decode('UTF-8')) - if romVersion < 30: - ctx.items_handling = 0b001 # full local - else: - item_handling = await snes_read(ctx, SM_REMOTE_ITEM_FLAG_ADDR, 1) - ctx.items_handling = 0b001 if item_handling is None else item_handling[0] - else: - game_name = await snes_read(ctx, SMZ3_ROMNAME_START, 3) - if game_name == b"ZSM": - ctx.game = GAME_SMZ3 - ctx.items_handling = 0b101 # local items and remote start inventory - else: - ctx.game = GAME_ALTTP - ctx.items_handling = 0b001 # full local - rom = await snes_read(ctx, SM_ROMNAME_START if ctx.game == GAME_SM else SMZ3_ROMNAME_START if ctx.game == GAME_SMZ3 else ROMNAME_START, ROMNAME_SIZE) - if rom is None or rom == bytes([0] * ROMNAME_SIZE): - continue + from worlds.dkc3.Client import dkc3_rom_init + init_handled = await dkc3_rom_init(ctx) + if not init_handled: + game_name = await snes_read(ctx, SM_ROMNAME_START, 5) + if game_name is None: + continue + elif game_name[:2] == b"SM": + ctx.game = GAME_SM + # versions lower than 0.3.0 dont have item handling flag nor remote item support + romVersion = int(game_name[2:5].decode('UTF-8')) + if romVersion < 30: + ctx.items_handling = 0b001 # full local + else: + item_handling = await snes_read(ctx, SM_REMOTE_ITEM_FLAG_ADDR, 1) + ctx.items_handling = 0b001 if item_handling is None else item_handling[0] + else: + game_name = await snes_read(ctx, SMZ3_ROMNAME_START, 3) + if game_name == b"ZSM": + ctx.game = GAME_SMZ3 + ctx.items_handling = 0b101 # local items and remote start inventory + else: + ctx.game = GAME_ALTTP + ctx.items_handling = 0b001 # full local - ctx.rom = rom - if ctx.game != GAME_SMZ3: - death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR if ctx.game == GAME_ALTTP else - SM_DEATH_LINK_ACTIVE_ADDR, 1) - if death_link: - ctx.allow_collect = bool(death_link[0] & 0b100) - ctx.death_link_allow_survive = bool(death_link[0] & 0b10) - await ctx.update_death_link(bool(death_link[0] & 0b1)) - if not ctx.prev_rom or ctx.prev_rom != ctx.rom: - ctx.locations_checked = set() - ctx.locations_scouted = set() - ctx.locations_info = {} - ctx.prev_rom = ctx.rom + rom = await snes_read(ctx, SM_ROMNAME_START if ctx.game == GAME_SM else SMZ3_ROMNAME_START if ctx.game == GAME_SMZ3 else ROMNAME_START, ROMNAME_SIZE) + if rom is None or rom == bytes([0] * ROMNAME_SIZE): + continue + + ctx.rom = rom + if ctx.game != GAME_SMZ3: + death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR if ctx.game == GAME_ALTTP else + SM_DEATH_LINK_ACTIVE_ADDR, 1) + if death_link: + ctx.allow_collect = bool(death_link[0] & 0b100) + ctx.death_link_allow_survive = bool(death_link[0] & 0b10) + await ctx.update_death_link(bool(death_link[0] & 0b1)) + if not ctx.prev_rom or ctx.prev_rom != ctx.rom: + ctx.locations_checked = set() + ctx.locations_scouted = set() + ctx.locations_info = {} + ctx.prev_rom = ctx.rom if ctx.awaiting_rom: await ctx.server_auth(False) @@ -1279,6 +1286,9 @@ async def game_watcher(ctx: Context): color(ctx.item_names[item.item], 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'), ctx.location_names[item.location], itemOutPtr, len(ctx.items_received))) await snes_flush_writes(ctx) + elif ctx.game == GAME_DKC3: + from worlds.dkc3.Client import dkc3_game_watcher + await dkc3_game_watcher(ctx) async def run_game(romfile): diff --git a/host.yaml b/host.yaml index af16a825..86f88de0 100644 --- a/host.yaml +++ b/host.yaml @@ -127,3 +127,12 @@ smz3_options: # True for operating system default program # Alternatively, a path to a program to open the .sfc file with rom_start: true +dkc3_options: + # File name of the DKC3 US rom + rom_file: "Donkey Kong Country 3 - Dixie Kong's Double Trouble! (USA) (En,Fr).sfc" + # Set this to your SNI folder location if you want the MultiClient to attempt an auto start, does nothing if not found + sni: "SNI" + # Set this to false to never autostart a rom (such as after patching) + # True for operating system default program + # Alternatively, a path to a program to open the .sfc file with + rom_start: true diff --git a/inno_setup.iss b/inno_setup.iss index 1005cada..ff2da121 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -54,6 +54,7 @@ Name: "custom"; Description: "Custom installation"; Flags: iscustom Name: "core"; Description: "Core Files"; Types: full hosting playing custom; Flags: fixed Name: "generator"; Description: "Generator"; Types: full hosting Name: "generator/sm"; Description: "Super Metroid ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning +Name: "generator/dkc3"; Description: "Donkey Kong Country 3 ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning Name: "generator/soe"; Description: "Secret of Evermore ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning Name: "generator/lttp"; Description: "A Link to the Past ROM Setup and Enemizer"; Types: full hosting; ExtraDiskSpaceRequired: 5191680 Name: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 100663296; Flags: disablenouninstallwarning @@ -62,6 +63,7 @@ Name: "client"; Description: "Clients"; Types: full playing Name: "client/sni"; Description: "SNI Client"; Types: full playing Name: "client/sni/lttp"; Description: "SNI Client - A Link to the Past Patch Setup"; Types: full playing; Flags: disablenouninstallwarning Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup"; Types: full playing; Flags: disablenouninstallwarning +Name: "client/sni/dkc3"; Description: "SNI Client - Donkey Kong Country 3 Patch Setup"; Types: full playing; Flags: disablenouninstallwarning Name: "client/factorio"; Description: "Factorio"; Types: full playing Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278 Name: "client/oot"; Description: "Ocarina of Time"; Types: full playing @@ -76,6 +78,7 @@ NAME: "{app}"; Flags: setntfscompression; Permissions: everyone-modify users-mod [Files] Source: "{code:GetROMPath}"; DestDir: "{app}"; DestName: "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"; Flags: external; Components: client/sni/lttp or generator/lttp Source: "{code:GetSMROMPath}"; DestDir: "{app}"; DestName: "Super Metroid (JU).sfc"; Flags: external; Components: client/sni/sm or generator/sm +Source: "{code:GetDKC3ROMPath}"; DestDir: "{app}"; DestName: "Donkey Kong Country 3 - Dixie Kong's Double Trouble! (USA) (En,Fr).sfc"; Flags: external; Components: client/sni/dkc3 or generator/dkc3 Source: "{code:GetSoEROMPath}"; DestDir: "{app}"; DestName: "Secret of Evermore (USA).sfc"; Flags: external; Components: generator/soe Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: client/oot or generator/oot Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs @@ -143,6 +146,11 @@ Root: HKCR; Subkey: "{#MyAppName}smpatch"; ValueData: "Archi Root: HKCR; Subkey: "{#MyAppName}smpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: "{#MyAppName}smpatch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: ".apdkc3"; ValueData: "{#MyAppName}dkc3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}dkc3patch"; ValueData: "Archipelago Donkey Kong Country 3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}dkc3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni +Root: HKCR; Subkey: "{#MyAppName}dkc3patch\shell\open\command"; ValueData: """{app}\ArchipelagoSNIClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/sni + Root: HKCR; Subkey: ".apsmz3"; ValueData: "{#MyAppName}smz3patch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: "{#MyAppName}smz3patch"; ValueData: "Archipelago SMZ3 Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/sni Root: HKCR; Subkey: "{#MyAppName}smz3patch\DefaultIcon"; ValueData: "{app}\ArchipelagoSNIClient.exe,0"; ValueType: string; ValueName: ""; Components: client/sni @@ -206,6 +214,9 @@ var LttPROMFilePage: TInputFileWizardPage; var smrom: string; var SMRomFilePage: TInputFileWizardPage; +var dkc3rom: string; +var DKC3RomFilePage: TInputFileWizardPage; + var soerom: string; var SoERomFilePage: TInputFileWizardPage; @@ -295,6 +306,8 @@ begin Result := not (LttPROMFilePage.Values[0] = '') else if (assigned(SMROMFilePage)) and (CurPageID = SMROMFilePage.ID) then Result := not (SMROMFilePage.Values[0] = '') + else if (assigned(DKC3ROMFilePage)) and (CurPageID = DKC3ROMFilePage.ID) then + Result := not (DKC3ROMFilePage.Values[0] = '') else if (assigned(SoEROMFilePage)) and (CurPageID = SoEROMFilePage.ID) then Result := not (SoEROMFilePage.Values[0] = '') else if (assigned(OoTROMFilePage)) and (CurPageID = OoTROMFilePage.ID) then @@ -335,6 +348,22 @@ begin Result := ''; end; +function GetDKC3ROMPath(Param: string): string; +begin + if Length(dkc3rom) > 0 then + Result := dkc3rom + else if Assigned(DKC3RomFilePage) then + begin + R := CompareStr(GetSNESMD5OfFile(DKC3ROMFilePage.Values[0]), '120abf304f0c40fe059f6a192ed4f947') + if R <> 0 then + MsgBox('Donkey Kong Country 3 ROM validation failed. Very likely wrong file.', mbInformation, MB_OK); + + Result := DKC3ROMFilePage.Values[0] + end + else + Result := ''; + end; + function GetSoEROMPath(Param: string): string; begin if Length(soerom) > 0 then @@ -379,6 +408,10 @@ begin if Length(smrom) = 0 then SMRomFilePage:= AddRomPage('Super Metroid (JU).sfc'); + dkc3rom := CheckRom('Donkey Kong Country 3 - Dixie Kong''s Double Trouble! (USA) (En,Fr).sfc', '120abf304f0c40fe059f6a192ed4f947'); + if Length(dkc3rom) = 0 then + DKC3RomFilePage:= AddRomPage('Donkey Kong Country 3 - Dixie Kong''s Double Trouble! (USA) (En,Fr).sfc'); + soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a'); if Length(soerom) = 0 then SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc'); @@ -392,6 +425,8 @@ begin Result := not (WizardIsComponentSelected('client/sni/lttp') or WizardIsComponentSelected('generator/lttp')); if (assigned(SMROMFilePage)) and (PageID = SMROMFilePage.ID) then Result := not (WizardIsComponentSelected('client/sni/sm') or WizardIsComponentSelected('generator/sm')); + if (assigned(DKC3ROMFilePage)) and (PageID = DKC3ROMFilePage.ID) then + Result := not (WizardIsComponentSelected('client/sni/dkc3') or WizardIsComponentSelected('generator/dkc3')); if (assigned(SoEROMFilePage)) and (PageID = SoEROMFilePage.ID) then Result := not (WizardIsComponentSelected('generator/soe')); if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then diff --git a/worlds/dkc3/Client.py b/worlds/dkc3/Client.py new file mode 100644 index 00000000..12643a5f --- /dev/null +++ b/worlds/dkc3/Client.py @@ -0,0 +1,220 @@ +import logging +import asyncio + +from NetUtils import ClientStatus, color +from SNIClient import Context, snes_buffered_write, snes_flush_writes, snes_read +from Patch import GAME_DKC3 + +snes_logger = logging.getLogger("SNES") + +# DKC3 - DKC3_TODO: Check these values +ROM_START = 0x000000 +WRAM_START = 0xF50000 +WRAM_SIZE = 0x20000 +SRAM_START = 0xE00000 + +SAVEDATA_START = WRAM_START + 0xF000 +SAVEDATA_SIZE = 0x500 + +DKC3_ROMNAME_START = 0x00FFC0 +DKC3_ROMHASH_START = 0x7FC0 +ROMNAME_SIZE = 0x15 +ROMHASH_SIZE = 0x15 + +DKC3_RECV_PROGRESS_ADDR = WRAM_START + 0x632 # DKC3_TODO: Find a permanent home for this +DKC3_FILE_NAME_ADDR = WRAM_START + 0x5D9 +DEATH_LINK_ACTIVE_ADDR = DKC3_ROMNAME_START + 0x15 # DKC3_TODO: Find a permanent home for this + + +async def deathlink_kill_player(ctx: Context): + pass + #if ctx.game == GAME_DKC3: + # DKC3_TODO: Handle Receiving Deathlink + + +async def dkc3_rom_init(ctx: Context): + if not ctx.rom: + ctx.finished_game = False + ctx.death_link_allow_survive = False + game_name = await snes_read(ctx, DKC3_ROMNAME_START, 0x15) + if game_name is None or game_name != b"DONKEY KONG COUNTRY 3": + return False + else: + ctx.game = GAME_DKC3 + ctx.items_handling = 0b111 # remote items + + rom = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE) + if rom is None or rom == bytes([0] * ROMHASH_SIZE): + return False + + ctx.rom = rom + + #death_link = await snes_read(ctx, DEATH_LINK_ACTIVE_ADDR, 1) + ## DKC3_TODO: Handle Deathlink + #if death_link: + # ctx.allow_collect = bool(death_link[0] & 0b100) + # await ctx.update_death_link(bool(death_link[0] & 0b1)) + return True + + +async def dkc3_game_watcher(ctx: Context): + if ctx.game == GAME_DKC3: + # DKC3_TODO: Handle Deathlink + save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5) + if save_file_name is None or save_file_name[0] == 0x00: + # We haven't loaded a save file + return + + new_checks = [] + from worlds.dkc3.Rom import location_rom_data, item_rom_data + for loc_id, loc_data in location_rom_data.items(): + if loc_id not in ctx.locations_checked: + data = await snes_read(ctx, WRAM_START + loc_data[0], 1) + masked_data = data[0] & (1 << loc_data[1]) + bit_set = (masked_data != 0) + invert_bit = ((len(loc_data) >= 3) and loc_data[2]) + if bit_set != invert_bit: + # DKC3_TODO: Handle non-included checks + new_checks.append(loc_id) + + save_file_name = await snes_read(ctx, DKC3_FILE_NAME_ADDR, 0x5) + if save_file_name is None or save_file_name[0] == 0x00: + # We have somehow exited the save file + return + + rom = await snes_read(ctx, DKC3_ROMHASH_START, ROMHASH_SIZE) + if rom != ctx.rom: + ctx.rom = None + # We have somehow loaded a different ROM + return + + for new_check_id in new_checks: + ctx.locations_checked.add(new_check_id) + location = ctx.location_names[new_check_id] + snes_logger.info( + f'New Check: {location} ({len(ctx.locations_checked)}/{len(ctx.missing_locations) + len(ctx.checked_locations)})') + await ctx.send_msgs([{"cmd": 'LocationChecks', "locations": [new_check_id]}]) + + # DKC3_TODO: Make this actually visually display new things received (ASM Hook required) + recv_count = await snes_read(ctx, DKC3_RECV_PROGRESS_ADDR, 1) + recv_index = recv_count[0] + + if recv_index < len(ctx.items_received): + item = ctx.items_received[recv_index] + recv_index += 1 + logging.info('Received %s from %s (%s) (%d/%d in list)' % ( + color(ctx.item_names[item.item], 'red', 'bold'), + color(ctx.player_names[item.player], 'yellow'), + ctx.location_names[item.location], recv_index, len(ctx.items_received))) + + snes_buffered_write(ctx, DKC3_RECV_PROGRESS_ADDR, bytes([recv_index])) + if item.item in item_rom_data: + item_count = await snes_read(ctx, WRAM_START + item_rom_data[item.item][0], 0x1) + new_item_count = item_count[0] + 1 + for address in item_rom_data[item.item]: + snes_buffered_write(ctx, WRAM_START + address, bytes([new_item_count])) + + # Handle Coin Displays + current_level = await snes_read(ctx, WRAM_START + 0x5E3, 0x5) + if item.item == 0xDC3002 and (current_level[0] == 0x0A and current_level[2] == 0x00 and current_level[4] == 0x03): + # Bazaar and Barter + item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1) + new_item_count = item_count[0] + 1 + snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count])) + elif item.item == 0xDC3002 and current_level[0] == 0x04: + # Swanky + item_count = await snes_read(ctx, WRAM_START + 0xA26, 0x1) + new_item_count = item_count[0] + 1 + snes_buffered_write(ctx, WRAM_START + 0xA26, bytes([new_item_count])) + elif item.item == 0xDC3003 and (current_level[0] == 0x0A and current_level[2] == 0x08 and current_level[4] == 0x01): + # Boomer + item_count = await snes_read(ctx, WRAM_START + 0xB02, 0x1) + new_item_count = item_count[0] + 1 + snes_buffered_write(ctx, WRAM_START + 0xB02, bytes([new_item_count])) + else: + # Handle Patch and Skis + if item.item == 0xDC3007: + num_upgrades = 1 + inventory = await snes_read(ctx, WRAM_START + 0x605, 0xF) + + if (inventory[0] & 0x02): + num_upgrades = 3 + elif (inventory[13] & 0x08) or (inventory[0] & 0x01): + num_upgrades = 2 + + if num_upgrades == 1: + snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x01])) + if inventory[4] == 0: + snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x01])) + elif inventory[6] == 0: + snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x01])) + elif inventory[8] == 0: + snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x01])) + elif inventory[10] == 0: + snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x01])) + + cove_mekanos_progress = await snes_read(ctx, WRAM_START + 0x691, 0x2) + snes_buffered_write(ctx, WRAM_START + 0x691, bytes([cove_mekanos_progress[0] | 0x01])) + snes_buffered_write(ctx, WRAM_START + 0x692, bytes([cove_mekanos_progress[1] | 0x01])) + elif num_upgrades == 2: + snes_buffered_write(ctx, WRAM_START + 0x605, bytes([inventory[0] | 0x02])) + if inventory[4] == 0: + snes_buffered_write(ctx, WRAM_START + 0x609, bytes([0x02])) + elif inventory[6] == 0: + snes_buffered_write(ctx, WRAM_START + 0x60B, bytes([0x02])) + elif inventory[8] == 0: + snes_buffered_write(ctx, WRAM_START + 0x60D, bytes([0x02])) + elif inventory[10] == 0: + snes_buffered_write(ctx, WRAM_START + 0x60F, bytes([0x02])) + elif num_upgrades == 3: + snes_buffered_write(ctx, WRAM_START + 0x606, bytes([inventory[1] | 0x20])) + + k3_ridge_progress = await snes_read(ctx, WRAM_START + 0x693, 0x2) + snes_buffered_write(ctx, WRAM_START + 0x693, bytes([k3_ridge_progress[0] | 0x01])) + snes_buffered_write(ctx, WRAM_START + 0x694, bytes([k3_ridge_progress[1] | 0x01])) + elif item.item == 0xDC3000: + # Handle Victory + if not ctx.finished_game: + await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) + ctx.finished_game = True + else: + print("Item Not Recognized: ", item.item) + pass + + await snes_flush_writes(ctx) + + # DKC3_TODO: This method of collect should work, however it does not unlock the next level correctly when previous is flagged + # Handle Collected Locations + #for loc_id in ctx.checked_locations: + # if loc_id not in ctx.locations_checked: + # loc_data = location_rom_data[loc_id] + # data = await snes_read(ctx, WRAM_START + loc_data[0], 1) + # invert_bit = ((len(loc_data) >= 3) and loc_data[2]) + # if not invert_bit: + # masked_data = data[0] | (1 << loc_data[1]) + # print("Collected Location: ", hex(loc_data[0]), " | ", loc_data[1]) + # snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data])) + # await snes_flush_writes(ctx) + # else: + # masked_data = data[0] & ~(1 << loc_data[1]) + # print("Collected Inverted Location: ", hex(loc_data[0]), " | ", loc_data[1]) + # snes_buffered_write(ctx, WRAM_START + loc_data[0], bytes([masked_data])) + # await snes_flush_writes(ctx) + # ctx.locations_checked.add(loc_id) + + # Calculate Boomer Cost Text + boomer_cost_text = await snes_read(ctx, WRAM_START + 0xAAFD, 2) + if boomer_cost_text[0] == 0x31 and boomer_cost_text[1] == 0x35: + boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1) + boomer_cost_tens = int(boomer_cost[0]) // 10 + boomer_cost_ones = int(boomer_cost[0]) % 10 + snes_buffered_write(ctx, WRAM_START + 0xAAFD, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones])) + await snes_flush_writes(ctx) + + boomer_final_cost_text = await snes_read(ctx, WRAM_START + 0xAB9B, 2) + if boomer_final_cost_text[0] == 0x32 and boomer_final_cost_text[1] == 0x35: + boomer_cost = await snes_read(ctx, ROM_START + 0x349857, 1) + boomer_cost_tens = boomer_cost[0] // 10 + boomer_cost_ones = boomer_cost[0] % 10 + snes_buffered_write(ctx, WRAM_START + 0xAB9B, bytes([0x30 + boomer_cost_tens, 0x30 + boomer_cost_ones])) + await snes_flush_writes(ctx) diff --git a/worlds/dkc3/Items.py b/worlds/dkc3/Items.py new file mode 100644 index 00000000..358873cd --- /dev/null +++ b/worlds/dkc3/Items.py @@ -0,0 +1,52 @@ +import typing + +from BaseClasses import Item, ItemClassification +from .Names import ItemName + + +class ItemData(typing.NamedTuple): + code: typing.Optional[int] + progression: bool + quantity: int = 1 + event: bool = False + + +class DKC3Item(Item): + game: str = "Donkey Kong Country 3" + + +# Separate tables for each type of item. +junk_table = { + ItemName.one_up_balloon: ItemData(0xDC3001, False), + ItemName.bear_coin: ItemData(0xDC3002, False), +} + +collectable_table = { + ItemName.bonus_coin: ItemData(0xDC3003, True), + ItemName.dk_coin: ItemData(0xDC3004, True), + ItemName.banana_bird: ItemData(0xDC3005, True), + ItemName.krematoa_cog: ItemData(0xDC3006, True), + ItemName.progressive_boat: ItemData(0xDC3007, True), +} + +inventory_table = { + ItemName.present: ItemData(0xDC3008, True), + ItemName.bowling_ball: ItemData(0xDC3009, True), + ItemName.shell: ItemData(0xDC300A, True), + ItemName.mirror: ItemData(0xDC300B, True), + ItemName.flower: ItemData(0xDC300C, True), + ItemName.wrench: ItemData(0xDC300D, True), +} + +event_table = { + ItemName.victory: ItemData(0xDC3000, True), +} + +# Complete item table. +item_table = { + **junk_table, + **collectable_table, + **event_table, +} + +lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code} diff --git a/worlds/dkc3/Levels.py b/worlds/dkc3/Levels.py new file mode 100644 index 00000000..c3983ab8 --- /dev/null +++ b/worlds/dkc3/Levels.py @@ -0,0 +1,115 @@ + +from .Names import LocationName + +class DKC3Level(): + nameIDAddress: int + levelIDAddress: int + nameID: int + levelID: int + + def __init__(self, nameIDAddress: int, levelIDAddress: int, nameID: int, levelID: int): + self.nameIDAddress = nameIDAddress + self.levelIDAddress = levelIDAddress + self.nameID = nameID + self.levelID = levelID + + +level_dict = { + LocationName.lakeside_limbo_region: DKC3Level(0x34D19C, 0x34D19D, 0x01, 0x25), + LocationName.doorstop_dash_region: DKC3Level(0x34D1A7, 0x34D1A8, 0x02, 0x28), + LocationName.tidal_trouble_region: DKC3Level(0x34D1BD, 0x34D1BE, 0x04, 0x27), + LocationName.skiddas_row_region: DKC3Level(0x34D1C8, 0x34D1C9, 0x05, 0x2B), + LocationName.murky_mill_region: DKC3Level(0x34D1D3, 0x34D1D4, 0x0D, 0x2A), + + LocationName.barrel_shield_bust_up_region: DKC3Level(0x34D217, 0x34D218, 0x0B, 0x30), + LocationName.riverside_race_region: DKC3Level(0x34D22D, 0x34D22E, 0x0C, 0x32), + LocationName.squeals_on_wheels_region: DKC3Level(0x34D238, 0x34D239, 0x06, 0x29), + LocationName.springin_spiders_region: DKC3Level(0x34D24E, 0x34D24F, 0x0E, 0x2F), + LocationName.bobbing_barrel_brawl_region: DKC3Level(0x34D264, 0x34D265, 0x37, 0x34), + + LocationName.bazzas_blockade_region: DKC3Level(0x34D29D, 0x34D29E, 0x14, 0x35), + LocationName.rocket_barrel_ride_region: DKC3Level(0x34D2A8, 0x34D2A9, 0x15, 0x38), + LocationName.kreeping_klasps_region: DKC3Level(0x34D2BE, 0x34D2BF, 0x16, 0x26), + LocationName.tracker_barrel_trek_region: DKC3Level(0x34D2D4, 0x34D2D5, 0x17, 0x39), + LocationName.fish_food_frenzy_region: DKC3Level(0x34D2DF, 0x34D2E0, 0x18, 0x36), + + LocationName.fire_ball_frenzy_region: DKC3Level(0x34D30D, 0x34D30E, 0x1B, 0x3B), + LocationName.demolition_drain_pipe_region: DKC3Level(0x34D323, 0x34D324, 0x1D, 0x40), + LocationName.ripsaw_rage_region: DKC3Level(0x34D339, 0x34D33A, 0x1E, 0x2E), + LocationName.blazing_bazookas_region: DKC3Level(0x34D34F, 0x34D350, 0x1F, 0x3C), + LocationName.low_g_labyrinth_region: DKC3Level(0x34D35A, 0x34D35B, 0x20, 0x3E), + + LocationName.krevice_kreepers_region: DKC3Level(0x34D388, 0x34D389, 0x23, 0x41), + LocationName.tearaway_toboggan_region: DKC3Level(0x34D393, 0x34D394, 0x24, 0x2D), + LocationName.barrel_drop_bounce_region: DKC3Level(0x34D39E, 0x34D39F, 0x25, 0x3A), + LocationName.krack_shot_kroc_region: DKC3Level(0x34D3A9, 0x34D3AA, 0x26, 0x3D), + LocationName.lemguin_lunge_region: DKC3Level(0x34D3B4, 0x34D3B5, 0x27, 0x2C), + + LocationName.buzzer_barrage_region: DKC3Level(0x34D40E, 0x34D40F, 0x2B, 0x44), + LocationName.kong_fused_cliffs_region: DKC3Level(0x34D424, 0x34D425, 0x2D, 0x42), + LocationName.floodlit_fish_region: DKC3Level(0x34D42F, 0x34D430, 0x2E, 0x37), + LocationName.pothole_panic_region: DKC3Level(0x34D43A, 0x34D43B, 0x2F, 0x45), + LocationName.ropey_rumpus_region: DKC3Level(0x34D450, 0x34D451, 0x30, 0x43), + + LocationName.konveyor_rope_clash_region: DKC3Level(0x34D489, 0x34D48A, 0x38, 0x48), + LocationName.creepy_caverns_region: DKC3Level(0x34D49F, 0x34D4A0, 0x36, 0x46), + LocationName.lightning_lookout_region: DKC3Level(0x34D4AA, 0x34D4AB, 0x10, 0x33), + LocationName.koindozer_klamber_region: DKC3Level(0x34D4C0, 0x34D4C1, 0x34, 0x47), + LocationName.poisonous_pipeline_region: DKC3Level(0x34D4D6, 0x34D4D7, 0x39, 0x3F), + + LocationName.stampede_sprint_region: DKC3Level(0x34D51A, 0x34D51B, 0x3D, 0x49), + LocationName.criss_cross_cliffs_region: DKC3Level(0x34D525, 0x34D526, 0x3E, 0x4A), + LocationName.tyrant_twin_tussle_region: DKC3Level(0x34D530, 0x34D531, 0x3F, 0x4B), + LocationName.swoopy_salvo_region: DKC3Level(0x34D53B, 0x34D53C, 0x40, 0x31), + #LocationName.rocket_rush_region: DKC3Level(0x34D546, 0x34D547, 0x05, 0x4C), # Rocket Rush is not getting shuffled +} + +level_list = [ + LocationName.lakeside_limbo_region, + LocationName.doorstop_dash_region, + LocationName.tidal_trouble_region, + LocationName.skiddas_row_region, + LocationName.murky_mill_region, + + LocationName.barrel_shield_bust_up_region, + LocationName.riverside_race_region, + LocationName.squeals_on_wheels_region, + LocationName.springin_spiders_region, + LocationName.bobbing_barrel_brawl_region, + + LocationName.bazzas_blockade_region, + LocationName.rocket_barrel_ride_region, + LocationName.kreeping_klasps_region, + LocationName.tracker_barrel_trek_region, + LocationName.fish_food_frenzy_region, + + LocationName.fire_ball_frenzy_region, + LocationName.demolition_drain_pipe_region, + LocationName.ripsaw_rage_region, + LocationName.blazing_bazookas_region, + LocationName.low_g_labyrinth_region, + + LocationName.krevice_kreepers_region, + LocationName.tearaway_toboggan_region, + LocationName.barrel_drop_bounce_region, + LocationName.krack_shot_kroc_region, + LocationName.lemguin_lunge_region, + + LocationName.buzzer_barrage_region, + LocationName.kong_fused_cliffs_region, + LocationName.floodlit_fish_region, + LocationName.pothole_panic_region, + LocationName.ropey_rumpus_region, + + LocationName.konveyor_rope_clash_region, + LocationName.creepy_caverns_region, + LocationName.lightning_lookout_region, + LocationName.koindozer_klamber_region, + LocationName.poisonous_pipeline_region, + + LocationName.stampede_sprint_region, + LocationName.criss_cross_cliffs_region, + LocationName.tyrant_twin_tussle_region, + LocationName.swoopy_salvo_region, + #LocationName.rocket_rush_region, +] diff --git a/worlds/dkc3/Locations.py b/worlds/dkc3/Locations.py new file mode 100644 index 00000000..aa8acf72 --- /dev/null +++ b/worlds/dkc3/Locations.py @@ -0,0 +1,283 @@ +import typing + +from BaseClasses import Location +from .Names import LocationName + + +class DKC3Location(Location): + game: str = "Donkey Kong Country 3" + + progress_byte: int = 0x000000 + progress_bit: int = 0 + inverted_bit: bool = False + + def __init__(self, player: int, name: str = '', address: int = None, parent=None, prog_byte: int = None, prog_bit: int = None, invert: bool = False): + super().__init__(player, name, address, parent) + self.progress_byte = prog_byte + self.progress_bit = prog_bit + self.inverted_bit = invert + + +level_location_table = { + LocationName.lakeside_limbo_flag: 0xDC3000, + LocationName.lakeside_limbo_bonus_1: 0xDC3001, + LocationName.lakeside_limbo_bonus_2: 0xDC3002, + LocationName.lakeside_limbo_dk: 0xDC3003, + + LocationName.doorstop_dash_flag: 0xDC3004, + LocationName.doorstop_dash_bonus_1: 0xDC3005, + LocationName.doorstop_dash_bonus_2: 0xDC3006, + LocationName.doorstop_dash_dk: 0xDC3007, + + LocationName.tidal_trouble_flag: 0xDC3008, + LocationName.tidal_trouble_bonus_1: 0xDC3009, + LocationName.tidal_trouble_bonus_2: 0xDC300A, + LocationName.tidal_trouble_dk: 0xDC300B, + + LocationName.skiddas_row_flag: 0xDC300C, + LocationName.skiddas_row_bonus_1: 0xDC300D, + LocationName.skiddas_row_bonus_2: 0xDC300E, + LocationName.skiddas_row_dk: 0xDC300F, + + LocationName.murky_mill_flag: 0xDC3010, + LocationName.murky_mill_bonus_1: 0xDC3011, + LocationName.murky_mill_bonus_2: 0xDC3012, + LocationName.murky_mill_dk: 0xDC3013, + + LocationName.barrel_shield_bust_up_flag: 0xDC3014, + LocationName.barrel_shield_bust_up_bonus_1: 0xDC3015, + LocationName.barrel_shield_bust_up_bonus_2: 0xDC3016, + LocationName.barrel_shield_bust_up_dk: 0xDC3017, + + LocationName.riverside_race_flag: 0xDC3018, + LocationName.riverside_race_bonus_1: 0xDC3019, + LocationName.riverside_race_bonus_2: 0xDC301A, + LocationName.riverside_race_dk: 0xDC301B, + + LocationName.squeals_on_wheels_flag: 0xDC301C, + LocationName.squeals_on_wheels_bonus_1: 0xDC301D, + LocationName.squeals_on_wheels_bonus_2: 0xDC301E, + LocationName.squeals_on_wheels_dk: 0xDC301F, + + LocationName.springin_spiders_flag: 0xDC3020, + LocationName.springin_spiders_bonus_1: 0xDC3021, + LocationName.springin_spiders_bonus_2: 0xDC3022, + LocationName.springin_spiders_dk: 0xDC3023, + + LocationName.bobbing_barrel_brawl_flag: 0xDC3024, + LocationName.bobbing_barrel_brawl_bonus_1: 0xDC3025, + LocationName.bobbing_barrel_brawl_bonus_2: 0xDC3026, + LocationName.bobbing_barrel_brawl_dk: 0xDC3027, + + LocationName.bazzas_blockade_flag: 0xDC3028, + LocationName.bazzas_blockade_bonus_1: 0xDC3029, + LocationName.bazzas_blockade_bonus_2: 0xDC302A, + LocationName.bazzas_blockade_dk: 0xDC302B, + + LocationName.rocket_barrel_ride_flag: 0xDC302C, + LocationName.rocket_barrel_ride_bonus_1: 0xDC302D, + LocationName.rocket_barrel_ride_bonus_2: 0xDC302E, + LocationName.rocket_barrel_ride_dk: 0xDC302F, + + LocationName.kreeping_klasps_flag: 0xDC3030, + LocationName.kreeping_klasps_bonus_1: 0xDC3031, + LocationName.kreeping_klasps_bonus_2: 0xDC3032, + LocationName.kreeping_klasps_dk: 0xDC3033, + + LocationName.tracker_barrel_trek_flag: 0xDC3034, + LocationName.tracker_barrel_trek_bonus_1: 0xDC3035, + LocationName.tracker_barrel_trek_bonus_2: 0xDC3036, + LocationName.tracker_barrel_trek_dk: 0xDC3037, + + LocationName.fish_food_frenzy_flag: 0xDC3038, + LocationName.fish_food_frenzy_bonus_1: 0xDC3039, + LocationName.fish_food_frenzy_bonus_2: 0xDC303A, + LocationName.fish_food_frenzy_dk: 0xDC303B, + + LocationName.fire_ball_frenzy_flag: 0xDC303C, + LocationName.fire_ball_frenzy_bonus_1: 0xDC303D, + LocationName.fire_ball_frenzy_bonus_2: 0xDC303E, + LocationName.fire_ball_frenzy_dk: 0xDC303F, + + LocationName.demolition_drain_pipe_flag: 0xDC3040, + LocationName.demolition_drain_pipe_bonus_1: 0xDC3041, + LocationName.demolition_drain_pipe_bonus_2: 0xDC3042, + LocationName.demolition_drain_pipe_dk: 0xDC3043, + + LocationName.ripsaw_rage_flag: 0xDC3044, + LocationName.ripsaw_rage_bonus_1: 0xDC3045, + LocationName.ripsaw_rage_bonus_2: 0xDC3046, + LocationName.ripsaw_rage_dk: 0xDC3047, + + LocationName.blazing_bazookas_flag: 0xDC3048, + LocationName.blazing_bazookas_bonus_1: 0xDC3049, + LocationName.blazing_bazookas_bonus_2: 0xDC304A, + LocationName.blazing_bazookas_dk: 0xDC304B, + + LocationName.low_g_labyrinth_flag: 0xDC304C, + LocationName.low_g_labyrinth_bonus_1: 0xDC304D, + LocationName.low_g_labyrinth_bonus_2: 0xDC304E, + LocationName.low_g_labyrinth_dk: 0xDC304F, + + LocationName.krevice_kreepers_flag: 0xDC3050, + LocationName.krevice_kreepers_bonus_1: 0xDC3051, + LocationName.krevice_kreepers_bonus_2: 0xDC3052, + LocationName.krevice_kreepers_dk: 0xDC3053, + + LocationName.tearaway_toboggan_flag: 0xDC3054, + LocationName.tearaway_toboggan_bonus_1: 0xDC3055, + LocationName.tearaway_toboggan_bonus_2: 0xDC3056, + LocationName.tearaway_toboggan_dk: 0xDC3057, + + LocationName.barrel_drop_bounce_flag: 0xDC3058, + LocationName.barrel_drop_bounce_bonus_1: 0xDC3059, + LocationName.barrel_drop_bounce_bonus_2: 0xDC305A, + LocationName.barrel_drop_bounce_dk: 0xDC305B, + + LocationName.krack_shot_kroc_flag: 0xDC305C, + LocationName.krack_shot_kroc_bonus_1: 0xDC305D, + LocationName.krack_shot_kroc_bonus_2: 0xDC305E, + LocationName.krack_shot_kroc_dk: 0xDC305F, + + LocationName.lemguin_lunge_flag: 0xDC3060, + LocationName.lemguin_lunge_bonus_1: 0xDC3061, + LocationName.lemguin_lunge_bonus_2: 0xDC3062, + LocationName.lemguin_lunge_dk: 0xDC3063, + + LocationName.buzzer_barrage_flag: 0xDC3064, + LocationName.buzzer_barrage_bonus_1: 0xDC3065, + LocationName.buzzer_barrage_bonus_2: 0xDC3066, + LocationName.buzzer_barrage_dk: 0xDC3067, + + LocationName.kong_fused_cliffs_flag: 0xDC3068, + LocationName.kong_fused_cliffs_bonus_1: 0xDC3069, + LocationName.kong_fused_cliffs_bonus_2: 0xDC306A, + LocationName.kong_fused_cliffs_dk: 0xDC306B, + + LocationName.floodlit_fish_flag: 0xDC306C, + LocationName.floodlit_fish_bonus_1: 0xDC306D, + LocationName.floodlit_fish_bonus_2: 0xDC306E, + LocationName.floodlit_fish_dk: 0xDC306F, + + LocationName.pothole_panic_flag: 0xDC3070, + LocationName.pothole_panic_bonus_1: 0xDC3071, + LocationName.pothole_panic_bonus_2: 0xDC3072, + LocationName.pothole_panic_dk: 0xDC3073, + + LocationName.ropey_rumpus_flag: 0xDC3074, + LocationName.ropey_rumpus_bonus_1: 0xDC3075, + LocationName.ropey_rumpus_bonus_2: 0xDC3076, + LocationName.ropey_rumpus_dk: 0xDC3077, + + LocationName.konveyor_rope_clash_flag: 0xDC3078, + LocationName.konveyor_rope_clash_bonus_1: 0xDC3079, + LocationName.konveyor_rope_clash_bonus_2: 0xDC307A, + LocationName.konveyor_rope_clash_dk: 0xDC307B, + + LocationName.creepy_caverns_flag: 0xDC307C, + LocationName.creepy_caverns_bonus_1: 0xDC307D, + LocationName.creepy_caverns_bonus_2: 0xDC307E, + LocationName.creepy_caverns_dk: 0xDC307F, + + LocationName.lightning_lookout_flag: 0xDC3080, + LocationName.lightning_lookout_bonus_1: 0xDC3081, + LocationName.lightning_lookout_bonus_2: 0xDC3082, + LocationName.lightning_lookout_dk: 0xDC3083, + + LocationName.koindozer_klamber_flag: 0xDC3084, + LocationName.koindozer_klamber_bonus_1: 0xDC3085, + LocationName.koindozer_klamber_bonus_2: 0xDC3086, + LocationName.koindozer_klamber_dk: 0xDC3087, + + LocationName.poisonous_pipeline_flag: 0xDC3088, + LocationName.poisonous_pipeline_bonus_1: 0xDC3089, + LocationName.poisonous_pipeline_bonus_2: 0xDC308A, + LocationName.poisonous_pipeline_dk: 0xDC308B, + + LocationName.stampede_sprint_flag: 0xDC308C, + LocationName.stampede_sprint_bonus_1: 0xDC308D, + LocationName.stampede_sprint_bonus_2: 0xDC308E, + LocationName.stampede_sprint_bonus_3: 0xDC308F, + LocationName.stampede_sprint_dk: 0xDC3090, + + LocationName.criss_cross_cliffs_flag: 0xDC3091, + LocationName.criss_cross_cliffs_bonus_1: 0xDC3092, + LocationName.criss_cross_cliffs_bonus_2: 0xDC3093, + LocationName.criss_cross_cliffs_dk: 0xDC3094, + + LocationName.tyrant_twin_tussle_flag: 0xDC3095, + LocationName.tyrant_twin_tussle_bonus_1: 0xDC3096, + LocationName.tyrant_twin_tussle_bonus_2: 0xDC3097, + LocationName.tyrant_twin_tussle_bonus_3: 0xDC3098, + LocationName.tyrant_twin_tussle_dk: 0xDC3099, + + LocationName.swoopy_salvo_flag: 0xDC309A, + LocationName.swoopy_salvo_bonus_1: 0xDC309B, + LocationName.swoopy_salvo_bonus_2: 0xDC309C, + LocationName.swoopy_salvo_bonus_3: 0xDC309D, + LocationName.swoopy_salvo_dk: 0xDC309E, + + LocationName.rocket_rush_flag: 0xDC309F, + LocationName.rocket_rush_dk: 0xDC30A0, +} + + +boss_location_table = { + LocationName.belchas_barn: 0xDC30A1, + LocationName.arichs_ambush: 0xDC30A2, + LocationName.squirts_showdown: 0xDC30A3, + LocationName.kaos_karnage: 0xDC30A4, + LocationName.bleaks_house: 0xDC30A5, + LocationName.barboss_barrier: 0xDC30A6, + LocationName.kastle_kaos: 0xDC30A7, + LocationName.knautilus: 0xDC30A8, +} + +secret_cave_location_table = { + LocationName.belchas_burrow: 0xDC30A9, + LocationName.kong_cave: 0xDC30AA, + LocationName.undercover_cove: 0xDC30AB, + LocationName.ks_cache: 0xDC30AC, + LocationName.hill_top_hoard: 0xDC30AD, + LocationName.bounty_beach: 0xDC30AE, + LocationName.smugglers_cove: 0xDC30AF, + LocationName.arichs_hoard: 0xDC30B0, + LocationName.bounty_bay: 0xDC30B1, + LocationName.sky_high_secret: 0xDC30B2, + LocationName.glacial_grotto: 0xDC30B3, + LocationName.cifftop_cache: 0xDC30B4, + LocationName.sewer_stockpile: 0xDC30B5, + LocationName.banana_bird_mother: 0xDC30B6, +} + +brothers_bear_location_table = { + LocationName.bazaars_general_store_1: 0xDC30B7, + LocationName.bazaars_general_store_2: 0xDC30B8, + LocationName.brambles_bungalow: 0xDC30B9, + LocationName.flower_spot: 0xDC30BA, + LocationName.barters_swap_shop: 0xDC30BB, + LocationName.barnacles_island: 0xDC30BC, + LocationName.blues_beach_hut: 0xDC30BD, + LocationName.blizzards_basecamp: 0xDC30BE, +} + +all_locations = { + **level_location_table, + **boss_location_table, + **secret_cave_location_table, + **brothers_bear_location_table, +} + +location_table = {} + + +def setup_locations(world, player: int): + location_table = {**level_location_table, **boss_location_table, **secret_cave_location_table} + + if False:#world.include_trade_sequence[player].value: + location_table.update({**brothers_bear_location_table}) + + return location_table + + +lookup_id_to_name: typing.Dict[int, str] = {id: name for name, _ in all_locations.items()} diff --git a/worlds/dkc3/Names/ItemName.py b/worlds/dkc3/Names/ItemName.py new file mode 100644 index 00000000..d3395832 --- /dev/null +++ b/worlds/dkc3/Names/ItemName.py @@ -0,0 +1,21 @@ +# Junk Definitions +one_up_balloon = "1-Up Balloon" +bear_coin = "Bear Coin" + +# Collectable Definitions +bonus_coin = "Bonus Coin" +dk_coin = "DK Coin" +banana_bird = "Banana Bird" +krematoa_cog = "Krematoa Cog" + +# Inventory Definitions +progressive_boat = "Progressive Boat Upgrade" +present = "Present" +bowling_ball = "Bowling Ball" +shell = "Shell" +mirror = "Mirror" +flower = "Flupperius Petallus Pongus" +wrench = "No. 6 Wrench" + +# Other Definitions +victory = "Donkey Kong" diff --git a/worlds/dkc3/Names/LocationName.py b/worlds/dkc3/Names/LocationName.py new file mode 100644 index 00000000..b3aca3b0 --- /dev/null +++ b/worlds/dkc3/Names/LocationName.py @@ -0,0 +1,336 @@ +# Level Definitions +lakeside_limbo_flag = "Lakeside Limbo - Flag" +lakeside_limbo_bonus_1 = "Lakeside Limbo - Bonus 1" +lakeside_limbo_bonus_2 = "Lakeside Limbo - Bonus 2" +lakeside_limbo_dk = "Lakeside Limbo - DK Coin" + +doorstop_dash_flag = "Doorstop Dash - Flag" +doorstop_dash_bonus_1 = "Doorstop Dash - Bonus 1" +doorstop_dash_bonus_2 = "Doorstop Dash - Bonus 2" +doorstop_dash_dk = "Doorstop Dash - DK Coin" + +tidal_trouble_flag = "Tidal Trouble - Flag" +tidal_trouble_bonus_1 = "Tidal Trouble - Bonus 1" +tidal_trouble_bonus_2 = "Tidal Trouble - Bonus 2" +tidal_trouble_dk = "Tidal Trouble - DK Coin" + +skiddas_row_flag = "Skidda's Row - Flag" +skiddas_row_bonus_1 = "Skidda's Row - Bonus 1" +skiddas_row_bonus_2 = "Skidda's Row - Bonus 2" +skiddas_row_dk = "Skidda's Row - DK Coin" + +murky_mill_flag = "Murky Mill - Flag" +murky_mill_bonus_1 = "Murky Mill - Bonus 1" +murky_mill_bonus_2 = "Murky Mill - Bonus 2" +murky_mill_dk = "Murky Mill - DK Coin" + +barrel_shield_bust_up_flag = "Barrel Shield Bust-Up - Flag" +barrel_shield_bust_up_bonus_1 = "Barrel Shield Bust-Up - Bonus 1" +barrel_shield_bust_up_bonus_2 = "Barrel Shield Bust-Up - Bonus 2" +barrel_shield_bust_up_dk = "Barrel Shield Bust-Up - DK Coin" + +riverside_race_flag = "Riverside Race - Flag" +riverside_race_bonus_1 = "Riverside Race - Bonus 1" +riverside_race_bonus_2 = "Riverside Race - Bonus 2" +riverside_race_dk = "Riverside Race - DK Coin" + +squeals_on_wheels_flag = "Squeals On Wheels - Flag" +squeals_on_wheels_bonus_1 = "Squeals On Wheels - Bonus 1" +squeals_on_wheels_bonus_2 = "Squeals On Wheels - Bonus 2" +squeals_on_wheels_dk = "Squeals On Wheels - DK Coin" + +springin_spiders_flag = "Springin' Spiders - Flag" +springin_spiders_bonus_1 = "Springin' Spiders - Bonus 1" +springin_spiders_bonus_2 = "Springin' Spiders - Bonus 2" +springin_spiders_dk = "Springin' Spiders - DK Coin" + +bobbing_barrel_brawl_flag = "Bobbing Barrel Brawl - Flag" +bobbing_barrel_brawl_bonus_1 = "Bobbing Barrel Brawl - Bonus 1" +bobbing_barrel_brawl_bonus_2 = "Bobbing Barrel Brawl - Bonus 2" +bobbing_barrel_brawl_dk = "Bobbing Barrel Brawl - DK Coin" + +bazzas_blockade_flag = "Bazza's Blockade - Flag" +bazzas_blockade_bonus_1 = "Bazza's Blockade - Bonus 1" +bazzas_blockade_bonus_2 = "Bazza's Blockade - Bonus 2" +bazzas_blockade_dk = "Bazza's Blockade - DK Coin" + +rocket_barrel_ride_flag = "Rocket Barrel Ride - Flag" +rocket_barrel_ride_bonus_1 = "Rocket Barrel Ride - Bonus 1" +rocket_barrel_ride_bonus_2 = "Rocket Barrel Ride - Bonus 2" +rocket_barrel_ride_dk = "Rocket Barrel Ride - DK Coin" + +kreeping_klasps_flag = "Kreeping Klasps - Flag" +kreeping_klasps_bonus_1 = "Kreeping Klasps - Bonus 1" +kreeping_klasps_bonus_2 = "Kreeping Klasps - Bonus 2" +kreeping_klasps_dk = "Kreeping Klasps - DK Coin" + +tracker_barrel_trek_flag = "Tracker Barrel Trek - Flag" +tracker_barrel_trek_bonus_1 = "Tracker Barrel Trek - Bonus 1" +tracker_barrel_trek_bonus_2 = "Tracker Barrel Trek - Bonus 2" +tracker_barrel_trek_dk = "Tracker Barrel Trek - DK Coin" + +fish_food_frenzy_flag = "Fish Food Frenzy - Flag" +fish_food_frenzy_bonus_1 = "Fish Food Frenzy - Bonus 1" +fish_food_frenzy_bonus_2 = "Fish Food Frenzy - Bonus 2" +fish_food_frenzy_dk = "Fish Food Frenzy - DK Coin" + +fire_ball_frenzy_flag = "Fire-Ball Frenzy - Flag" +fire_ball_frenzy_bonus_1 = "Fire-Ball Frenzy - Bonus 1" +fire_ball_frenzy_bonus_2 = "Fire-Ball Frenzy - Bonus 2" +fire_ball_frenzy_dk = "Fire-Ball Frenzy - DK Coin" + +demolition_drain_pipe_flag = "Demolition Drain-Pipe - Flag" +demolition_drain_pipe_bonus_1 = "Demolition Drain-Pipe - Bonus 1" +demolition_drain_pipe_bonus_2 = "Demolition Drain-Pipe - Bonus 2" +demolition_drain_pipe_dk = "Demolition Drain-Pipe - DK Coin" + +ripsaw_rage_flag = "Ripsaw Rage - Flag" +ripsaw_rage_bonus_1 = "Ripsaw Rage - Bonus 1" +ripsaw_rage_bonus_2 = "Ripsaw Rage - Bonus 2" +ripsaw_rage_dk = "Ripsaw Rage - DK Coin" + +blazing_bazookas_flag = "Blazing Bazookas - Flag" +blazing_bazookas_bonus_1 = "Blazing Bazookas - Bonus 1" +blazing_bazookas_bonus_2 = "Blazing Bazookas - Bonus 2" +blazing_bazookas_dk = "Blazing Bazookas - DK Coin" + +low_g_labyrinth_flag = "Low-G Labyrinth - Flag" +low_g_labyrinth_bonus_1 = "Low-G Labyrinth - Bonus 1" +low_g_labyrinth_bonus_2 = "Low-G Labyrinth - Bonus 2" +low_g_labyrinth_dk = "Low-G Labyrinth - DK Coin" + +krevice_kreepers_flag = "Krevice Kreepers - Flag" +krevice_kreepers_bonus_1 = "Krevice Kreepers - Bonus 1" +krevice_kreepers_bonus_2 = "Krevice Kreepers - Bonus 2" +krevice_kreepers_dk = "Krevice Kreepers - DK Coin" + +tearaway_toboggan_flag = "Tearaway Toboggan - Flag" +tearaway_toboggan_bonus_1 = "Tearaway Toboggan - Bonus 1" +tearaway_toboggan_bonus_2 = "Tearaway Toboggan - Bonus 2" +tearaway_toboggan_dk = "Tearaway Toboggan - DK Coin" + +barrel_drop_bounce_flag = "Barrel Drop Bounce - Flag" +barrel_drop_bounce_bonus_1 = "Barrel Drop Bounce - Bonus 1" +barrel_drop_bounce_bonus_2 = "Barrel Drop Bounce - Bonus 2" +barrel_drop_bounce_dk = "Barrel Drop Bounce - DK Coin" + +krack_shot_kroc_flag = "Krack-Shot Kroc - Flag" +krack_shot_kroc_bonus_1 = "Krack-Shot Kroc - Bonus 1" +krack_shot_kroc_bonus_2 = "Krack-Shot Kroc - Bonus 2" +krack_shot_kroc_dk = "Krack-Shot Kroc - DK Coin" + +lemguin_lunge_flag = "Lemguin Lunge - Flag" +lemguin_lunge_bonus_1 = "Lemguin Lunge - Bonus 1" +lemguin_lunge_bonus_2 = "Lemguin Lunge - Bonus 2" +lemguin_lunge_dk = "Lemguin Lunge - DK Coin" + +buzzer_barrage_flag = "Buzzer Barrage - Flag" +buzzer_barrage_bonus_1 = "Buzzer Barrage - Bonus 1" +buzzer_barrage_bonus_2 = "Buzzer Barrage - Bonus 2" +buzzer_barrage_dk = "Buzzer Barrage - DK Coin" + +kong_fused_cliffs_flag = "Kong-Fused Cliffs - Flag" +kong_fused_cliffs_bonus_1 = "Kong-Fused Cliffs - Bonus 1" +kong_fused_cliffs_bonus_2 = "Kong-Fused Cliffs - Bonus 2" +kong_fused_cliffs_dk = "Kong-Fused Cliffs - DK Coin" + +floodlit_fish_flag = "Floodlit Fish - Flag" +floodlit_fish_bonus_1 = "Floodlit Fish - Bonus 1" +floodlit_fish_bonus_2 = "Floodlit Fish - Bonus 2" +floodlit_fish_dk = "Floodlit Fish - DK Coin" + +pothole_panic_flag = "Pothole Panic - Flag" +pothole_panic_bonus_1 = "Pothole Panic - Bonus 1" +pothole_panic_bonus_2 = "Pothole Panic - Bonus 2" +pothole_panic_dk = "Pothole Panic - DK Coin" + +ropey_rumpus_flag = "Ropey Rumpus - Flag" +ropey_rumpus_bonus_1 = "Ropey Rumpus - Bonus 1" +ropey_rumpus_bonus_2 = "Ropey Rumpus - Bonus 2" +ropey_rumpus_dk = "Ropey Rumpus - DK Coin" + +konveyor_rope_clash_flag = "Konveyor Rope Klash - Flag" +konveyor_rope_clash_bonus_1 = "Konveyor Rope Klash - Bonus 1" +konveyor_rope_clash_bonus_2 = "Konveyor Rope Klash - Bonus 2" +konveyor_rope_clash_dk = "Konveyor Rope Klash - DK Coin" + +creepy_caverns_flag = "Creepy Caverns - Flag" +creepy_caverns_bonus_1 = "Creepy Caverns - Bonus 1" +creepy_caverns_bonus_2 = "Creepy Caverns - Bonus 2" +creepy_caverns_dk = "Creepy Caverns - DK Coin" + +lightning_lookout_flag = "Lightning Lookout - Flag" +lightning_lookout_bonus_1 = "Lightning Lookout - Bonus 1" +lightning_lookout_bonus_2 = "Lightning Lookout - Bonus 2" +lightning_lookout_dk = "Lightning Lookout - DK Coin" + +koindozer_klamber_flag = "Koindozer Klamber - Flag" +koindozer_klamber_bonus_1 = "Koindozer Klamber - Bonus 1" +koindozer_klamber_bonus_2 = "Koindozer Klamber - Bonus 2" +koindozer_klamber_dk = "Koindozer Klamber - DK Coin" + +poisonous_pipeline_flag = "Poisonous Pipeline - Flag" +poisonous_pipeline_bonus_1 = "Poisonous Pipeline - Bonus 1" +poisonous_pipeline_bonus_2 = "Poisonous Pipeline - Bonus 2" +poisonous_pipeline_dk = "Poisonous Pipeline - DK Coin" + +stampede_sprint_flag = "Stampede Sprint - Flag" +stampede_sprint_bonus_1 = "Stampede Sprint - Bonus 1" +stampede_sprint_bonus_2 = "Stampede Sprint - Bonus 2" +stampede_sprint_bonus_3 = "Stampede Sprint - Bonus 3" +stampede_sprint_dk = "Stampede Sprint - DK Coin" + +criss_cross_cliffs_flag = "Criss Kross Cliffs - Flag" +criss_cross_cliffs_bonus_1 = "Criss Kross Cliffs - Bonus 1" +criss_cross_cliffs_bonus_2 = "Criss Kross Cliffs - Bonus 2" +criss_cross_cliffs_dk = "Criss Kross Cliffs - DK Coin" + +tyrant_twin_tussle_flag = "Tyrant Twin Tussle - Flag" +tyrant_twin_tussle_bonus_1 = "Tyrant Twin Tussle - Bonus 1" +tyrant_twin_tussle_bonus_2 = "Tyrant Twin Tussle - Bonus 2" +tyrant_twin_tussle_bonus_3 = "Tyrant Twin Tussle - Bonus 3" +tyrant_twin_tussle_dk = "Tyrant Twin Tussle - DK Coin" + +swoopy_salvo_flag = "Swoopy Salvo - Flag" +swoopy_salvo_bonus_1 = "Swoopy Salvo - Bonus 1" +swoopy_salvo_bonus_2 = "Swoopy Salvo - Bonus 2" +swoopy_salvo_bonus_3 = "Swoopy Salvo - Bonus 3" +swoopy_salvo_dk = "Swoopy Salvo - DK Coin" + +rocket_rush_flag = "Rocket Rush - Flag" +rocket_rush_dk = "Rocket Rush - DK Coin" + +# Boss Definitions +belchas_barn = "Belcha's Barn" +arichs_ambush = "Arich's Ambush" +squirts_showdown = "Squirt's Showdown" +kaos_karnage = "KAOS Karnage" +bleaks_house = "Bleak's House" +barboss_barrier = "Barbos's Barrier" +kastle_kaos = "Kastle KAOS" +knautilus = "Knautilus" + + +# Banana Bird Cave Definitions +belchas_burrow = "Belcha's Burrow" +kong_cave = "Kong Cave" +undercover_cove = "Undercover Cove" +ks_cache = "K's Cache" +hill_top_hoard = "Hill-Top Hoard" +bounty_beach = "Bounty Beach" +smugglers_cove = "Smuggler's Cove" +arichs_hoard = "Arich's Hoard" +bounty_bay = "Bounty Bay" +sky_high_secret = "Sky-High Secret" +glacial_grotto = "Glacial Grotto" +cifftop_cache = "Clifftop Cache" +sewer_stockpile = "Sewer Stockpile" + +banana_bird_mother = "Banana Bird Mother" + + +# Brothers Bear Definitions +bazaars_general_store_1 = "Bazaar's General Store - 1" +bazaars_general_store_2 = "Bazaar's General Store - 2" +brambles_bungalow = "Bramble's Bungalow" +flower_spot = "Flower Spot" +barters_swap_shop = "Barter's Swap Shop" +barnacles_island = "Barnacle's Island" +blues_beach_hut = "Blue's Beach Hut" +blizzards_basecamp = "Bizzard's Basecamp" + + +# Region Definitions +menu_region = "Menu" +overworld_1_region = "Overworld 1" +overworld_2_region = "Overworld 2" +overworld_3_region = "Overworld 3" +overworld_4_region = "Overworld 4" + +bazaar_region = "Bazaar's General Store Region" +bramble_region = "Bramble's Bungalow Region" +flower_spot_region = "Flower Spot Region" +barter_region = "Barter's Swap Shop Region" +barnacle_region = "Barnacle's Island Region" +blue_region = "Blue's Beach Hut Region" +blizzard_region = "Bizzard's Basecamp Region" + +lake_orangatanga_region = "Lake_Orangatanga" +kremwood_forest_region = "Kremwood Forest" +cotton_top_cove_region = "Cotton-Top Cove" +mekanos_region = "Mekanos" +k3_region = "K3" +razor_ridge_region = "Razor Ridge" +kaos_kore_region = "KAOS Kore" +krematoa_region = "Krematoa" + +belchas_barn_region = "Belcha's Barn Region" +arichs_ambush_region = "Arich's Ambush Region" +squirts_showdown_region = "Squirt's Showdown Region" +kaos_karnage_region = "KAOS Karnage Region" +bleaks_house_region = "Bleak's House Region" +barboss_barrier_region = "Barbos's Barrier Region" +kastle_kaos_region = "Kastle KAOS Region" +knautilus_region = "Knautilus Region" + +belchas_burrow_region = "Belcha's Burrow Region" +kong_cave_region = "Kong Cave Region" +undercover_cove_region = "Undercover Cove Region" +ks_cache_region = "K's Cache Region" +hill_top_hoard_region = "Hill-Top Hoard Region" +bounty_beach_region = "Bounty Beach Region" +smugglers_cove_region = "Smuggler's Cove Region" +arichs_hoard_region = "Arich's Hoard Region" +bounty_bay_region = "Bounty Bay Region" +sky_high_secret_region = "Sky-High Secret Region" +glacial_grotto_region = "Glacial Grotto Region" +cifftop_cache_region = "Clifftop Cache Region" +sewer_stockpile_region = "Sewer Stockpile Region" + +lakeside_limbo_region = "Lakeside Limbo" +doorstop_dash_region = "Doorstop Dash" +tidal_trouble_region = "Tidal Trouble" +skiddas_row_region = "Skidda's Row" +murky_mill_region = "Murky Mill" + +barrel_shield_bust_up_region = "Barrel Shield Bust-Up" +riverside_race_region = "Riverside Race" +squeals_on_wheels_region = "Squeals On Wheels" +springin_spiders_region = "Springin' Spiders" +bobbing_barrel_brawl_region = "Bobbing Barrel Brawl" + +bazzas_blockade_region = "Bazza's Blockade" +rocket_barrel_ride_region = "Rocket Barrel Ride" +kreeping_klasps_region = "Kreeping Klasps" +tracker_barrel_trek_region = "Tracker Barrel Trek" +fish_food_frenzy_region = "Fish Food Frenzy" + +fire_ball_frenzy_region = "Fire-Ball Frenzy" +demolition_drain_pipe_region = "Demolition Drain-Pipe" +ripsaw_rage_region = "Ripsaw Rage" +blazing_bazookas_region = "Blazing Bazukas" +low_g_labyrinth_region = "Low-G Labyrinth" + +krevice_kreepers_region = "Krevice Kreepers" +tearaway_toboggan_region = "Tearaway Toboggan" +barrel_drop_bounce_region = "Barrel Drop Bounce" +krack_shot_kroc_region = "Krack-Shot Kroc" +lemguin_lunge_region = "Lemguin Lunge" + +buzzer_barrage_region = "Buzzer Barrage" +kong_fused_cliffs_region = "Kong-Fused Cliffs" +floodlit_fish_region = "Floodlit Fish" +pothole_panic_region = "Pothole Panic" +ropey_rumpus_region = "Ropey Rumpus" + +konveyor_rope_clash_region = "Konveyor Rope Klash" +creepy_caverns_region = "Creepy Caverns" +lightning_lookout_region = "Lightning Lookout" +koindozer_klamber_region = "Koindozer Klamber" +poisonous_pipeline_region = "Poisonous Pipeline" + +stampede_sprint_region = "Stampede Sprint" +criss_cross_cliffs_region = "Criss Kross Cliffs" +tyrant_twin_tussle_region = "Tyrant Twin Tussle" +swoopy_salvo_region = "Swoopy Salvo" +rocket_rush_region = "Rocket Rush" diff --git a/worlds/dkc3/Options.py b/worlds/dkc3/Options.py new file mode 100644 index 00000000..9e000149 --- /dev/null +++ b/worlds/dkc3/Options.py @@ -0,0 +1,132 @@ +import typing + +from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList + + +class Goal(Choice): + """ + Determines the goal of the seed + Knautilus: Reach the Knautilus and defeat Baron K. Roolenstein + Banana Bird Hunt: Find a certain number of Banana Birds and rescue their mother + """ + display_name = "Goal" + option_knautilus = 0 + option_banana_bird_hunt = 1 + default = 0 + + +class IncludeTradeSequence(Toggle): + """ + Allows logic to place items at the various steps of the trade sequence + """ + display_name = "Include Trade Sequence" + + +class DKCoinsForGyrocopter(Range): + """ + How many DK Coins are needed to unlock the Gyrocopter + Note: Achieving this number before unlocking the Turbo Ski will cause the game to grant you a + one-time upgrade to the next non-unlocked boat, until you return to Funky. Logic does not assume + that you will use this. + """ + display_name = "DK Coins for Gyrocopter" + range_start = 10 + range_end = 41 + default = 30 + + +class KrematoaBonusCoinCost(Range): + """ + How many Bonus Coins are needed to unlock each level in Krematoa + """ + display_name = "Krematoa Bonus Coins Cost" + range_start = 1 + range_end = 17 + default = 15 + + +class PercentageOfExtraBonusCoins(Range): + """ + What Percentage of unneeded Bonus Coins are included in the item pool + """ + display_name = "Percentage of Extra Bonus Coins" + range_start = 0 + range_end = 100 + default = 100 + + +class NumberOfBananaBirds(Range): + """ + How many Banana Birds are put into the item pool + """ + display_name = "Number of Banana Birds" + range_start = 5 + range_end = 15 + default = 15 + + +class PercentageOfBananaBirds(Range): + """ + What Percentage of Banana Birds in the item pool are required for Banana Bird Hunt + """ + display_name = "Percentage of Banana Birds" + range_start = 20 + range_end = 100 + default = 100 + + +class LevelShuffle(Toggle): + """ + Whether levels are shuffled + """ + display_name = "Level Shuffle" + + +class MusicShuffle(Toggle): + """ + Whether music is shuffled + """ + display_name = "Music Shuffle" + + +class KongPaletteSwap(Choice): + """ + Which Palette to use for the Kongs + """ + display_name = "Kong Palette Swap" + option_default = 0 + option_purple = 1 + option_spooky = 2 + option_dark = 3 + option_chocolate = 4 + option_shadow = 5 + option_red_gold = 6 + option_gbc = 7 + option_halloween = 8 + default = 0 + + +class StartingLifeCount(Range): + """ + How many extra lives to start the game with + """ + display_name = "Starting Life Count" + range_start = 1 + range_end = 99 + default = 5 + + +dkc3_options: typing.Dict[str, type(Option)] = { + #"death_link": DeathLink, # Disabled + "goal": Goal, + #"include_trade_sequence": IncludeTradeSequence, # Disabled + "dk_coins_for_gyrocopter": DKCoinsForGyrocopter, + "krematoa_bonus_coin_cost": KrematoaBonusCoinCost, + "percentage_of_extra_bonus_coins": PercentageOfExtraBonusCoins, + "number_of_banana_birds": NumberOfBananaBirds, + "percentage_of_banana_birds": PercentageOfBananaBirds, + "level_shuffle": LevelShuffle, + "music_shuffle": MusicShuffle, + "kong_palette_swap": KongPaletteSwap, + "starting_life_count": StartingLifeCount, +} diff --git a/worlds/dkc3/Regions.py b/worlds/dkc3/Regions.py new file mode 100644 index 00000000..6cb01e4f --- /dev/null +++ b/worlds/dkc3/Regions.py @@ -0,0 +1,879 @@ +import typing + +from BaseClasses import MultiWorld, Region, Entrance +from .Items import DKC3Item +from .Locations import DKC3Location +from .Names import LocationName, ItemName + + +def create_regions(world, player: int, active_locations): + menu_region = create_region(world, player, active_locations, 'Menu', None, None) + + overworld_1_region_locations = {} + if world.goal[player] != "knautilus": + overworld_1_region_locations.update({LocationName.banana_bird_mother: []}) + overworld_1_region = create_region(world, player, active_locations, LocationName.overworld_1_region, + overworld_1_region_locations, None) + + overworld_2_region_locations = {} + overworld_2_region = create_region(world, player, active_locations, LocationName.overworld_2_region, + overworld_2_region_locations, None) + + overworld_3_region_locations = {} + overworld_3_region = create_region(world, player, active_locations, LocationName.overworld_3_region, + overworld_3_region_locations, None) + + overworld_4_region_locations = {} + overworld_4_region = create_region(world, player, active_locations, LocationName.overworld_4_region, + overworld_4_region_locations, None) + + + lake_orangatanga_region = create_region(world, player, active_locations, LocationName.lake_orangatanga_region, None, None) + kremwood_forest_region = create_region(world, player, active_locations, LocationName.kremwood_forest_region, None, None) + cotton_top_cove_region = create_region(world, player, active_locations, LocationName.cotton_top_cove_region, None, None) + mekanos_region = create_region(world, player, active_locations, LocationName.mekanos_region, None, None) + k3_region = create_region(world, player, active_locations, LocationName.k3_region, None, None) + razor_ridge_region = create_region(world, player, active_locations, LocationName.razor_ridge_region, None, None) + kaos_kore_region = create_region(world, player, active_locations, LocationName.kaos_kore_region, None, None) + krematoa_region = create_region(world, player, active_locations, LocationName.krematoa_region, None, None) + + + lakeside_limbo_region_locations = { + LocationName.lakeside_limbo_flag : [0x657, 1], + LocationName.lakeside_limbo_bonus_1 : [0x657, 2], + LocationName.lakeside_limbo_bonus_2 : [0x657, 3], + LocationName.lakeside_limbo_dk : [0x657, 5], + } + lakeside_limbo_region = create_region(world, player, active_locations, LocationName.lakeside_limbo_region, + lakeside_limbo_region_locations, None) + + doorstop_dash_region_locations = { + LocationName.doorstop_dash_flag : [0x65A, 1], + LocationName.doorstop_dash_bonus_1 : [0x65A, 2], + LocationName.doorstop_dash_bonus_2 : [0x65A, 3], + LocationName.doorstop_dash_dk : [0x65A, 5], + } + doorstop_dash_region = create_region(world, player, active_locations, LocationName.doorstop_dash_region, + doorstop_dash_region_locations, None) + + tidal_trouble_region_locations = { + LocationName.tidal_trouble_flag : [0x659, 1], + LocationName.tidal_trouble_bonus_1 : [0x659, 2], + LocationName.tidal_trouble_bonus_2 : [0x659, 3], + LocationName.tidal_trouble_dk : [0x659, 5], + } + tidal_trouble_region = create_region(world, player, active_locations, LocationName.tidal_trouble_region, + tidal_trouble_region_locations, None) + + skiddas_row_region_locations = { + LocationName.skiddas_row_flag : [0x65D, 1], + LocationName.skiddas_row_bonus_1 : [0x65D, 2], + LocationName.skiddas_row_bonus_2 : [0x65D, 3], + LocationName.skiddas_row_dk : [0x65D, 5], + } + skiddas_row_region = create_region(world, player, active_locations, LocationName.skiddas_row_region, + skiddas_row_region_locations, None) + + murky_mill_region_locations = { + LocationName.murky_mill_flag : [0x65C, 1], + LocationName.murky_mill_bonus_1 : [0x65C, 2], + LocationName.murky_mill_bonus_2 : [0x65C, 3], + LocationName.murky_mill_dk : [0x65C, 5], + } + murky_mill_region = create_region(world, player, active_locations, LocationName.murky_mill_region, + murky_mill_region_locations, None) + + barrel_shield_bust_up_region_locations = { + LocationName.barrel_shield_bust_up_flag : [0x662, 1], + LocationName.barrel_shield_bust_up_bonus_1 : [0x662, 2], + LocationName.barrel_shield_bust_up_bonus_2 : [0x662, 3], + LocationName.barrel_shield_bust_up_dk : [0x662, 5], + } + barrel_shield_bust_up_region = create_region(world, player, active_locations, LocationName.barrel_shield_bust_up_region, + barrel_shield_bust_up_region_locations, None) + + riverside_race_region_locations = { + LocationName.riverside_race_flag : [0x664, 1], + LocationName.riverside_race_bonus_1 : [0x664, 2], + LocationName.riverside_race_bonus_2 : [0x664, 3], + LocationName.riverside_race_dk : [0x664, 5], + } + riverside_race_region = create_region(world, player, active_locations, LocationName.riverside_race_region, + riverside_race_region_locations, None) + + squeals_on_wheels_region_locations = { + LocationName.squeals_on_wheels_flag : [0x65B, 1], + LocationName.squeals_on_wheels_bonus_1 : [0x65B, 2], + LocationName.squeals_on_wheels_bonus_2 : [0x65B, 3], + LocationName.squeals_on_wheels_dk : [0x65B, 5], + } + squeals_on_wheels_region = create_region(world, player, active_locations, LocationName.squeals_on_wheels_region, + squeals_on_wheels_region_locations, None) + + springin_spiders_region_locations = { + LocationName.springin_spiders_flag : [0x661, 1], + LocationName.springin_spiders_bonus_1 : [0x661, 2], + LocationName.springin_spiders_bonus_2 : [0x661, 3], + LocationName.springin_spiders_dk : [0x661, 5], + } + springin_spiders_region = create_region(world, player, active_locations, LocationName.springin_spiders_region, + springin_spiders_region_locations, None) + + bobbing_barrel_brawl_region_locations = { + LocationName.bobbing_barrel_brawl_flag : [0x666, 1], + LocationName.bobbing_barrel_brawl_bonus_1 : [0x666, 2], + LocationName.bobbing_barrel_brawl_bonus_2 : [0x666, 3], + LocationName.bobbing_barrel_brawl_dk : [0x666, 5], + } + bobbing_barrel_brawl_region = create_region(world, player, active_locations, LocationName.bobbing_barrel_brawl_region, + bobbing_barrel_brawl_region_locations, None) + + bazzas_blockade_region_locations = { + LocationName.bazzas_blockade_flag : [0x667, 1], + LocationName.bazzas_blockade_bonus_1 : [0x667, 2], + LocationName.bazzas_blockade_bonus_2 : [0x667, 3], + LocationName.bazzas_blockade_dk : [0x667, 5], + } + bazzas_blockade_region = create_region(world, player, active_locations, LocationName.bazzas_blockade_region, + bazzas_blockade_region_locations, None) + + rocket_barrel_ride_region_locations = { + LocationName.rocket_barrel_ride_flag : [0x66A, 1], + LocationName.rocket_barrel_ride_bonus_1 : [0x66A, 2], + LocationName.rocket_barrel_ride_bonus_2 : [0x66A, 3], + LocationName.rocket_barrel_ride_dk : [0x66A, 5], + } + rocket_barrel_ride_region = create_region(world, player, active_locations, LocationName.rocket_barrel_ride_region, + rocket_barrel_ride_region_locations, None) + + kreeping_klasps_region_locations = { + LocationName.kreeping_klasps_flag : [0x658, 1], + LocationName.kreeping_klasps_bonus_1 : [0x658, 2], + LocationName.kreeping_klasps_bonus_2 : [0x658, 3], + LocationName.kreeping_klasps_dk : [0x658, 5], + } + kreeping_klasps_region = create_region(world, player, active_locations, LocationName.kreeping_klasps_region, + kreeping_klasps_region_locations, None) + + tracker_barrel_trek_region_locations = { + LocationName.tracker_barrel_trek_flag : [0x66B, 1], + LocationName.tracker_barrel_trek_bonus_1 : [0x66B, 2], + LocationName.tracker_barrel_trek_bonus_2 : [0x66B, 3], + LocationName.tracker_barrel_trek_dk : [0x66B, 5], + } + tracker_barrel_trek_region = create_region(world, player, active_locations, LocationName.tracker_barrel_trek_region, + tracker_barrel_trek_region_locations, None) + + fish_food_frenzy_region_locations = { + LocationName.fish_food_frenzy_flag : [0x668, 1], + LocationName.fish_food_frenzy_bonus_1 : [0x668, 2], + LocationName.fish_food_frenzy_bonus_2 : [0x668, 3], + LocationName.fish_food_frenzy_dk : [0x668, 5], + } + fish_food_frenzy_region = create_region(world, player, active_locations, LocationName.fish_food_frenzy_region, + fish_food_frenzy_region_locations, None) + + fire_ball_frenzy_region_locations = { + LocationName.fire_ball_frenzy_flag : [0x66D, 1], + LocationName.fire_ball_frenzy_bonus_1 : [0x66D, 2], + LocationName.fire_ball_frenzy_bonus_2 : [0x66D, 3], + LocationName.fire_ball_frenzy_dk : [0x66D, 5], + } + fire_ball_frenzy_region = create_region(world, player, active_locations, LocationName.fire_ball_frenzy_region, + fire_ball_frenzy_region_locations, None) + + demolition_drain_pipe_region_locations = { + LocationName.demolition_drain_pipe_flag : [0x672, 1], + LocationName.demolition_drain_pipe_bonus_1 : [0x672, 2], + LocationName.demolition_drain_pipe_bonus_2 : [0x672, 3], + LocationName.demolition_drain_pipe_dk : [0x672, 5], + } + demolition_drain_pipe_region = create_region(world, player, active_locations, LocationName.demolition_drain_pipe_region, + demolition_drain_pipe_region_locations, None) + + ripsaw_rage_region_locations = { + LocationName.ripsaw_rage_flag : [0x660, 1], + LocationName.ripsaw_rage_bonus_1 : [0x660, 2], + LocationName.ripsaw_rage_bonus_2 : [0x660, 3], + LocationName.ripsaw_rage_dk : [0x660, 5], + } + ripsaw_rage_region = create_region(world, player, active_locations, LocationName.ripsaw_rage_region, + ripsaw_rage_region_locations, None) + + blazing_bazookas_region_locations = { + LocationName.blazing_bazookas_flag : [0x66E, 1], + LocationName.blazing_bazookas_bonus_1 : [0x66E, 2], + LocationName.blazing_bazookas_bonus_2 : [0x66E, 3], + LocationName.blazing_bazookas_dk : [0x66E, 5], + } + blazing_bazookas_region = create_region(world, player, active_locations, LocationName.blazing_bazookas_region, + blazing_bazookas_region_locations, None) + + low_g_labyrinth_region_locations = { + LocationName.low_g_labyrinth_flag : [0x670, 1], + LocationName.low_g_labyrinth_bonus_1 : [0x670, 2], + LocationName.low_g_labyrinth_bonus_2 : [0x670, 3], + LocationName.low_g_labyrinth_dk : [0x670, 5], + } + low_g_labyrinth_region = create_region(world, player, active_locations, LocationName.low_g_labyrinth_region, + low_g_labyrinth_region_locations, None) + + krevice_kreepers_region_locations = { + LocationName.krevice_kreepers_flag : [0x673, 1], + LocationName.krevice_kreepers_bonus_1 : [0x673, 2], + LocationName.krevice_kreepers_bonus_2 : [0x673, 3], + LocationName.krevice_kreepers_dk : [0x673, 5], + } + krevice_kreepers_region = create_region(world, player, active_locations, LocationName.krevice_kreepers_region, + krevice_kreepers_region_locations, None) + + tearaway_toboggan_region_locations = { + LocationName.tearaway_toboggan_flag : [0x65F, 1], + LocationName.tearaway_toboggan_bonus_1 : [0x65F, 2], + LocationName.tearaway_toboggan_bonus_2 : [0x65F, 3], + LocationName.tearaway_toboggan_dk : [0x65F, 5], + } + tearaway_toboggan_region = create_region(world, player, active_locations, LocationName.tearaway_toboggan_region, + tearaway_toboggan_region_locations, None) + + barrel_drop_bounce_region_locations = { + LocationName.barrel_drop_bounce_flag : [0x66C, 1], + LocationName.barrel_drop_bounce_bonus_1 : [0x66C, 2], + LocationName.barrel_drop_bounce_bonus_2 : [0x66C, 3], + LocationName.barrel_drop_bounce_dk : [0x66C, 5], + } + barrel_drop_bounce_region = create_region(world, player, active_locations, LocationName.barrel_drop_bounce_region, + barrel_drop_bounce_region_locations, None) + + krack_shot_kroc_region_locations = { + LocationName.krack_shot_kroc_flag : [0x66F, 1], + LocationName.krack_shot_kroc_bonus_1 : [0x66F, 2], + LocationName.krack_shot_kroc_bonus_2 : [0x66F, 3], + LocationName.krack_shot_kroc_dk : [0x66F, 5], + } + krack_shot_kroc_region = create_region(world, player, active_locations, LocationName.krack_shot_kroc_region, + krack_shot_kroc_region_locations, None) + + lemguin_lunge_region_locations = { + LocationName.lemguin_lunge_flag : [0x65E, 1], + LocationName.lemguin_lunge_bonus_1 : [0x65E, 2], + LocationName.lemguin_lunge_bonus_2 : [0x65E, 3], + LocationName.lemguin_lunge_dk : [0x65E, 5], + } + lemguin_lunge_region = create_region(world, player, active_locations, LocationName.lemguin_lunge_region, + lemguin_lunge_region_locations, None) + + buzzer_barrage_region_locations = { + LocationName.buzzer_barrage_flag : [0x676, 1], + LocationName.buzzer_barrage_bonus_1 : [0x676, 2], + LocationName.buzzer_barrage_bonus_2 : [0x676, 3], + LocationName.buzzer_barrage_dk : [0x676, 5], + } + buzzer_barrage_region = create_region(world, player, active_locations, LocationName.buzzer_barrage_region, + buzzer_barrage_region_locations, None) + + kong_fused_cliffs_region_locations = { + LocationName.kong_fused_cliffs_flag : [0x674, 1], + LocationName.kong_fused_cliffs_bonus_1 : [0x674, 2], + LocationName.kong_fused_cliffs_bonus_2 : [0x674, 3], + LocationName.kong_fused_cliffs_dk : [0x674, 5], + } + kong_fused_cliffs_region = create_region(world, player, active_locations, LocationName.kong_fused_cliffs_region, + kong_fused_cliffs_region_locations, None) + + floodlit_fish_region_locations = { + LocationName.floodlit_fish_flag : [0x669, 1], + LocationName.floodlit_fish_bonus_1 : [0x669, 2], + LocationName.floodlit_fish_bonus_2 : [0x669, 3], + LocationName.floodlit_fish_dk : [0x669, 5], + } + floodlit_fish_region = create_region(world, player, active_locations, LocationName.floodlit_fish_region, + floodlit_fish_region_locations, None) + + pothole_panic_region_locations = { + LocationName.pothole_panic_flag : [0x677, 1], + LocationName.pothole_panic_bonus_1 : [0x677, 2], + LocationName.pothole_panic_bonus_2 : [0x677, 3], + LocationName.pothole_panic_dk : [0x677, 5], + } + pothole_panic_region = create_region(world, player, active_locations, LocationName.pothole_panic_region, + pothole_panic_region_locations, None) + + ropey_rumpus_region_locations = { + LocationName.ropey_rumpus_flag : [0x675, 1], + LocationName.ropey_rumpus_bonus_1 : [0x675, 2], + LocationName.ropey_rumpus_bonus_2 : [0x675, 3], + LocationName.ropey_rumpus_dk : [0x675, 5], + } + ropey_rumpus_region = create_region(world, player, active_locations, LocationName.ropey_rumpus_region, + ropey_rumpus_region_locations, None) + + konveyor_rope_clash_region_locations = { + LocationName.konveyor_rope_clash_flag : [0x657, 1], + LocationName.konveyor_rope_clash_bonus_1 : [0x657, 2], + LocationName.konveyor_rope_clash_bonus_2 : [0x657, 3], + LocationName.konveyor_rope_clash_dk : [0x657, 5], + } + konveyor_rope_clash_region = create_region(world, player, active_locations, LocationName.konveyor_rope_clash_region, + konveyor_rope_clash_region_locations, None) + + creepy_caverns_region_locations = { + LocationName.creepy_caverns_flag : [0x678, 1], + LocationName.creepy_caverns_bonus_1 : [0x678, 2], + LocationName.creepy_caverns_bonus_2 : [0x678, 3], + LocationName.creepy_caverns_dk : [0x678, 5], + } + creepy_caverns_region = create_region(world, player, active_locations, LocationName.creepy_caverns_region, + creepy_caverns_region_locations, None) + + lightning_lookout_region_locations = { + LocationName.lightning_lookout_flag : [0x665, 1], + LocationName.lightning_lookout_bonus_1 : [0x665, 2], + LocationName.lightning_lookout_bonus_2 : [0x665, 3], + LocationName.lightning_lookout_dk : [0x665, 5], + } + lightning_lookout_region = create_region(world, player, active_locations, LocationName.lightning_lookout_region, + lightning_lookout_region_locations, None) + + koindozer_klamber_region_locations = { + LocationName.koindozer_klamber_flag : [0x679, 1], + LocationName.koindozer_klamber_bonus_1 : [0x679, 2], + LocationName.koindozer_klamber_bonus_2 : [0x679, 3], + LocationName.koindozer_klamber_dk : [0x679, 5], + } + koindozer_klamber_region = create_region(world, player, active_locations, LocationName.koindozer_klamber_region, + koindozer_klamber_region_locations, None) + + poisonous_pipeline_region_locations = { + LocationName.poisonous_pipeline_flag : [0x671, 1], + LocationName.poisonous_pipeline_bonus_1 : [0x671, 2], + LocationName.poisonous_pipeline_bonus_2 : [0x671, 3], + LocationName.poisonous_pipeline_dk : [0x671, 5], + } + poisonous_pipeline_region = create_region(world, player, active_locations, LocationName.poisonous_pipeline_region, + poisonous_pipeline_region_locations, None) + + stampede_sprint_region_locations = { + LocationName.stampede_sprint_flag : [0x67B, 1], + LocationName.stampede_sprint_bonus_1 : [0x67B, 2], + LocationName.stampede_sprint_bonus_2 : [0x67B, 3], + LocationName.stampede_sprint_bonus_3 : [0x67B, 4], + LocationName.stampede_sprint_dk : [0x67B, 5], + } + stampede_sprint_region = create_region(world, player, active_locations, LocationName.stampede_sprint_region, + stampede_sprint_region_locations, None) + + criss_cross_cliffs_region_locations = { + LocationName.criss_cross_cliffs_flag : [0x67C, 1], + LocationName.criss_cross_cliffs_bonus_1 : [0x67C, 2], + LocationName.criss_cross_cliffs_bonus_2 : [0x67C, 3], + LocationName.criss_cross_cliffs_dk : [0x67C, 5], + } + criss_cross_cliffs_region = create_region(world, player, active_locations, LocationName.criss_cross_cliffs_region, + criss_cross_cliffs_region_locations, None) + + tyrant_twin_tussle_region_locations = { + LocationName.tyrant_twin_tussle_flag : [0x67D, 1], + LocationName.tyrant_twin_tussle_bonus_1 : [0x67D, 2], + LocationName.tyrant_twin_tussle_bonus_2 : [0x67D, 3], + LocationName.tyrant_twin_tussle_bonus_3 : [0x67D, 4], + LocationName.tyrant_twin_tussle_dk : [0x67D, 5], + } + tyrant_twin_tussle_region = create_region(world, player, active_locations, LocationName.tyrant_twin_tussle_region, + tyrant_twin_tussle_region_locations, None) + + swoopy_salvo_region_locations = { + LocationName.swoopy_salvo_flag : [0x663, 1], + LocationName.swoopy_salvo_bonus_1 : [0x663, 2], + LocationName.swoopy_salvo_bonus_2 : [0x663, 3], + LocationName.swoopy_salvo_bonus_3 : [0x663, 4], + LocationName.swoopy_salvo_dk : [0x663, 5], + } + swoopy_salvo_region = create_region(world, player, active_locations, LocationName.swoopy_salvo_region, + swoopy_salvo_region_locations, None) + + rocket_rush_region_locations = { + LocationName.rocket_rush_flag : [0x67E, 1], + LocationName.rocket_rush_dk : [0x67E, 5], + } + rocket_rush_region = create_region(world, player, active_locations, LocationName.rocket_rush_region, + rocket_rush_region_locations, None) + + belchas_barn_region_locations = { + LocationName.belchas_barn: [0x64F, 1], + } + belchas_barn_region = create_region(world, player, active_locations, LocationName.belchas_barn_region, + belchas_barn_region_locations, None) + + arichs_ambush_region_locations = { + LocationName.arichs_ambush: [0x650, 1], + } + arichs_ambush_region = create_region(world, player, active_locations, LocationName.arichs_ambush_region, + arichs_ambush_region_locations, None) + + squirts_showdown_region_locations = { + LocationName.squirts_showdown: [0x651, 1], + } + squirts_showdown_region = create_region(world, player, active_locations, LocationName.squirts_showdown_region, + squirts_showdown_region_locations, None) + + kaos_karnage_region_locations = { + LocationName.kaos_karnage: [0x652, 1], + } + kaos_karnage_region = create_region(world, player, active_locations, LocationName.kaos_karnage_region, + kaos_karnage_region_locations, None) + + bleaks_house_region_locations = { + LocationName.bleaks_house: [0x653, 1], + } + bleaks_house_region = create_region(world, player, active_locations, LocationName.bleaks_house_region, + bleaks_house_region_locations, None) + + barboss_barrier_region_locations = { + LocationName.barboss_barrier: [0x654, 1], + } + barboss_barrier_region = create_region(world, player, active_locations, LocationName.barboss_barrier_region, + barboss_barrier_region_locations, None) + + kastle_kaos_region_locations = { + LocationName.kastle_kaos: [0x655, 1], + } + kastle_kaos_region = create_region(world, player, active_locations, LocationName.kastle_kaos_region, + kastle_kaos_region_locations, None) + + knautilus_region_locations = { + LocationName.knautilus: [0x656, 1], + } + knautilus_region = create_region(world, player, active_locations, LocationName.knautilus_region, + knautilus_region_locations, None) + + belchas_burrow_region_locations = { + LocationName.belchas_burrow: [0x647, 1], + } + belchas_burrow_region = create_region(world, player, active_locations, LocationName.belchas_burrow_region, + belchas_burrow_region_locations, None) + + kong_cave_region_locations = { + LocationName.kong_cave: [0x645, 1], + } + kong_cave_region = create_region(world, player, active_locations, LocationName.kong_cave_region, + kong_cave_region_locations, None) + + undercover_cove_region_locations = { + LocationName.undercover_cove: [0x644, 1], + } + undercover_cove_region = create_region(world, player, active_locations, LocationName.undercover_cove_region, + undercover_cove_region_locations, None) + + ks_cache_region_locations = { + LocationName.ks_cache: [0x642, 1], + } + ks_cache_region = create_region(world, player, active_locations, LocationName.ks_cache_region, + ks_cache_region_locations, None) + + hill_top_hoard_region_locations = { + LocationName.hill_top_hoard: [0x643, 1], + } + hill_top_hoard_region = create_region(world, player, active_locations, LocationName.hill_top_hoard_region, + hill_top_hoard_region_locations, None) + + bounty_beach_region_locations = { + LocationName.bounty_beach: [0x646, 1], + } + bounty_beach_region = create_region(world, player, active_locations, LocationName.bounty_beach_region, + bounty_beach_region_locations, None) + + smugglers_cove_region_locations = { + LocationName.smugglers_cove: [0x648, 1], + } + smugglers_cove_region = create_region(world, player, active_locations, LocationName.smugglers_cove_region, + smugglers_cove_region_locations, None) + + arichs_hoard_region_locations = { + LocationName.arichs_hoard: [0x649, 1], + } + arichs_hoard_region = create_region(world, player, active_locations, LocationName.arichs_hoard_region, + arichs_hoard_region_locations, None) + + bounty_bay_region_locations = { + LocationName.bounty_bay: [0x64A, 1], + } + bounty_bay_region = create_region(world, player, active_locations, LocationName.bounty_bay_region, + bounty_bay_region_locations, None) + + sky_high_secret_region_locations = { + LocationName.sky_high_secret: [0x64B, 1], + } + sky_high_secret_region = create_region(world, player, active_locations, LocationName.sky_high_secret_region, + sky_high_secret_region_locations, None) + + glacial_grotto_region_locations = { + LocationName.glacial_grotto: [0x64C, 1], + } + glacial_grotto_region = create_region(world, player, active_locations, LocationName.glacial_grotto_region, + glacial_grotto_region_locations, None) + + cifftop_cache_region_locations = { + LocationName.cifftop_cache: [0x64D, 1], + } + cifftop_cache_region = create_region(world, player, active_locations, LocationName.cifftop_cache_region, + cifftop_cache_region_locations, None) + + sewer_stockpile_region_locations = { + LocationName.sewer_stockpile: [0x64E, 1], + } + sewer_stockpile_region = create_region(world, player, active_locations, LocationName.sewer_stockpile_region, + sewer_stockpile_region_locations, None) + + + # Set up the regions correctly. + world.regions += [ + menu_region, + overworld_1_region, + overworld_2_region, + overworld_3_region, + overworld_4_region, + lake_orangatanga_region, + kremwood_forest_region, + cotton_top_cove_region, + mekanos_region, + k3_region, + razor_ridge_region, + kaos_kore_region, + krematoa_region, + lakeside_limbo_region, + doorstop_dash_region, + tidal_trouble_region, + skiddas_row_region, + murky_mill_region, + barrel_shield_bust_up_region, + riverside_race_region, + squeals_on_wheels_region, + springin_spiders_region, + bobbing_barrel_brawl_region, + bazzas_blockade_region, + rocket_barrel_ride_region, + kreeping_klasps_region, + tracker_barrel_trek_region, + fish_food_frenzy_region, + fire_ball_frenzy_region, + demolition_drain_pipe_region, + ripsaw_rage_region, + blazing_bazookas_region, + low_g_labyrinth_region, + krevice_kreepers_region, + tearaway_toboggan_region, + barrel_drop_bounce_region, + krack_shot_kroc_region, + lemguin_lunge_region, + buzzer_barrage_region, + kong_fused_cliffs_region, + floodlit_fish_region, + pothole_panic_region, + ropey_rumpus_region, + konveyor_rope_clash_region, + creepy_caverns_region, + lightning_lookout_region, + koindozer_klamber_region, + poisonous_pipeline_region, + stampede_sprint_region, + criss_cross_cliffs_region, + tyrant_twin_tussle_region, + swoopy_salvo_region, + rocket_rush_region, + belchas_barn_region, + arichs_ambush_region, + squirts_showdown_region, + kaos_karnage_region, + bleaks_house_region, + barboss_barrier_region, + kastle_kaos_region, + knautilus_region, + belchas_burrow_region, + kong_cave_region, + undercover_cove_region, + ks_cache_region, + hill_top_hoard_region, + bounty_beach_region, + smugglers_cove_region, + arichs_hoard_region, + bounty_bay_region, + sky_high_secret_region, + glacial_grotto_region, + cifftop_cache_region, + sewer_stockpile_region, + ] + + bazaar_region_locations = {} + bramble_region_locations = {} + flower_spot_region_locations = {} + barter_region_locations = {} + barnacle_region_locations = {} + blue_region_locations = {} + blizzard_region_locations = {} + + if False:#world.include_trade_sequence[player]: + bazaar_region_locations.update({ + LocationName.bazaars_general_store_1: [0x615, 2, True], + LocationName.bazaars_general_store_2: [0x615, 3, True], + }) + + bramble_region_locations.update({ + LocationName.brambles_bungalow: [0x619, 2], + }) + + #flower_spot_region_locations.update({ + # LocationName.flower_spot: [0x615, 3, True], + #}) + + barter_region_locations.update({ + LocationName.barters_swap_shop: [0x61B, 3], + }) + + barnacle_region_locations.update({ + LocationName.barnacles_island: [0x61D, 2], + }) + + blue_region_locations.update({ + LocationName.blues_beach_hut: [0x621, 4], + }) + + blizzard_region_locations.update({ + LocationName.blizzards_basecamp: [0x625, 4, True], + }) + + bazaar_region = create_region(world, player, active_locations, LocationName.bazaar_region, + bazaar_region_locations, None) + bramble_region = create_region(world, player, active_locations, LocationName.bramble_region, + bramble_region_locations, None) + flower_spot_region = create_region(world, player, active_locations, LocationName.flower_spot_region, + flower_spot_region_locations, None) + barter_region = create_region(world, player, active_locations, LocationName.barter_region, + barter_region_locations, None) + barnacle_region = create_region(world, player, active_locations, LocationName.barnacle_region, + barnacle_region_locations, None) + blue_region = create_region(world, player, active_locations, LocationName.blue_region, + blue_region_locations, None) + blizzard_region = create_region(world, player, active_locations, LocationName.blizzard_region, + blizzard_region_locations, None) + + world.regions += [ + bazaar_region, + bramble_region, + flower_spot_region, + barter_region, + barnacle_region, + blue_region, + blizzard_region, + ] + + +def connect_regions(world, player, level_list): + names: typing.Dict[str, int] = {} + + # Overworld + connect(world, player, names, 'Menu', LocationName.overworld_1_region) + connect(world, player, names, LocationName.overworld_1_region, LocationName.overworld_2_region, + lambda state: (state.has(ItemName.progressive_boat, player, 1))) + connect(world, player, names, LocationName.overworld_2_region, LocationName.overworld_3_region, + lambda state: (state.has(ItemName.progressive_boat, player, 3))) + connect(world, player, names, LocationName.overworld_1_region, LocationName.overworld_4_region, + lambda state: (state.has(ItemName.dk_coin, player, world.dk_coins_for_gyrocopter[player].value) and + state.has(ItemName.progressive_boat, player, 3))) + + # World Connections + connect(world, player, names, LocationName.overworld_1_region, LocationName.lake_orangatanga_region) + connect(world, player, names, LocationName.overworld_1_region, LocationName.kremwood_forest_region) + connect(world, player, names, LocationName.overworld_1_region, LocationName.bounty_beach_region) + connect(world, player, names, LocationName.overworld_1_region, LocationName.bazaar_region) + + connect(world, player, names, LocationName.overworld_2_region, LocationName.cotton_top_cove_region) + connect(world, player, names, LocationName.overworld_2_region, LocationName.mekanos_region) + connect(world, player, names, LocationName.overworld_2_region, LocationName.kong_cave_region) + connect(world, player, names, LocationName.overworld_2_region, LocationName.bramble_region) + + connect(world, player, names, LocationName.overworld_3_region, LocationName.k3_region) + connect(world, player, names, LocationName.overworld_3_region, LocationName.razor_ridge_region) + connect(world, player, names, LocationName.overworld_3_region, LocationName.kaos_kore_region) + connect(world, player, names, LocationName.overworld_3_region, LocationName.krematoa_region) + connect(world, player, names, LocationName.overworld_3_region, LocationName.undercover_cove_region) + connect(world, player, names, LocationName.overworld_3_region, LocationName.flower_spot_region) + connect(world, player, names, LocationName.overworld_3_region, LocationName.barter_region) + + connect(world, player, names, LocationName.overworld_4_region, LocationName.belchas_burrow_region) + connect(world, player, names, LocationName.overworld_4_region, LocationName.ks_cache_region) + connect(world, player, names, LocationName.overworld_4_region, LocationName.hill_top_hoard_region) + + + # Lake Orangatanga Connections + lake_orangatanga_levels = [ + level_list[0], + level_list[1], + level_list[2], + level_list[3], + level_list[4], + LocationName.belchas_barn_region, + LocationName.barnacle_region, + LocationName.smugglers_cove_region, + ] + + for i in range(0, len(lake_orangatanga_levels)): + connect(world, player, names, LocationName.lake_orangatanga_region, lake_orangatanga_levels[i]) + + # Kremwood Forest Connections + kremwood_forest_levels = [ + level_list[5], + level_list[6], + level_list[7], + level_list[8], + level_list[9], + LocationName.arichs_ambush_region, + LocationName.arichs_hoard_region, + ] + + for i in range(0, len(kremwood_forest_levels) - 1): + connect(world, player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[i]) + + connect(world, player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[-1], + lambda state: (state.can_reach(LocationName.riverside_race_flag, "Location", player))) + + # Cotton-Top Cove Connections + cotton_top_cove_levels = [ + LocationName.blue_region, + level_list[10], + level_list[11], + level_list[12], + level_list[13], + level_list[14], + LocationName.squirts_showdown_region, + LocationName.bounty_bay_region, + ] + + for i in range(0, len(cotton_top_cove_levels)): + connect(world, player, names, LocationName.cotton_top_cove_region, cotton_top_cove_levels[i]) + + # Mekanos Connections + mekanos_levels = [ + level_list[15], + level_list[16], + level_list[17], + level_list[18], + level_list[19], + LocationName.kaos_karnage_region, + ] + + for i in range(0, len(mekanos_levels)): + connect(world, player, names, LocationName.mekanos_region, mekanos_levels[i]) + + if False:#world.include_trade_sequence[player]: + connect(world, player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, + lambda state: (state.has(ItemName.bowling_ball, player, 1))) + else: + connect(world, player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region, + lambda state: (state.can_reach(LocationName.bleaks_house, "Location", player))) + + # K3 Connections + k3_levels = [ + level_list[20], + level_list[21], + level_list[22], + level_list[23], + level_list[24], + LocationName.bleaks_house_region, + LocationName.blizzard_region, + LocationName.glacial_grotto_region, + ] + + for i in range(0, len(k3_levels)): + connect(world, player, names, LocationName.k3_region, k3_levels[i]) + + # Razor Ridge Connections + razor_ridge_levels = [ + level_list[25], + level_list[26], + level_list[27], + level_list[28], + level_list[29], + LocationName.barboss_barrier_region, + ] + + for i in range(0, len(razor_ridge_levels)): + connect(world, player, names, LocationName.razor_ridge_region, razor_ridge_levels[i]) + + if False:#world.include_trade_sequence[player]: + connect(world, player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region, + lambda state: (state.has(ItemName.wrench, player, 1))) + else: + connect(world, player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region) + + # KAOS Kore Connections + kaos_kore_levels = [ + level_list[30], + level_list[31], + level_list[32], + level_list[33], + level_list[34], + LocationName.kastle_kaos_region, + LocationName.sewer_stockpile_region, + ] + + for i in range(0, len(kaos_kore_levels)): + connect(world, player, names, LocationName.kaos_kore_region, kaos_kore_levels[i]) + + # Krematoa Connections + krematoa_levels = [ + level_list[35], + level_list[36], + level_list[37], + level_list[38], + LocationName.rocket_rush_region, + ] + + for i in range(0, len(krematoa_levels)): + connect(world, player, names, LocationName.krematoa_region, krematoa_levels[i], + lambda state: (state.has(ItemName.bonus_coin, player, world.krematoa_bonus_coin_cost[player].value * (i+1)))) + + connect(world, player, names, LocationName.krematoa_region, LocationName.knautilus_region, + lambda state: (state.has(ItemName.krematoa_cog, player, 5))) + + +def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None, exits=None): + # Shamelessly stolen from the ROR2 definition + ret = Region(name, None, name, player) + ret.world = world + if locations: + for locationName, locationData in locations.items(): + loc_id = active_locations.get(locationName, 0) + if loc_id: + loc_byte = locationData[0] if (len(locationData) > 0) else 0 + loc_bit = locationData[1] if (len(locationData) > 1) else 0 + loc_invert = locationData[2] if (len(locationData) > 2) else False + + location = DKC3Location(player, locationName, loc_id, ret, loc_byte, loc_bit, loc_invert) + ret.locations.append(location) + if exits: + for exit in exits: + ret.exits.append(Entrance(player, exit, ret)) + + return ret + + +def connect(world: MultiWorld, player: int, used_names: typing.Dict[str, int], source: str, target: str, + rule: typing.Optional[typing.Callable] = None): + source_region = world.get_region(source, player) + target_region = world.get_region(target, player) + + if target not in used_names: + used_names[target] = 1 + name = target + else: + used_names[target] += 1 + name = target + (' ' * used_names[target]) + + connection = Entrance(player, name, source_region) + + if rule: + connection.access_rule = rule + + source_region.exits.append(connection) + connection.connect(target_region) diff --git a/worlds/dkc3/Rom.py b/worlds/dkc3/Rom.py new file mode 100644 index 00000000..761161ee --- /dev/null +++ b/worlds/dkc3/Rom.py @@ -0,0 +1,553 @@ +import Utils +from Patch import read_rom +from .Locations import lookup_id_to_name, all_locations +from .Levels import level_list, level_dict + +USHASH = '120abf304f0c40fe059f6a192ed4f947' +ROM_PLAYER_LIMIT = 65535 + +import hashlib +import os +import math + + +location_rom_data = { + 0xDC3000: [0x657, 1], # Lakeside Limbo + 0xDC3001: [0x657, 2], + 0xDC3002: [0x657, 3], + 0xDC3003: [0x657, 5], + + 0xDC3004: [0x65A, 1], # Doorstop Dash + 0xDC3005: [0x65A, 2], + 0xDC3006: [0x65A, 3], + 0xDC3007: [0x65A, 5], + + 0xDC3008: [0x659, 1], # Tidal Trouble + 0xDC3009: [0x659, 2], + 0xDC300A: [0x659, 3], + 0xDC300B: [0x659, 5], + + 0xDC300C: [0x65D, 1], # Skidda's Row + 0xDC300D: [0x65D, 2], + 0xDC300E: [0x65D, 3], + 0xDC300F: [0x65D, 5], + + 0xDC3010: [0x65C, 1], # Murky Mill + 0xDC3011: [0x65C, 2], + 0xDC3012: [0x65C, 3], + 0xDC3013: [0x65C, 5], + + + 0xDC3014: [0x662, 1], # Barrel Shield Bust-Up + 0xDC3015: [0x662, 2], + 0xDC3016: [0x662, 3], + 0xDC3017: [0x662, 5], + + 0xDC3018: [0x664, 1], # Riverside Race + 0xDC3019: [0x664, 2], + 0xDC301A: [0x664, 3], + 0xDC301B: [0x664, 5], + + 0xDC301C: [0x65B, 1], # Squeals on Wheels + 0xDC301D: [0x65B, 2], + 0xDC301E: [0x65B, 3], + 0xDC301F: [0x65B, 5], + + 0xDC3020: [0x661, 1], # Springin' Spiders + 0xDC3021: [0x661, 2], + 0xDC3022: [0x661, 3], + 0xDC3023: [0x661, 5], + + 0xDC3024: [0x666, 1], # Bobbing Barrel Brawl + 0xDC3025: [0x666, 2], + 0xDC3026: [0x666, 3], + 0xDC3027: [0x666, 5], + + + 0xDC3028: [0x667, 1], # Bazza's Blockade + 0xDC3029: [0x667, 2], + 0xDC302A: [0x667, 3], + 0xDC302B: [0x667, 5], + + 0xDC302C: [0x66A, 1], # Rocket Barrel Ride + 0xDC302D: [0x66A, 2], + 0xDC302E: [0x66A, 3], + 0xDC302F: [0x66A, 5], + + 0xDC3030: [0x658, 1], # Kreeping Klasps + 0xDC3031: [0x658, 2], + 0xDC3032: [0x658, 3], + 0xDC3033: [0x658, 5], + + 0xDC3034: [0x66B, 1], # Tracker Barrel Trek + 0xDC3035: [0x66B, 2], + 0xDC3036: [0x66B, 3], + 0xDC3037: [0x66B, 5], + + 0xDC3038: [0x668, 1], # Fish Food Frenzy + 0xDC3039: [0x668, 2], + 0xDC303A: [0x668, 3], + 0xDC303B: [0x668, 5], + + + 0xDC303C: [0x66D, 1], # Fire-ball Frenzy + 0xDC303D: [0x66D, 2], + 0xDC303E: [0x66D, 3], + 0xDC303F: [0x66D, 5], + + 0xDC3040: [0x672, 1], # Demolition Drainpipe + 0xDC3041: [0x672, 2], + 0xDC3042: [0x672, 3], + 0xDC3043: [0x672, 5], + + 0xDC3044: [0x660, 1], # Ripsaw Rage + 0xDC3045: [0x660, 2], + 0xDC3046: [0x660, 3], + 0xDC3047: [0x660, 5], + + 0xDC3048: [0x66E, 1], # Blazing Bazukas + 0xDC3049: [0x66E, 2], + 0xDC304A: [0x66E, 3], + 0xDC304B: [0x66E, 5], + + 0xDC304C: [0x670, 1], # Low-G Labyrinth + 0xDC304D: [0x670, 2], + 0xDC304E: [0x670, 3], + 0xDC304F: [0x670, 5], + + + 0xDC3050: [0x673, 1], # Krevice Kreepers + 0xDC3051: [0x673, 2], + 0xDC3052: [0x673, 3], + 0xDC3053: [0x673, 5], + + 0xDC3054: [0x65F, 1], # Tearaway Toboggan + 0xDC3055: [0x65F, 2], + 0xDC3056: [0x65F, 3], + 0xDC3057: [0x65F, 5], + + 0xDC3058: [0x66C, 1], # Barrel Drop Bounce + 0xDC3059: [0x66C, 2], + 0xDC305A: [0x66C, 3], + 0xDC305B: [0x66C, 5], + + 0xDC305C: [0x66F, 1], # Krack-Shot Kroc + 0xDC305D: [0x66F, 2], + 0xDC305E: [0x66F, 3], + 0xDC305F: [0x66F, 5], + + 0xDC3060: [0x65E, 1], # Lemguin Lunge + 0xDC3061: [0x65E, 2], + 0xDC3062: [0x65E, 3], + 0xDC3063: [0x65E, 5], + + + 0xDC3064: [0x676, 1], # Buzzer Barrage + 0xDC3065: [0x676, 2], + 0xDC3066: [0x676, 3], + 0xDC3067: [0x676, 5], + + 0xDC3068: [0x674, 1], # Kong-Fused Cliffs + 0xDC3069: [0x674, 2], + 0xDC306A: [0x674, 3], + 0xDC306B: [0x674, 5], + + 0xDC306C: [0x669, 1], # Floodlit Fish + 0xDC306D: [0x669, 2], + 0xDC306E: [0x669, 3], + 0xDC306F: [0x669, 5], + + 0xDC3070: [0x677, 1], # Pothole Panic + 0xDC3071: [0x677, 2], + 0xDC3072: [0x677, 3], + 0xDC3073: [0x677, 5], + + 0xDC3074: [0x675, 1], # Ropey Rumpus + 0xDC3075: [0x675, 2], + 0xDC3076: [0x675, 3], + 0xDC3077: [0x675, 5], + + + 0xDC3078: [0x67A, 1], # Konveyor Rope Klash + 0xDC3079: [0x67A, 2], + 0xDC307A: [0x67A, 3], + 0xDC307B: [0x67A, 5], + + 0xDC307C: [0x678, 1], # Creepy Caverns + 0xDC307D: [0x678, 2], + 0xDC307E: [0x678, 3], + 0xDC307F: [0x678, 5], + + 0xDC3080: [0x665, 1], # Lightning Lookout + 0xDC3081: [0x665, 2], + 0xDC3082: [0x665, 3], + 0xDC3083: [0x665, 5], + + 0xDC3084: [0x679, 1], # Koindozer Klamber + 0xDC3085: [0x679, 2], + 0xDC3086: [0x679, 3], + 0xDC3087: [0x679, 5], + + 0xDC3088: [0x671, 1], # Poisonous Pipeline + 0xDC3089: [0x671, 2], + 0xDC308A: [0x671, 3], + 0xDC308B: [0x671, 5], + + + 0xDC308C: [0x67B, 1], # Stampede Sprint + 0xDC308D: [0x67B, 2], + 0xDC308E: [0x67B, 3], + 0xDC308F: [0x67B, 4], + 0xDC3090: [0x67B, 5], + + 0xDC3091: [0x67C, 1], # Criss Kross Cliffs + 0xDC3092: [0x67C, 2], + 0xDC3093: [0x67C, 3], + 0xDC3094: [0x67C, 5], + + 0xDC3095: [0x67D, 1], # Tyrant Twin Tussle + 0xDC3096: [0x67D, 2], + 0xDC3097: [0x67D, 3], + 0xDC3098: [0x67D, 4], + 0xDC3099: [0x67D, 5], + + 0xDC309A: [0x663, 1], # Swoopy Salvo + 0xDC309B: [0x663, 2], + 0xDC309C: [0x663, 3], + 0xDC309D: [0x663, 4], + 0xDC309E: [0x663, 5], + + 0xDC309F: [0x67E, 1], # Rocket Rush + 0xDC30A0: [0x67E, 5], + + 0xDC30A1: [0x64F, 1], # Bosses + 0xDC30A2: [0x650, 1], + 0xDC30A3: [0x651, 1], + 0xDC30A4: [0x652, 1], + 0xDC30A5: [0x653, 1], + 0xDC30A6: [0x654, 1], + 0xDC30A7: [0x655, 1], + 0xDC30A8: [0x656, 1], + + 0xDC30A9: [0x647, 1], # Banana Bird Caves + 0xDC30AA: [0x645, 1], + 0xDC30AB: [0x644, 1], + 0xDC30AC: [0x642, 1], + 0xDC30AD: [0x643, 1], + 0xDC30AE: [0x646, 1], + 0xDC30AF: [0x648, 1], + 0xDC30B0: [0x649, 1], + 0xDC30B1: [0x64A, 1], + 0xDC30B2: [0x64B, 1], + 0xDC30B3: [0x64C, 1], + 0xDC30B4: [0x64D, 1], + 0xDC30B5: [0x64E, 1], + + 0xDC30B6: [0x5FD, 4], # Banana Bird Mother + + # DKC3_TODO: Disabled until Trade Sequence + #0xDC30B7: [0x615, 2, True], + #0xDC30B8: [0x615, 3, True], + #0xDC30B9: [0x619, 2], + ##0xDC30BA: + #0xDC30BB: [0x61B, 3], + #0xDC30BC: [0x61D, 2], + #0xDC30BD: [0x621, 4], + #0xDC30BE: [0x625, 4, True], +} + + +item_rom_data = { + 0xDC3001: [0x5D5], # 1-Up Balloon + 0xDC3002: [0x5C9], # Bear Coin + 0xDC3003: [0x5CB], # Bonus Coin + 0xDC3004: [0x5CF], # DK Coin + 0xDC3005: [0x5CD], # Banana Bird + 0xDC3006: [0x5D1, 0x603], # Cog +} + +music_rom_data = [ + 0x3D06B1, + 0x3D0753, + 0x3D071D, + 0x3D07FA, + 0x3D07C4, + + 0x3D08FE, + 0x3D096C, + 0x3D078E, + 0x3D08CD, + 0x3D09DD, + + 0x3D0A0E, + 0x3D0AB3, + 0x3D06E7, + 0x3D0AE4, + 0x3D0A45, + + 0x3D0B46, + 0x3D0C40, + 0x3D0897, + 0x3D0B77, + 0x3D0BD9, + + 0x3D0C71, + 0x3D0866, + 0x3D0B15, + 0x3D0BA8, + 0x3D0830, + + 0x3D0D04, + 0x3D0CA2, + 0x3D0A7C, + 0x3D0D35, + 0x3D0CD3, + + 0x3D0DC8, + 0x3D0D66, + 0x3D09AC, + 0x3D0D97, + 0x3D0C0F, + + 0x3D0DF9, + 0x3D0E31, + 0x3D0E62, + 0x3D0934, + 0x3D0E9A, +] + +level_music_ids = [ + 0x06, + 0x07, + 0x08, + 0x0A, + 0x0B, + 0x0E, + 0x0F, + 0x10, + 0x17, + 0x19, + 0x1C, + 0x1D, + 0x1E, + 0x21, +] + +class LocalRom(object): + + def __init__(self, file, patch=True, vanillaRom=None, name=None, hash=None): + self.name = name + self.hash = hash + self.orig_buffer = None + + with open(file, 'rb') as stream: + self.buffer = read_rom(stream) + #if patch: + # self.patch_rom() + # self.orig_buffer = self.buffer.copy() + #if vanillaRom: + # with open(vanillaRom, 'rb') as vanillaStream: + # self.orig_buffer = read_rom(vanillaStream) + + def read_bit(self, address: int, bit_number: int) -> bool: + bitflag = (1 << bit_number) + return ((self.buffer[address] & bitflag) != 0) + + def read_byte(self, address: int) -> int: + return self.buffer[address] + + def read_bytes(self, startaddress: int, length: int) -> bytes: + return self.buffer[startaddress:startaddress + length] + + def write_byte(self, address: int, value: int): + self.buffer[address] = value + + def write_bytes(self, startaddress: int, values): + self.buffer[startaddress:startaddress + len(values)] = values + + def write_to_file(self, file): + with open(file, 'wb') as outfile: + outfile.write(self.buffer) + + def read_from_file(self, file): + with open(file, 'rb') as stream: + self.buffer = bytearray(stream.read()) + + + +def patch_rom(world, rom, player, active_level_list): + local_random = world.slot_seeds[player] + + # Boomer Costs + bonus_coin_cost = world.krematoa_bonus_coin_cost[player] + inverted_bonus_coin_cost = 0x100 - bonus_coin_cost + rom.write_byte(0x3498B9, inverted_bonus_coin_cost) + rom.write_byte(0x3498BA, inverted_bonus_coin_cost) + rom.write_byte(0x3498BB, inverted_bonus_coin_cost) + rom.write_byte(0x3498BC, inverted_bonus_coin_cost) + rom.write_byte(0x3498BD, inverted_bonus_coin_cost) + + rom.write_byte(0x349857, bonus_coin_cost) + rom.write_byte(0x349862, bonus_coin_cost) + + # Gyrocopter Costs + dk_coin_cost = world.dk_coins_for_gyrocopter[player] + rom.write_byte(0x3484A6, dk_coin_cost) + rom.write_byte(0x3484D5, dk_coin_cost) + rom.write_byte(0x3484D7, 0x90) + rom.write_byte(0x3484DC, 0xEA) + rom.write_byte(0x3484DD, 0xEA) + rom.write_byte(0x3484DE, 0xEA) + rom.write_byte(0x348528, 0x80) # Prevent Single-Ski Lock + + + # Make Swanky free + rom.write_byte(0x348C48, 0x00) + + # Banana Bird Costs + if world.goal[player] == "banana_bird_hunt": + banana_bird_cost = math.floor(world.number_of_banana_birds[player] * world.percentage_of_banana_birds[player] / 100.0) + rom.write_byte(0x34AB85, banana_bird_cost) + rom.write_byte(0x329FD8, banana_bird_cost) + rom.write_byte(0x32A025, banana_bird_cost) + rom.write_byte(0x329FDA, 0xB0) + else: + # rom.write_byte(0x34AB84, 0x20) # These cause hangs at Wrinkly's + # rom.write_byte(0x329FD8, 0x20) + # rom.write_byte(0x32A025, 0x20) + rom.write_byte(0x329FDA, 0xB0) + + # Baffle Mirror Fix + rom.write_byte(0x9133, 0x08) + rom.write_byte(0x9135, 0x0C) + rom.write_byte(0x9136, 0x2B) + rom.write_byte(0x9137, 0x06) + + # Palette Swap + rom.write_byte(0x3B96A5, 0xD0) + if world.kong_palette_swap[player] == "default": + rom.write_byte(0x3B96A9, 0x00) + rom.write_byte(0x3B96A8, 0x00) + elif world.kong_palette_swap[player] == "purple": + rom.write_byte(0x3B96A9, 0x00) + rom.write_byte(0x3B96A8, 0x3C) + elif world.kong_palette_swap[player] == "spooky": + rom.write_byte(0x3B96A9, 0x00) + rom.write_byte(0x3B96A8, 0xA0) + elif world.kong_palette_swap[player] == "dark": + rom.write_byte(0x3B96A9, 0x05) + rom.write_byte(0x3B96A8, 0xA0) + elif world.kong_palette_swap[player] == "chocolate": + rom.write_byte(0x3B96A9, 0x1D) + rom.write_byte(0x3B96A8, 0xA0) + elif world.kong_palette_swap[player] == "shadow": + rom.write_byte(0x3B96A9, 0x45) + rom.write_byte(0x3B96A8, 0xA0) + elif world.kong_palette_swap[player] == "red_gold": + rom.write_byte(0x3B96A9, 0x5D) + rom.write_byte(0x3B96A8, 0xA0) + elif world.kong_palette_swap[player] == "gbc": + rom.write_byte(0x3B96A9, 0x20) + rom.write_byte(0x3B96A8, 0x3C) + elif world.kong_palette_swap[player] == "halloween": + rom.write_byte(0x3B96A9, 0x70) + rom.write_byte(0x3B96A8, 0x3C) + + if world.music_shuffle[player]: + for address in music_rom_data: + rand_song = local_random.choice(level_music_ids) + rom.write_byte(address, rand_song) + + # Starting Lives + rom.write_byte(0x9130, world.starting_life_count[player].value) + rom.write_byte(0x913B, world.starting_life_count[player].value) + + + # Handle Level Shuffle Here + if world.level_shuffle[player]: + for i in range(len(active_level_list)): + rom.write_byte(level_dict[level_list[i]].nameIDAddress, level_dict[active_level_list[i]].nameID) + rom.write_byte(level_dict[level_list[i]].levelIDAddress, level_dict[active_level_list[i]].levelID) + + # First levels of each world + rom.write_byte(0x34BC3E, (0x32 + level_dict[active_level_list[0]].levelID)) + rom.write_byte(0x34BC47, (0x32 + level_dict[active_level_list[5]].levelID)) + rom.write_byte(0x34BC4A, (0x32 + level_dict[active_level_list[10]].levelID)) + rom.write_byte(0x34BC53, (0x32 + level_dict[active_level_list[15]].levelID)) + rom.write_byte(0x34BC59, (0x32 + level_dict[active_level_list[20]].levelID)) + rom.write_byte(0x34BC5C, (0x32 + level_dict[active_level_list[25]].levelID)) + rom.write_byte(0x34BC65, (0x32 + level_dict[active_level_list[30]].levelID)) + rom.write_byte(0x34BC6E, (0x32 + level_dict[active_level_list[35]].levelID)) + + # Cotton-Top Cove Boss Unlock + rom.write_byte(0x34C02A, (0x32 + level_dict[active_level_list[14]].levelID)) + + # Kong-Fused Cliffs Unlock + rom.write_byte(0x34C213, (0x32 + level_dict[active_level_list[25]].levelID)) + rom.write_byte(0x34C21B, (0x32 + level_dict[active_level_list[26]].levelID)) + + if world.goal[player] == "knautilus": + # Swap Kastle KAOS and Knautilus + rom.write_byte(0x34D4E1, 0xC2) + rom.write_byte(0x34D4E2, 0x24) + rom.write_byte(0x34D551, 0xBA) + rom.write_byte(0x34D552, 0x23) + + rom.write_byte(0x32F339, 0x55) + + + from Main import __version__ + rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21] + rom.name.extend([0] * (21 - len(rom.name))) + rom.write_bytes(0x7FC0, rom.name) + + # DKC3_TODO: This is a hack, reconsider + # Don't grant (DK, Bonus, Bear) Coins + rom.write_byte(0x3BD454, 0xEA) + rom.write_byte(0x3BD455, 0xEA) + + # Don't grant Cogs + rom.write_byte(0x3BD574, 0xEA) + rom.write_byte(0x3BD575, 0xEA) + rom.write_byte(0x3BD576, 0xEA) + + # Don't grant Banana Birds at their caves + rom.write_byte(0x32DD62, 0xEA) + rom.write_byte(0x32DD63, 0xEA) + rom.write_byte(0x32DD64, 0xEA) + + # Don't grant Patch and Skis from their bosses + rom.write_byte(0x3F3762, 0x00) + rom.write_byte(0x3F377B, 0x00) + rom.write_byte(0x3F3797, 0x00) + + # Always allow Start+Select + rom.write_byte(0x8BAB, 0x01) + + # Handle Alt Palettes in Krematoa + rom.write_byte(0x3B97E9, 0x80) + rom.write_byte(0x3B97EA, 0xEA) + + + +def get_base_rom_bytes(file_name: str = "") -> bytes: + base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + file_name = get_base_rom_path(file_name) + base_rom_bytes = bytes(read_rom(open(file_name, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if USHASH != basemd5.hexdigest(): + raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. ' + 'Get the correct game and version, then dump it') + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + +def get_base_rom_path(file_name: str = "") -> str: + options = Utils.get_options() + if not file_name: + file_name = options["dkc3_options"]["rom_file"] + if not os.path.exists(file_name): + file_name = Utils.local_path(file_name) + return file_name diff --git a/worlds/dkc3/Rules.py b/worlds/dkc3/Rules.py new file mode 100644 index 00000000..dcfc9124 --- /dev/null +++ b/worlds/dkc3/Rules.py @@ -0,0 +1,32 @@ +import math + +from BaseClasses import MultiWorld +from .Names import LocationName, ItemName +from ..AutoWorld import LogicMixin +from ..generic.Rules import add_rule, set_rule + + +def set_rules(world: MultiWorld, player: int): + + if False:#world.include_trade_sequence[player]: + add_rule(world.get_location(LocationName.barnacles_island, player), + lambda state: state.has(ItemName.shell, player)) + + add_rule(world.get_location(LocationName.blues_beach_hut, player), + lambda state: state.has(ItemName.present, player)) + + add_rule(world.get_location(LocationName.brambles_bungalow, player), + lambda state: state.has(ItemName.flower, player)) + + add_rule(world.get_location(LocationName.barters_swap_shop, player), + lambda state: state.has(ItemName.mirror, player)) + + + if world.goal[player] != "knautilus": + required_banana_birds = math.floor( + world.number_of_banana_birds[player].value * (world.percentage_of_banana_birds[player].value / 100.0)) + + add_rule(world.get_location(LocationName.banana_bird_mother, player), + lambda state: state.has(ItemName.banana_bird, player, required_banana_birds)) + + world.completion_condition[player] = lambda state: state.has(ItemName.victory, player) diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py new file mode 100644 index 00000000..54087db9 --- /dev/null +++ b/worlds/dkc3/__init__.py @@ -0,0 +1,204 @@ +import os +import typing +import math +import threading + +from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification +from .Items import DKC3Item, ItemData, item_table, inventory_table +from .Locations import DKC3Location, all_locations, setup_locations +from .Options import dkc3_options +from .Regions import create_regions, connect_regions +from .Levels import level_list +from .Rules import set_rules +from .Names import ItemName, LocationName +from ..AutoWorld import WebWorld, World +from .Rom import LocalRom, patch_rom, get_base_rom_path +import Patch + + +class DKC3Web(WebWorld): + theme = "jungle" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Donkey Kong Country 3 randomizer connected to an Archipelago Multiworld.", + "English", + "setup_en.md", + "setup/en", + ["PoryGone"] + ) + + tutorials = [setup_en] + + +class DKC3World(World): + """ + Donkey Kong Country 3 is an action platforming game. + Play as Dixie Kong and her baby cousin Kiddy as they try to solve the + mystery of why Donkey Kong and Diddy disappeared while on vacation. + """ + game: str = "Donkey Kong Country 3" + options = dkc3_options + topology_present = False + data_version = 1 + #hint_blacklist = {LocationName.rocket_rush_flag} + + item_name_to_id = {name: data.code for name, data in item_table.items()} + location_name_to_id = all_locations + + active_level_list: typing.List[str] + web = DKC3Web() + + def __init__(self, world: MultiWorld, player: int): + self.rom_name_available_event = threading.Event() + super().__init__(world, player) + + @classmethod + def stage_assert_generate(cls, world): + rom_file = get_base_rom_path() + if not os.path.exists(rom_file): + raise FileNotFoundError(rom_file) + + def _get_slot_data(self): + return { + #"death_link": self.world.death_link[self.player].value, + "active_levels": self.active_level_list, + } + + def _create_items(self, name: str): + data = item_table[name] + return [self.create_item(name)] * data.quantity + + def fill_slot_data(self) -> dict: + slot_data = self._get_slot_data() + for option_name in dkc3_options: + option = getattr(self.world, option_name)[self.player] + slot_data[option_name] = option.value + + return slot_data + + def generate_basic(self): + self.topology_present = self.world.level_shuffle[self.player].value + itempool: typing.List[DKC3Item] = [] + + # Levels + total_required_locations = 161 + + number_of_banana_birds = 0 + # Rocket Rush Cog + total_required_locations -= 1 + number_of_cogs = 4 + self.world.get_location(LocationName.rocket_rush_flag, self.player).place_locked_item(self.create_item(ItemName.krematoa_cog)) + number_of_bosses = 8 + if self.world.goal[self.player] == "knautilus": + self.world.get_location(LocationName.kastle_kaos, self.player).place_locked_item(self.create_item(ItemName.victory)) + number_of_bosses = 7 + else: + self.world.get_location(LocationName.banana_bird_mother, self.player).place_locked_item(self.create_item(ItemName.victory)) + number_of_banana_birds = self.world.number_of_banana_birds[self.player] + + # Bosses + total_required_locations += number_of_bosses + + # Secret Caves + total_required_locations += 13 + + ## Brothers Bear + if False:#self.world.include_trade_sequence[self.player]: + total_required_locations += 8 + + number_of_bonus_coins = (self.world.krematoa_bonus_coin_cost[self.player] * 5) + number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.world.percentage_of_extra_bonus_coins[self.player] / 100) + + itempool += [self.create_item(ItemName.bonus_coin)] * number_of_bonus_coins + itempool += [self.create_item(ItemName.dk_coin)] * 41 + itempool += [self.create_item(ItemName.banana_bird)] * number_of_banana_birds + itempool += [self.create_item(ItemName.krematoa_cog)] * number_of_cogs + itempool += [self.create_item(ItemName.progressive_boat)] * 3 + + total_junk_count = total_required_locations - len(itempool) + + itempool += [self.create_item(ItemName.bear_coin)] * total_junk_count + + self.active_level_list = level_list.copy() + + if self.world.level_shuffle[self.player]: + self.world.random.shuffle(self.active_level_list) + + connect_regions(self.world, self.player, self.active_level_list) + + self.world.itempool += itempool + + def generate_output(self, output_directory: str): + try: + world = self.world + player = self.player + + rom = LocalRom(get_base_rom_path()) + patch_rom(self.world, rom, self.player, self.active_level_list) + + self.active_level_list.append(LocationName.rocket_rush_region) + + outfilepname = f'_P{player}' + outfilepname += f"_{world.player_name[player].replace(' ', '_')}" \ + if world.player_name[player] != 'Player%d' % player else '' + + rompath = os.path.join(output_directory, f'AP_{world.seed_name}{outfilepname}.sfc') + rom.write_to_file(rompath) + Patch.create_patch_file(rompath, player=player, player_name=world.player_name[player], game=Patch.GAME_DKC3) + os.unlink(rompath) + self.rom_name = rom.name + except: + raise + finally: + self.rom_name_available_event.set() # make sure threading continues and errors are collected + + def modify_multidata(self, multidata: dict): + import base64 + # wait for self.rom_name to be available. + self.rom_name_available_event.wait() + rom_name = getattr(self, "rom_name", None) + # we skip in case of error, so that the original error in the output thread is the one that gets raised + if rom_name: + new_name = base64.b64encode(bytes(self.rom_name)).decode() + multidata["connect_names"][new_name] = multidata["connect_names"][self.world.player_name[self.player]] + + if self.topology_present: + world_names = [ + LocationName.lake_orangatanga_region, + LocationName.kremwood_forest_region, + LocationName.cotton_top_cove_region, + LocationName.mekanos_region, + LocationName.k3_region, + LocationName.razor_ridge_region, + LocationName.kaos_kore_region, + LocationName.krematoa_region, + ] + er_hint_data = {} + for world_index in range(len(world_names)): + for level_index in range(5): + level_region = self.world.get_region(self.active_level_list[world_index * 5 + level_index], self.player) + for location in level_region.locations: + er_hint_data[location.address] = world_names[world_index] + multidata['er_hint_data'][self.player] = er_hint_data + + def create_regions(self): + location_table = setup_locations(self.world, self.player) + create_regions(self.world, self.player, location_table) + + def create_item(self, name: str, force_non_progression=False) -> Item: + data = item_table[name] + + if force_non_progression: + classification = ItemClassification.filler + elif data.progression: + classification = ItemClassification.progression + else: + classification = ItemClassification.filler + + created_item = DKC3Item(name, classification, data.code, self.player) + + return created_item + + def set_rules(self): + set_rules(self.world, self.player) diff --git a/worlds/dkc3/docs/en_Donkey Kong Country 3.md b/worlds/dkc3/docs/en_Donkey Kong Country 3.md new file mode 100644 index 00000000..2041f0a4 --- /dev/null +++ b/worlds/dkc3/docs/en_Donkey Kong Country 3.md @@ -0,0 +1,35 @@ +# Donkey Kong Country 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. + +## What is the goal of Donkey Kong Country 3 when randomized? + +There are two goals which can be chosen: +- `Knautilus`: Collect Bonus Coins and Krematoa Cogs to reach K. Rool's submarine in Krematoa +- `Banana Bird Hunt`: Collect Banana Birds to free the Banana Bird Mother + +## What items and locations get shuffled? + +All Bonus Coins, DK Coins, and Banana Birds (if on a `Banana Bird Hunt` goal) are randomized. Additionally, level clears award a location check. +The Patch and two Skis for upgrading the boat are included. Bear Coins are provided if additional items are needed for the item pool. +Four of the Five Krematoa Cogs are randomized, but the final one is always in its vanilla location at the Flag of Rocket Rush in Krematoa + +## 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 Donkey Kong Country 3 + +Items pickups all retain their original appearance. You won't know if an item belongs to another player until you collect. + +## When the player receives an item, what happens? + +Currently, the items are silently added to the player's inventory, which can be seen when saving the game. diff --git a/worlds/dkc3/docs/setup_en.md b/worlds/dkc3/docs/setup_en.md new file mode 100644 index 00000000..34a297ea --- /dev/null +++ b/worlds/dkc3/docs/setup_en.md @@ -0,0 +1,161 @@ +# Donkey Kong Country 3 Randomizer Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). Make sure to check the box for `SNI Client - Donkey Kong Country 3 Patch Setup` + + +- Hardware or software capable of loading and playing SNES ROM files + - An emulator capable of connecting to SNI such as: + - snes9x Multitroid + from: [snes9x Multitroid Download](https://drive.google.com/drive/folders/1_ej-pwWtCAHYXIrvs5Hro16A1s9Hi3Jz), + - BizHawk from: [BizHawk Website](http://tasvideos.org/BizHawk.html) + - RetroArch 1.10.3 or newer from: [RetroArch Website](https://retroarch.com?page=platforms). Or, + - An SD2SNES, FXPak Pro ([FXPak Pro Store Page](https://krikzz.com/store/home/54-fxpak-pro.html)), or other + compatible hardware +- Your legally obtained Donkey Kong Country 3 ROM file, probably named `Donkey Kong Country 3 - Dixie Kong's Double Trouble! (USA) (En,Fr).sfc` + +## Installation Procedures + +### Windows Setup + +1. During the installation of Archipelago, you will have been asked to install the SNI Client. If you did not do this, + or you are on an older version, you may run the installer again to install the SNI Client. +2. During setup, you will be asked to locate your base ROM file. This is your Donkey Kong Country 3 ROM file. +3. If you are using an emulator, you should assign your Lua capable emulator as your default program for launching ROM + files. + 1. Extract your emulator's folder to your Desktop, or somewhere you will remember. + 2. Right-click on a ROM file and select **Open with...** + 3. Check the box next to **Always use this app to open .sfc files** + 4. Scroll to the bottom of the list and click the grey text **Look for another App on this PC** + 5. Browse for your emulator's `.exe` file and click **Open**. This file should be located inside the folder you + extracted in step one. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +See the guide on setting up a basic YAML at the Archipelago setup +guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) + +### Where do I get a config file? + +The Player Settings page on the website allows you to configure your personal settings and export a config file from +them. Player settings page: [Donkey Kong Country 3 Player Settings Page](/games/Donkey%20Kong%20Country%203/player-settings) + +### Verifying your config file + +If you would like to validate your config file to make sure it works, you may do so on the YAML Validator page. YAML +validator page: [YAML Validation page](/mysterycheck) + +## Generating a Single-Player Game + +1. Navigate to the Player Settings page, configure your options, and click the "Generate Game" button. + - Player Settings page: [Donkey Kong Country 3 Player Settings Page](/games/Donkey%20Kong%20Country%203/player-settings) +2. You will be presented with a "Seed Info" page. +3. Click the "Create New Room" link. +4. You will be presented with a server page, from which you can download your patch file. +5. Double-click on your patch file, and the Donkey Kong Country 3 Client will launch automatically, create your ROM from the + patch file, and open your emulator for you. +6. Since this is a single-player game, you will no longer need the client, so feel free to close it. + +## Joining a MultiWorld Game + +### Obtain your patch file and create your ROM + +When you join a multiworld game, you will be asked to provide your config file to whoever is hosting. Once that is done, +the host will provide you with either a link to download your patch file, or with a zip file containing everyone's patch +files. Your patch file should have a `.apsm` extension. + +Put your patch file on your desktop or somewhere convenient, and double click it. This should automatically launch the +client, and will also create your ROM in the same place as your patch file. + +### Connect to the client + +#### With an emulator + +When the client launched automatically, SNI should have also automatically launched in the background. If this is its +first time launching, you may be prompted to allow it to communicate through the Windows Firewall. + +##### snes9x Multitroid + +1. Load your ROM file if it hasn't already been loaded. +2. Click on the File menu and hover on **Lua Scripting** +3. Click on **New Lua Script Window...** +4. In the new window, click **Browse...** +5. Select the connector lua file included with your client + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. +6. If you see an error while loading the script that states `socket.dll missing` or similar, navigate to the folder of +the lua you are using in your file explorer and copy the `socket.dll` to the base folder of your snes9x install. + +##### BizHawk + +1. Ensure you have the BSNES core loaded. You may do this by clicking on the Tools menu in BizHawk and following these + menu options: + `Config --> Cores --> SNES --> BSNES` + Once you have changed the loaded core, you must restart BizHawk. +2. Load your ROM file if it hasn't already been loaded. +3. Click on the Tools menu and click on **Lua Console** +4. Click the button to open a new Lua script. +5. Select the `Connector.lua` file included with your client + - Look in the Archipelago folder for `/SNI/lua/x64` or `/SNI/lua/x86` depending on if the + emulator is 64-bit or 32-bit. Please note the most recent versions of BizHawk are 64-bit only. + +##### RetroArch 1.10.3 or newer + +You only have to do these steps once. Note, RetroArch 1.9.x will not work as it is older than 1.10.3. + +1. Enter the RetroArch main menu screen. +2. Go to Settings --> User Interface. Set "Show Advanced Settings" to ON. +3. Go to Settings --> Network. Set "Network Commands" to ON. (It is found below Request Device 16.) Leave the default + Network Command Port at 55355. + +![Screenshot of Network Commands setting](/static/generated/docs/A%20Link%20to%20the%20Past/retroarch-network-commands-en.png) +4. Go to Main Menu --> Online Updater --> Core Downloader. Scroll down and select "Nintendo - SNES / SFC (bsnes-mercury + Performance)". + +When loading a ROM, be sure to select a **bsnes-mercury** core. These are the only cores that allow external tools to +read ROM data. + +#### With hardware + +This guide assumes you have downloaded the correct firmware for your device. If you have not done so already, please do +this now. SD2SNES and FXPak Pro users may download the appropriate firmware on the SD2SNES releases page. SD2SNES +releases page: [SD2SNES Releases Page](https://github.com/RedGuyyyy/sd2snes/releases) + +Other hardware may find helpful information on the usb2snes platforms +page: [usb2snes Supported Platforms Page](http://usb2snes.com/#supported-platforms) + +1. Close your emulator, which may have auto-launched. +2. Power on your device and load the ROM. + +### Connect to the Archipelago Server + +The patch file which launched your client should have automatically connected you to the AP Server. There are a few +reasons this may not happen however, including if the game is hosted on the website but was generated elsewhere. If the +client window shows "Server Status: Not Connected", simply ask the host for the address of the server, and copy/paste it +into the "Server" input field then press enter. + +The client will attempt to reconnect to the new server address, and should momentarily show "Server Status: Connected". + +### Play the game + +When the client shows both SNES Device and Server as connected, you're ready to begin playing. Congratulations on +successfully joining a multiworld game! + +## Hosting a MultiWorld game + +The recommended way to host a game is to use our hosting service. The process is relatively simple: + +1. Collect config files from your players. +2. Create a zip file containing your players' config files. +3. Upload that zip file to the Generate page above. + - Generate page: [WebHost Seed Generation Page](/generate) +4. Wait a moment while the seed is generated. +5. When the seed is generated, you will be redirected to a "Seed Info" page. +6. Click "Create New Room". This will take you to the server page. Provide the link to this page to your players, so + they may download their patch files from there. +7. Note that a link to a MultiWorld Tracker is at the top of the room page. The tracker shows the progress of all + players in the game. Any observers may also be given the link to this page. +8. Once all players have joined, you may begin playing.