From dc2aa5f41e208168fe36848ba69dd1919f46901c Mon Sep 17 00:00:00 2001 From: PoryGone <98504756+PoryGone@users.noreply.github.com> Date: Sun, 29 Jan 2023 23:53:56 -0500 Subject: [PATCH] SMW: v1.1 Content Update (#1344) * Make Bowser unkillable on Egg Hunt * Increment Data Package version Changed a location name. * Baseline for Bowser Rooms shuffling * Add boss shuffle * Remove extra space * Overworld Palette Shuffle * Fix Literature Trap typo * Handle Queuing traps and new Timer Trap * Fix trap name and actually create them * Early Climb and Overworld Speed * Add correct tooltip for Early Climb * Tooltip text edit * Address unconnected regions * Add option to fully exclude Special Zone levels from the seed * Fix Chocolate Island 4 Dragon Coins logic * Update worlds/smw/Client.py to use `getattr` --- worlds/smw/Aesthetics.py | 15 ++++ worlds/smw/Client.py | 82 +++++++++++++++++- worlds/smw/Items.py | 1 + worlds/smw/Levels.py | 99 +++++++++++++++++++-- worlds/smw/Locations.py | 22 +++++ worlds/smw/Names/ItemName.py | 1 + worlds/smw/Names/LiteratureTrap.py | 2 +- worlds/smw/Options.py | 84 +++++++++++++++++- worlds/smw/Regions.py | 20 +---- worlds/smw/Rom.py | 133 ++++++++++++++++++++++++----- worlds/smw/__init__.py | 20 ++++- 11 files changed, 423 insertions(+), 56 deletions(-) diff --git a/worlds/smw/Aesthetics.py b/worlds/smw/Aesthetics.py index 624440c55f6..fb53295de19 100644 --- a/worlds/smw/Aesthetics.py +++ b/worlds/smw/Aesthetics.py @@ -136,6 +136,15 @@ 0xFFF45A: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07], # Castle } +valid_ow_palettes = { + 0x2D1E: [0x00, 0x01, 0x03], # Main OW + 0x2D1F: [0x00, 0x03, 0x04], # Yoshi's Island + 0x2D20: [0x00, 0x01, 0x03, 0x04], # Vanilla Dome + 0x2D21: [0x00, 0x02, 0x03, 0x04], # Forest of Illusion + 0x2D22: [0x00, 0x01, 0x03, 0x04], # Valley of Bowser + 0x2D24: [0x00, 0x02, 0x03], # Star Road +} + def generate_shuffled_level_music(world, player): shuffled_level_music = level_music_value_data.copy() @@ -158,6 +167,12 @@ def generate_shuffled_ow_music(world, player): return shuffled_ow_music +def generate_shuffled_ow_palettes(rom, world, player): + if world.overworld_palette_shuffle[player]: + for address, valid_palettes in valid_ow_palettes.items(): + chosen_palette = world.random.choice(valid_palettes) + rom.write_byte(address, chosen_palette) + def generate_shuffled_header_data(rom, world, player): if world.music_shuffle[player] != "full" and not world.foreground_palette_shuffle[player] and not world.background_palette_shuffle[player]: return diff --git a/worlds/smw/Client.py b/worlds/smw/Client.py index c2981eff8e5..92aeac4d4a2 100644 --- a/worlds/smw/Client.py +++ b/worlds/smw/Client.py @@ -169,7 +169,74 @@ async def handle_message_queue(self, ctx): await snes_flush_writes(ctx) - return + + def add_trap_to_queue(self, trap_item, trap_msg): + self.trap_queue = getattr(self, "trap_queue", []) + + self.trap_queue.append((trap_item, trap_msg)) + + + async def handle_trap_queue(self, ctx): + from SNIClient import snes_buffered_write, snes_flush_writes, snes_read + + if not hasattr(self, "trap_queue") or len(self.trap_queue) == 0: + return + + game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + if game_state[0] != 0x14: + return + + mario_state = await snes_read(ctx, SMW_MARIO_STATE_ADDR, 0x1) + if mario_state[0] != 0x00: + return + + pause_state = await snes_read(ctx, SMW_PAUSE_ADDR, 0x1) + if pause_state[0] != 0x00: + return + + next_trap, message = self.trap_queue.pop(0) + + from worlds.smw.Rom import trap_rom_data + if next_trap.item in trap_rom_data: + trap_active = await snes_read(ctx, WRAM_START + trap_rom_data[next_trap.item][0], 0x3) + + if next_trap.item == 0xBC0016: + # Timer Trap + if trap_active[0] == 0 or (trap_active[0] == 1 and trap_active[1] == 0 and trap_active[2] == 0): + # Trap already active + self.add_trap_to_queue(next_trap, message) + return + else: + snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0], bytes([0x01])) + snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0] + 1, bytes([0x00])) + snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0] + 2, bytes([0x00])) + else: + if trap_active[0] > 0: + # Trap already active + self.add_trap_to_queue(next_trap, message) + return + else: + verify_game_state = await snes_read(ctx, SMW_GAME_STATE_ADDR, 0x1) + if verify_game_state[0] == 0x14 and len(trap_rom_data[next_trap.item]) > 2: + snes_buffered_write(ctx, SMW_SFX_ADDR, bytes([trap_rom_data[next_trap.item][2]])) + + new_item_count = trap_rom_data[next_trap.item][1] + snes_buffered_write(ctx, WRAM_START + trap_rom_data[next_trap.item][0], bytes([new_item_count])) + + current_level = await snes_read(ctx, SMW_CURRENT_LEVEL_ADDR, 0x1) + if current_level[0] in SMW_BAD_TEXT_BOX_LEVELS: + return + + boss_state = await snes_read(ctx, SMW_BOSS_STATE_ADDR, 0x1) + if boss_state[0] in SMW_BOSS_STATES: + return + + active_boss = await snes_read(ctx, SMW_ACTIVE_BOSS_ADDR, 0x1) + if active_boss[0] != 0x00: + return + + if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((next_trap.flags & 1) != 0)): + self.add_message_to_queue(message) async def game_watcher(self, ctx): @@ -229,13 +296,14 @@ async def game_watcher(self, ctx): await snes_flush_writes(ctx) await self.handle_message_queue(ctx) + await self.handle_trap_queue(ctx) new_checks = [] event_data = await snes_read(ctx, SMW_EVENT_ROM_DATA, 0x60) progress_data = bytearray(await snes_read(ctx, SMW_PROGRESS_DATA, 0x0F)) dragon_coins_data = bytearray(await snes_read(ctx, SMW_DRAGON_COINS_DATA, 0x0C)) dragon_coins_active = await snes_read(ctx, SMW_DRAGON_COINS_ACTIVE_ADDR, 0x1) - from worlds.smw.Rom import item_rom_data, ability_rom_data + from worlds.smw.Rom import item_rom_data, ability_rom_data, trap_rom_data from worlds.smw.Levels import location_id_to_level_id, level_info_dict from worlds import AutoWorldRegister for loc_name, level_data in location_id_to_level_id.items(): @@ -307,7 +375,7 @@ async def game_watcher(self, ctx): ctx.location_names[item.location], recv_index, len(ctx.items_received))) if ctx.receive_option == 1 or (ctx.receive_option == 2 and ((item.flags & 1) != 0)): - if item.item != 0xBC0012: + if item.item != 0xBC0012 and item.item not in trap_rom_data: # Don't send messages for Boss Tokens item_name = ctx.item_names[item.item] player_name = ctx.player_names[item.player] @@ -316,7 +384,13 @@ async def game_watcher(self, ctx): self.add_message_to_queue(receive_message) snes_buffered_write(ctx, SMW_RECV_PROGRESS_ADDR, bytes([recv_index])) - if item.item in item_rom_data: + if item.item in trap_rom_data: + item_name = ctx.item_names[item.item] + player_name = ctx.player_names[item.player] + + receive_message = generate_received_text(item_name, player_name) + self.add_trap_to_queue(item, receive_message) + elif item.item in item_rom_data: item_count = await snes_read(ctx, WRAM_START + item_rom_data[item.item][0], 0x1) increment = item_rom_data[item.item][1] diff --git a/worlds/smw/Items.py b/worlds/smw/Items.py index e650aef4a57..5b6cce5a7f6 100644 --- a/worlds/smw/Items.py +++ b/worlds/smw/Items.py @@ -49,6 +49,7 @@ class SMWItem(Item): ItemName.ice_trap: ItemData(0xBC0013, False, True), ItemName.stun_trap: ItemData(0xBC0014, False, True), ItemName.literature_trap: ItemData(0xBC0015, False, True), + ItemName.timer_trap: ItemData(0xBC0016, False, True), } event_table = { diff --git a/worlds/smw/Levels.py b/worlds/smw/Levels.py index e61a312b8d9..3940a08c7c5 100644 --- a/worlds/smw/Levels.py +++ b/worlds/smw/Levels.py @@ -1,6 +1,80 @@ from .Names import LocationName + +class BowserRoom(): + name: str + exitAddress: int + roomID: int + + def __init__(self, name: str, exitAddress: int, roomID: int): + self.name = name + self.exitAddress = exitAddress + self.roomID = roomID + +full_bowser_rooms = [ + BowserRoom("Hallway 1 - Door 1", 0x3A680, 0x0D), + BowserRoom("Hallway 1 - Door 2", 0x3A684, 0x0D), + BowserRoom("Hallway 1 - Door 3", 0x3A688, 0x0D), + BowserRoom("Hallway 1 - Door 4", 0x3A68C, 0x0D), + BowserRoom("Hallway 2 - Door 1", 0x3A8CB, 0xD0), + BowserRoom("Hallway 2 - Door 2", 0x3A8CF, 0xD0), + BowserRoom("Hallway 2 - Door 3", 0x3A8D3, 0xD0), + BowserRoom("Hallway 2 - Door 4", 0x3A8D7, 0xD0), + + BowserRoom("Room 1", 0x3A705, 0xD4), + BowserRoom("Room 2", 0x3A763, 0xD3), + BowserRoom("Room 3", 0x3A800, 0xD2), + BowserRoom("Room 4", 0x3A83D, 0xD1), + BowserRoom("Room 5", 0x3A932, 0xCF), + BowserRoom("Room 6", 0x3A9E1, 0xCE), + BowserRoom("Room 7", 0x3AA75, 0xCD), + BowserRoom("Room 8", 0x3AAC7, 0xCC), +] + +standard_bowser_rooms = [ + BowserRoom("Room 1", 0x3A705, 0xD4), + BowserRoom("Room 2", 0x3A763, 0xD3), + BowserRoom("Room 3", 0x3A800, 0xD2), + BowserRoom("Room 4", 0x3A83D, 0xD1), + BowserRoom("Room 5", 0x3A932, 0xCF), + BowserRoom("Room 6", 0x3A9E1, 0xCE), + BowserRoom("Room 7", 0x3AA75, 0xCD), + BowserRoom("Room 8", 0x3AAC7, 0xCC), +] + + +class BossRoom(): + name: str + exitAddress: int + exitAddressAlt: int + roomID: int + + def __init__(self, name: str, exitAddress: int, roomID: int, exitAddressAlt=None): + self.name = name + self.exitAddress = exitAddress + self.roomID = roomID + self.exitAddressAlt = exitAddressAlt + + +submap_boss_rooms = [ + BossRoom("#1 Lemmy Koopa", 0x311E3, 0xF6), # Submap 0x1F6 + BossRoom("#3 Lemmy Koopa", 0x33749, 0xF2), # Submap 0x1F2 + BossRoom("Valley Reznor", 0x3A132, 0xDE), # Submap 0x1DE + BossRoom("#7 Larry Koopa", 0x3A026, 0xEB), # Submap 0x1EB +] + +ow_boss_rooms = [ + BossRoom("#2 Morton Koopa Jr.", 0x3209B, 0xE5), # OW 0x0E5 + BossRoom("Vanilla Reznor", 0x33EAB, 0xDF), # OW 0x0DF + BossRoom("#4 Ludwig von Koopa", 0x346EA, 0xD9), # OW 0x0D9 + BossRoom("Forest Reznor", 0x3643E, 0xD5, 0x36442), # OW 0x0D5 + BossRoom("#5 Roy Koopa", 0x35ABC, 0xCC), # OW 0x0CC + BossRoom("Chocolate Reznor", 0x3705B, 0xE2), # OW 0x0E2 + BossRoom("#6 Wendy O. Koopa", 0x38BB5, 0xD3), # OW 0x0D3 +] + + class SMWPath(): thisEndDirection: int otherLevelID: int @@ -203,6 +277,9 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 0x3B, 0x3A, 0x37, +] + +special_zone_levels = [ 0x4E, 0x4F, 0x50, @@ -443,6 +520,7 @@ def generate_level_list(world, player): world.random.shuffle(easy_single_levels_copy) hard_single_levels_copy = hard_single_levels.copy() world.random.shuffle(hard_single_levels_copy) + special_zone_levels_copy = special_zone_levels.copy() easy_double_levels_copy = easy_double_levels.copy() world.random.shuffle(easy_double_levels_copy) hard_double_levels_copy = hard_double_levels.copy() @@ -474,6 +552,8 @@ def generate_level_list(world, player): shuffled_level_list.append(0x16) single_levels_copy = (easy_single_levels_copy.copy() + hard_single_levels_copy.copy()) + if not world.exclude_special_zone[player]: + single_levels_copy.extend(special_zone_levels_copy) world.random.shuffle(single_levels_copy) castle_fortress_levels_copy = (easy_castle_fortress_levels_copy.copy() + hard_castle_fortress_levels_copy.copy()) @@ -566,14 +646,17 @@ def generate_level_list(world, player): # Special Zone shuffled_level_list.append(0x4D) - shuffled_level_list.append(single_levels_copy.pop(0)) - shuffled_level_list.append(single_levels_copy.pop(0)) - shuffled_level_list.append(single_levels_copy.pop(0)) - shuffled_level_list.append(single_levels_copy.pop(0)) - shuffled_level_list.append(single_levels_copy.pop(0)) - shuffled_level_list.append(single_levels_copy.pop(0)) - shuffled_level_list.append(single_levels_copy.pop(0)) - shuffled_level_list.append(single_levels_copy.pop(0)) + if not world.exclude_special_zone[player]: + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + else: + shuffled_level_list.extend(special_zone_levels_copy) shuffled_level_list.append(0x48) return shuffled_level_list diff --git a/worlds/smw/Locations.py b/worlds/smw/Locations.py index a997f92f658..a8b7f7a4ec2 100644 --- a/worlds/smw/Locations.py +++ b/worlds/smw/Locations.py @@ -212,6 +212,28 @@ def __init__(self, player: int, name: str = '', address: int = None, parent=None **yoshi_house_location_table, } +special_zone_level_names = [ + LocationName.special_zone_1_exit_1, + LocationName.special_zone_2_exit_1, + LocationName.special_zone_3_exit_1, + LocationName.special_zone_4_exit_1, + LocationName.special_zone_5_exit_1, + LocationName.special_zone_6_exit_1, + LocationName.special_zone_7_exit_1, + LocationName.special_zone_8_exit_1, +] + +special_zone_dragon_coin_names = [ + LocationName.special_zone_1_dragon, + LocationName.special_zone_2_dragon, + LocationName.special_zone_3_dragon, + LocationName.special_zone_4_dragon, + LocationName.special_zone_5_dragon, + LocationName.special_zone_6_dragon, + LocationName.special_zone_7_dragon, + LocationName.special_zone_8_dragon, +] + location_table = {} diff --git a/worlds/smw/Names/ItemName.py b/worlds/smw/Names/ItemName.py index 72c984b016e..fecb18685ef 100644 --- a/worlds/smw/Names/ItemName.py +++ b/worlds/smw/Names/ItemName.py @@ -26,6 +26,7 @@ ice_trap = "Ice Trap" stun_trap = "Stun Trap" literature_trap = "Literature Trap" +timer_trap = "Timer Trap" # Other Definitions victory = "The Princess" diff --git a/worlds/smw/Names/LiteratureTrap.py b/worlds/smw/Names/LiteratureTrap.py index 94c038228d8..19cd402b1cd 100644 --- a/worlds/smw/Names/LiteratureTrap.py +++ b/worlds/smw/Names/LiteratureTrap.py @@ -25,7 +25,7 @@ [[0x18, 0x4e, 0x54, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x41, 0x51, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x48, 0xcd, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x47, 0x44, 0x40, 0x43, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x9f, 0x47, 0x40, 0x55, 0x44, 0x1f, 0x45, 0x44, 0x44, 0x53, 0x1f, 0x48, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x9f, 0x52, 0x47, 0x4e, 0x44, 0x52, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x1f, 0x42, 0x40, 0x4d, 0x9f, 0x52, 0x53, 0x44, 0x44, 0x51, 0x1f, 0x58, 0x4e, 0x54, 0x51, 0x52, 0x44, 0x4b, 0x45, 0x1f, 0x40, 0x4d, 0xd8, 0x43, 0x48, 0x51, 0x44, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x42, 0x47, 0x4e, 0x4e, 0x52, 0x44, 0x1b, 0x1f, 0x18, 0x4e, 0x54, 0x5d, 0x51, 0x44, 0x1f, 0x4e, 0x4d, 0x9f, 0x58, 0x4e, 0x54, 0x51, 0x1f, 0x4e, 0x56, 0x4d, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x58, 0x4e, 0x54, 0x9f, ], [0x4a, 0x4d, 0x4e, 0x56, 0x1f, 0x56, 0x47, 0x40, 0x53, 0x1f, 0x58, 0x4e, 0x54, 0x9f, 0x4a, 0x4d, 0x4e, 0x56, 0x1b, 0x1f, 0x0, 0x4d, 0x43, 0x1f, 0x18, 0xe, 0x14, 0x1f, 0x40, 0x51, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4e, 0x4d, 0x44, 0x1f, 0x56, 0x47, 0x4e, 0x5d, 0x4b, 0x4b, 0x9f, 0x43, 0x44, 0x42, 0x48, 0x43, 0x44, 0x1f, 0x56, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x53, 0x4e, 0x9f, 0x46, 0x4e, 0x1b, 0x1b, 0x1b, 0x1f, 0x1c, 0x12, 0x54, 0x44, 0x52, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], [[0x16, 0x47, 0x44, 0x4d, 0x1f, 0x58, 0x4e, 0x54, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x44, 0x4b, 0x48, 0x4c, 0x48, 0x4d, 0x40, 0x53, 0x44, 0x43, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x48, 0x52, 0x9f, 0x48, 0x4c, 0x4f, 0x4e, 0x52, 0x52, 0x48, 0x41, 0x4b, 0x44, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x4d, 0x9f, 0x56, 0x47, 0x40, 0x53, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x51, 0x44, 0x4c, 0x40, 0x48, 0x4d, 0x52, 0x1d, 0x9f, 0x47, 0x4e, 0x56, 0x44, 0x55, 0x44, 0x51, 0x9f, 0x48, 0x4c, 0x4f, 0x51, 0x4e, 0x41, 0x40, 0x41, 0x4b, 0x44, 0x1d, 0x1f, 0x4c, 0x54, 0x52, 0x53, 0x9f, 0x41, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x53, 0x51, 0x54, 0x53, 0x47, 0x1b, 0x9f, ], [0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], [[0x5d, 0xc, 0x58, 0x1f, 0x4c, 0x48, 0x4d, 0x43, 0x1d, 0x5d, 0x1f, 0x47, 0x44, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1d, 0x1f, 0x5d, 0x51, 0x44, 0x41, 0x44, 0x4b, 0x52, 0x1f, 0x40, 0x53, 0x9f, 0x52, 0x53, 0x40, 0x46, 0x4d, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x1f, 0x6, 0x48, 0x55, 0x44, 0x9f, 0x4c, 0x44, 0x1f, 0x4f, 0x51, 0x4e, 0x41, 0x4b, 0x44, 0x4c, 0x52, 0x1d, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x9f, 0x4c, 0x44, 0x1f, 0x56, 0x4e, 0x51, 0x4a, 0x1d, 0x1f, 0x46, 0x48, 0x55, 0x44, 0x1f, 0x4c, 0x44, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x40, 0x41, 0x52, 0x53, 0x51, 0x54, 0x52, 0x44, 0x9f, 0x42, 0x51, 0x58, 0x4f, 0x53, 0x4e, 0x46, 0x51, 0x40, 0x4c, 0x1f, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x48, 0x4d, 0x53, 0x51, 0x48, 0x42, 0x40, 0x53, 0x44, 0x9f, ], [0x40, 0x4d, 0x40, 0x4b, 0x58, 0x52, 0x48, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x8, 0x1f, 0x40, 0xcc, 0x48, 0x4d, 0x1f, 0x4c, 0x58, 0x1f, 0x4e, 0x56, 0x4d, 0x1f, 0x4f, 0x51, 0x4e, 0x4f, 0x44, 0x51, 0x9f, 0x40, 0x53, 0x4c, 0x4e, 0x52, 0x4f, 0x47, 0x44, 0x51, 0x44, 0x1b, 0x1f, 0x8, 0x1f, 0x42, 0x40, 0x4d, 0x9f, 0x43, 0x48, 0x52, 0x4f, 0x44, 0x4d, 0x52, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x4d, 0x1f, 0x56, 0x48, 0x53, 0xc7, 0x40, 0x51, 0x53, 0x48, 0x45, 0x48, 0x42, 0x48, 0x40, 0x4b, 0x9f, 0x52, 0x53, 0x48, 0x4c, 0x54, 0x4b, 0x40, 0x4d, 0x53, 0x52, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x1f, 0x8, 0x9f, 0x40, 0x41, 0x47, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x54, 0x4b, 0x4b, 0x9f, 0x51, 0x4e, 0x54, 0x53, 0x48, 0x4d, 0x44, 0x1f, 0x4e, 0x45, 0x9f, ], [0x44, 0x57, 0x48, 0x52, 0x53, 0x44, 0x4d, 0x42, 0x44, 0x1b, 0x1f, 0x8, 0x1f, 0x42, 0x51, 0x40, 0x55, 0xc4, 0x45, 0x4e, 0x51, 0x1f, 0x4c, 0x44, 0x4d, 0x53, 0x40, 0x4b, 0x9f, 0x44, 0x57, 0x40, 0x4b, 0x53, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x1f, 0x13, 0x47, 0x40, 0x53, 0x9f, 0x48, 0x52, 0x1f, 0x56, 0x47, 0x58, 0x1f, 0x8, 0x1f, 0x47, 0x40, 0x55, 0x44, 0x9f, 0x42, 0x47, 0x4e, 0x52, 0x44, 0x4d, 0x1f, 0x4c, 0x58, 0x1f, 0x4e, 0x56, 0x4d, 0x9f, 0x4f, 0x40, 0x51, 0x53, 0x48, 0x42, 0x54, 0x4b, 0x40, 0x51, 0x9f, 0x4f, 0x51, 0x4e, 0x45, 0x44, 0x52, 0x52, 0x48, 0x4e, 0x4d, 0x1d, 0x1f, 0x4e, 0x51, 0x9f, 0x51, 0x40, 0x53, 0x47, 0x44, 0x51, 0x1f, 0x42, 0x51, 0x44, 0x40, 0x53, 0x44, 0x43, 0x1f, 0x48, 0x53, 0x9d, ], [0x45, 0x4e, 0x51, 0x1f, 0x8, 0x1f, 0x40, 0x4c, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x4e, 0x4d, 0x4b, 0x58, 0x9f, 0x4e, 0x4d, 0x44, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x1b, 0xdd, 0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], -[[0xb, 0x48, 0x45, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x4b, 0xd8, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x9f, 0x40, 0x4d, 0x58, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x53, 0x47, 0xc4, 0x4c, 0x48, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x48, 0x4d, 0x55, 0x44, 0x4d, 0x53, 0x1b, 0x1f, 0x16, 0x44, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x43, 0x40, 0x51, 0x44, 0x1f, 0x53, 0x4e, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x44, 0x48, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x40, 0x51, 0x44, 0x9f, ], [0x51, 0x44, 0x40, 0x4b, 0x4b, 0x58, 0x1f, 0x4c, 0x44, 0x51, 0x44, 0x9f, 0x42, 0x4e, 0x4c, 0x4c, 0x4e, 0x4d, 0x4f, 0x4b, 0x40, 0x42, 0x44, 0x52, 0x1f, 0x4e, 0x45, 0x9f, 0x44, 0x57, 0x48, 0x52, 0x53, 0x44, 0x4d, 0x42, 0x44, 0x1b, 0x1f, 0x8, 0x45, 0x1f, 0x56, 0x44, 0x9f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x45, 0x4b, 0x58, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x4e, 0x45, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x48, 0x4d, 0x43, 0x4e, 0x56, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x9f, 0x48, 0x4d, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x1d, 0x1f, 0x47, 0x4e, 0x55, 0x44, 0x51, 0x9f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x46, 0x51, 0x44, 0x40, 0x53, 0x9f, 0x42, 0x48, 0x53, 0x58, 0x1d, 0x1f, 0x46, 0x44, 0x4d, 0x53, 0x4b, 0x58, 0x9f, ], [0x51, 0x44, 0x4c, 0x4e, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x51, 0x4e, 0x4e, 0x45, 0x52, 0x1d, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x44, 0x44, 0x4f, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0xd3, 0x53, 0x47, 0x44, 0x1f, 0x50, 0x54, 0x44, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x46, 0x4e, 0x48, 0x4d, 0x46, 0x9f, 0x4e, 0x4d, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x9f, 0x42, 0x4e, 0x48, 0x4d, 0x42, 0x48, 0x43, 0x44, 0x4d, 0x42, 0x44, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x4b, 0x40, 0x4d, 0x4d, 0x48, 0x4d, 0x46, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x42, 0x51, 0x4e, 0x52, 0x52, 0x1c, 0x4f, 0x54, 0x51, 0x4f, 0x4e, 0x52, 0x44, 0x52, 0x1d, 0x9f, ], [0x53, 0x47, 0x44, 0x1f, 0x56, 0x4e, 0x4d, 0x43, 0x44, 0x51, 0x45, 0x54, 0x4b, 0x9f, 0x42, 0x47, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x44, 0x55, 0x44, 0x4d, 0x53, 0x52, 0x1d, 0x9f, 0x56, 0x4e, 0x51, 0x4a, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x46, 0x44, 0x4d, 0x44, 0x51, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4b, 0x44, 0x40, 0x43, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x4e, 0x54, 0x53, 0x51, 0x44, 0x9f, 0x51, 0x44, 0x52, 0x54, 0x4b, 0x53, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4c, 0x40, 0x4a, 0x44, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x45, 0x48, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x9f, ], [0x56, 0x48, 0x53, 0x47, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x42, 0x4e, 0x4d, 0x55, 0x44, 0x4d, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x4b, 0x48, 0x53, 0x48, 0x44, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x45, 0x4e, 0x51, 0x44, 0x52, 0x44, 0x44, 0x4d, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x4b, 0x54, 0x52, 0x48, 0x4e, 0x4d, 0x52, 0x1f, 0x4c, 0x4e, 0x52, 0x53, 0x9f, 0x52, 0x53, 0x40, 0x4b, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x54, 0x4d, 0x4f, 0x51, 0x4e, 0x45, 0x48, 0x53, 0x40, 0x41, 0x4b, 0x44, 0x1b, 0x9f, 0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, ]], +[[0xb, 0x48, 0x45, 0x44, 0x1f, 0x48, 0x52, 0x1f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x4b, 0xd8, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x9f, 0x40, 0x4d, 0x58, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x53, 0x47, 0xc4, 0x4c, 0x48, 0x4d, 0x43, 0x1f, 0x4e, 0x45, 0x1f, 0x4c, 0x40, 0x4d, 0x1f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x48, 0x4d, 0x55, 0x44, 0x4d, 0x53, 0x1b, 0x1f, 0x16, 0x44, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4d, 0x4e, 0x53, 0x1f, 0x43, 0x40, 0x51, 0x44, 0x1f, 0x53, 0x4e, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x44, 0x48, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x1f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x40, 0x51, 0x44, 0x9f, ], [0x51, 0x44, 0x40, 0x4b, 0x4b, 0x58, 0x1f, 0x4c, 0x44, 0x51, 0x44, 0x9f, 0x42, 0x4e, 0x4c, 0x4c, 0x4e, 0x4d, 0x4f, 0x4b, 0x40, 0x42, 0x44, 0x52, 0x1f, 0x4e, 0x45, 0x9f, 0x44, 0x57, 0x48, 0x52, 0x53, 0x44, 0x4d, 0x42, 0x44, 0x1b, 0x1f, 0x8, 0x45, 0x1f, 0x56, 0x44, 0x9f, 0x42, 0x4e, 0x54, 0x4b, 0x43, 0x1f, 0x45, 0x4b, 0x58, 0x1f, 0x4e, 0x54, 0x53, 0x1f, 0x4e, 0x45, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x56, 0x48, 0x4d, 0x43, 0x4e, 0x56, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x9f, 0x48, 0x4d, 0x1f, 0x47, 0x40, 0x4d, 0x43, 0x1d, 0x1f, 0x47, 0x4e, 0x55, 0x44, 0x51, 0x9f, 0x4e, 0x55, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x46, 0x51, 0x44, 0x40, 0x53, 0x9f, 0x42, 0x48, 0x53, 0x58, 0x1d, 0x1f, 0x46, 0x44, 0x4d, 0x53, 0x4b, 0x58, 0x9f, ], [0x51, 0x44, 0x4c, 0x4e, 0x55, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x51, 0x4e, 0x4e, 0x45, 0x52, 0x1d, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x4f, 0x44, 0x44, 0x4f, 0x1f, 0x48, 0x4d, 0x1f, 0x40, 0xd3, 0x53, 0x47, 0x44, 0x1f, 0x50, 0x54, 0x44, 0x44, 0x51, 0x1f, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x52, 0x9f, 0x56, 0x47, 0x48, 0x42, 0x47, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x46, 0x4e, 0x48, 0x4d, 0x46, 0x9f, 0x4e, 0x4d, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x53, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x9f, 0x42, 0x4e, 0x48, 0x4d, 0x42, 0x48, 0x43, 0x44, 0x4d, 0x42, 0x44, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x4b, 0x40, 0x4d, 0x4d, 0x48, 0x4d, 0x46, 0x52, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x42, 0x51, 0x4e, 0x52, 0x52, 0x1c, 0x4f, 0x54, 0x51, 0x4f, 0x4e, 0x52, 0x44, 0x52, 0x1d, 0x9f, ], [0x53, 0x47, 0x44, 0x1f, 0x56, 0x4e, 0x4d, 0x43, 0x44, 0x51, 0x45, 0x54, 0x4b, 0x9f, 0x42, 0x47, 0x40, 0x48, 0x4d, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x44, 0x55, 0x44, 0x4d, 0x53, 0x52, 0x1d, 0x9f, 0x56, 0x4e, 0x51, 0x4a, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x47, 0x51, 0x4e, 0x54, 0x46, 0x47, 0x9f, 0x46, 0x44, 0x4d, 0x44, 0x51, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x52, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x4b, 0x44, 0x40, 0x43, 0x48, 0x4d, 0x46, 0x1f, 0x53, 0x4e, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4c, 0x4e, 0x52, 0x53, 0x1f, 0x4e, 0x54, 0x53, 0x51, 0x44, 0x9f, 0x51, 0x44, 0x52, 0x54, 0x4b, 0x53, 0x52, 0x1d, 0x1f, 0x48, 0x53, 0x1f, 0x56, 0x4e, 0x54, 0x4b, 0x43, 0x9f, 0x4c, 0x40, 0x4a, 0x44, 0x1f, 0x40, 0x4b, 0x4b, 0x1f, 0x45, 0x48, 0x42, 0x53, 0x48, 0x4e, 0x4d, 0x9f, ], [0x56, 0x48, 0x53, 0x47, 0x1f, 0x48, 0x53, 0x52, 0x9f, 0x42, 0x4e, 0x4d, 0x55, 0x44, 0x4d, 0x53, 0x48, 0x4e, 0x4d, 0x40, 0x4b, 0x48, 0x53, 0x48, 0x44, 0x52, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x45, 0x4e, 0x51, 0x44, 0x52, 0x44, 0x44, 0x4d, 0x9f, 0x42, 0x4e, 0x4d, 0x42, 0x4b, 0x54, 0x52, 0x48, 0x4e, 0x4d, 0x52, 0x1f, 0x4c, 0x4e, 0x52, 0x53, 0x9f, 0x52, 0x53, 0x40, 0x4b, 0x44, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x54, 0x4d, 0x4f, 0x51, 0x4e, 0x45, 0x48, 0x53, 0x40, 0x41, 0x4b, 0x44, 0x1b, 0x9f, 0x1c, 0x3, 0x4e, 0x58, 0x4b, 0x44, 0x1f, 0x9f, 0x9f, ]], [[0x13, 0x47, 0x44, 0x1f, 0x52, 0x53, 0x4e, 0x51, 0x58, 0x1f, 0x52, 0x4e, 0x1f, 0x45, 0x40, 0x51, 0x1b, 0x9f, 0x8, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x41, 0x44, 0x46, 0x48, 0x4d, 0x4d, 0x48, 0x4d, 0x46, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x14, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x9f, 0x42, 0x51, 0x44, 0x40, 0x53, 0x44, 0x43, 0x1b, 0x1f, 0x13, 0x47, 0x48, 0x52, 0x1f, 0x47, 0x40, 0x52, 0x9f, 0x4c, 0x40, 0x43, 0x44, 0x1f, 0x40, 0x1f, 0x4b, 0x4e, 0x53, 0x1f, 0x4e, 0x45, 0x9f, 0x4f, 0x44, 0x4e, 0x4f, 0x4b, 0x44, 0x1f, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x40, 0x4d, 0x46, 0x51, 0x58, 0x9f, 0x40, 0x4d, 0x43, 0x1f, 0x41, 0x44, 0x44, 0x4d, 0x1f, 0x56, 0x48, 0x43, 0x44, 0x4b, 0x58, 0x9f, 0x51, 0x44, 0x46, 0x40, 0x51, 0x43, 0x44, 0x43, 0x1f, 0x40, 0x52, 0x1f, 0x40, 0x1f, 0x41, 0x40, 0x43, 0x9f, ], [0x4c, 0x4e, 0x55, 0x44, 0x1b, 0x1f, 0x1c, 0x0, 0x43, 0x40, 0x4c, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, ]], [[0x5, 0x4e, 0x51, 0x1f, 0x48, 0x4d, 0x52, 0x53, 0x40, 0x4d, 0x42, 0x44, 0x1d, 0x1f, 0x4e, 0x4d, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4f, 0x4b, 0x40, 0x4d, 0x44, 0x53, 0x1f, 0x4, 0x40, 0x51, 0x53, 0x47, 0x1d, 0x9f, 0x4c, 0x40, 0x4d, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x9f, 0x40, 0x52, 0x52, 0x54, 0x4c, 0x44, 0x43, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x47, 0x44, 0x9f, 0x56, 0x40, 0x52, 0x1f, 0x4c, 0x4e, 0x51, 0x44, 0x9f, 0x48, 0x4d, 0x53, 0x44, 0x4b, 0x4b, 0x48, 0x46, 0x44, 0x4d, 0x53, 0x1f, 0x53, 0x47, 0x40, 0x4d, 0x9f, 0x43, 0x4e, 0x4b, 0x4f, 0x47, 0x48, 0x4d, 0x52, 0x1f, 0x41, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x9f, 0x47, 0x44, 0x1f, 0x47, 0x40, 0x43, 0x1f, 0x40, 0x42, 0x47, 0x48, 0x44, 0x55, 0x44, 0x43, 0x1f, 0x52, 0xce, ], [0x4c, 0x54, 0x42, 0x47, 0x1c, 0x53, 0x47, 0x44, 0x1f, 0x56, 0x47, 0x44, 0x44, 0x4b, 0x1d, 0x9f, 0xd, 0x44, 0x56, 0x1f, 0x18, 0x4e, 0x51, 0x4a, 0x1d, 0x1f, 0x56, 0x40, 0x51, 0x52, 0x1f, 0x40, 0x4d, 0xc3, 0x52, 0x4e, 0x1f, 0x4e, 0x4d, 0x1c, 0x56, 0x47, 0x48, 0x4b, 0x52, 0x53, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x43, 0x4e, 0x4b, 0x4f, 0x47, 0x48, 0x4d, 0x52, 0x1f, 0x47, 0x40, 0x43, 0x9f, 0x44, 0x55, 0x44, 0x51, 0x1f, 0x43, 0x4e, 0x4d, 0x44, 0x1f, 0x56, 0x40, 0x52, 0x1f, 0x4c, 0x54, 0x42, 0xca, 0x40, 0x41, 0x4e, 0x54, 0x53, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x56, 0x40, 0x53, 0x44, 0xd1, 0x47, 0x40, 0x55, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0x1f, 0x46, 0x4e, 0x4e, 0x43, 0x9f, 0x53, 0x48, 0x4c, 0x44, 0x1b, 0x1f, 0x1, 0x54, 0x53, 0x9f, ], [0x42, 0x4e, 0x4d, 0x55, 0x44, 0x51, 0x52, 0x44, 0x4b, 0x58, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x43, 0x4e, 0x4b, 0x4f, 0x47, 0x48, 0x4d, 0x52, 0x1f, 0x47, 0x40, 0x43, 0x9f, 0x40, 0x4b, 0x56, 0x40, 0x58, 0x52, 0x1f, 0x41, 0x44, 0x4b, 0x48, 0x44, 0x55, 0x44, 0x43, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x58, 0x1f, 0x56, 0x44, 0x51, 0x44, 0x1f, 0x45, 0x40, 0xd1, 0x4c, 0x4e, 0x51, 0x44, 0x1f, 0x48, 0x4d, 0x53, 0x44, 0x4b, 0x4b, 0x48, 0x46, 0x44, 0x4d, 0x53, 0x9f, 0x53, 0x47, 0x40, 0x4d, 0x1f, 0x4c, 0x40, 0x4d, 0x1b, 0x45, 0x4e, 0x51, 0x9f, 0x4f, 0x51, 0x44, 0x42, 0x48, 0x52, 0x44, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x1f, 0x52, 0x40, 0x4c, 0xc4, 0x51, 0x44, 0x40, 0x52, 0x4e, 0x4d, 0x52, 0x1b, 0x1f, 0x1c, 0x0, 0x43, 0x40, 0x4c, 0x52, 0x1f, 0x9f, ]], [[0x8, 0x53, 0x1f, 0x48, 0x52, 0x1f, 0x4a, 0x4d, 0x4e, 0x56, 0x4d, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x9f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x40, 0x51, 0x44, 0x1f, 0x40, 0x4d, 0x9f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x1f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0x51, 0x1f, 0x4e, 0xc5, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x52, 0x1d, 0x1f, 0x52, 0x48, 0x4c, 0x4f, 0x4b, 0x58, 0x9f, 0x41, 0x44, 0x42, 0x40, 0x54, 0x52, 0x44, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x1f, 0x48, 0x52, 0x9f, 0x40, 0x4d, 0x1f, 0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x1f, 0x40, 0x4c, 0x4e, 0x54, 0x4d, 0xd3, 0x4e, 0x45, 0x1f, 0x52, 0x4f, 0x40, 0x42, 0x44, 0x1f, 0x45, 0x4e, 0x51, 0x1f, 0x53, 0x47, 0x44, 0x4c, 0x9f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x1f, 0x48, 0x4d, 0x1b, 0x1f, 0x7, 0x4e, 0x56, 0x44, 0x55, 0x44, 0x51, 0x9d, ], [0x4d, 0x4e, 0x53, 0x1f, 0x44, 0x55, 0x44, 0x51, 0x58, 0x1f, 0x4e, 0x4d, 0x44, 0x1f, 0x4e, 0x45, 0x9f, 0x53, 0x47, 0x44, 0x4c, 0x1f, 0x48, 0x52, 0x1f, 0x48, 0x4d, 0x47, 0x40, 0x41, 0x48, 0x53, 0x44, 0x43, 0x9b, 0x13, 0x47, 0x44, 0x51, 0x44, 0x45, 0x4e, 0x51, 0x44, 0x1d, 0x1f, 0x53, 0x47, 0x44, 0x51, 0x44, 0x9f, 0x4c, 0x54, 0x52, 0x53, 0x1f, 0x41, 0x44, 0x1f, 0x40, 0x1f, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x9f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0x51, 0x1f, 0x4e, 0x45, 0x9f, 0x48, 0x4d, 0x47, 0x40, 0x41, 0x48, 0x53, 0x44, 0x43, 0x1f, 0x56, 0x4e, 0x51, 0x4b, 0x43, 0x52, 0x1b, 0x9f, 0x0, 0x4d, 0x58, 0x1f, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x44, 0x1f, 0x4d, 0x54, 0x4c, 0x41, 0x44, 0x51, 0x9f, 0x43, 0x48, 0x55, 0x48, 0x43, 0x44, 0x43, 0x1f, 0x41, 0x58, 0x9f, ], [0x48, 0x4d, 0x45, 0x48, 0x4d, 0x48, 0x53, 0x58, 0x1f, 0x48, 0x52, 0x1f, 0x40, 0x52, 0x9f, 0x4d, 0x44, 0x40, 0x51, 0x1f, 0x53, 0x4e, 0x1f, 0x4d, 0x4e, 0x53, 0x47, 0x48, 0x4d, 0x46, 0x1f, 0x40, 0xd2, 0x4c, 0x40, 0x4a, 0x44, 0x52, 0x1f, 0x4d, 0x4e, 0x1f, 0x4e, 0x43, 0x43, 0x52, 0x1d, 0x1f, 0x52, 0x4e, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x40, 0x55, 0x44, 0x51, 0x40, 0x46, 0x44, 0x9f, 0x4f, 0x4e, 0x4f, 0x54, 0x4b, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x4b, 0x4b, 0x9f, 0x53, 0x47, 0x44, 0x1f, 0x4f, 0x4b, 0x40, 0x4d, 0x44, 0x53, 0x52, 0x1f, 0x48, 0x4d, 0x1f, 0x53, 0x47, 0xc4, 0x14, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x42, 0x40, 0x4d, 0x1f, 0x41, 0x44, 0x9f, 0x52, 0x40, 0x48, 0x43, 0x1f, 0x53, 0x4e, 0x1f, 0x41, 0x44, 0x1f, 0x59, 0x44, 0x51, 0x4e, 0x1b, 0x9f, ], [0x5, 0x51, 0x4e, 0x4c, 0x1f, 0x53, 0x47, 0x48, 0x52, 0x1f, 0x48, 0x53, 0x9f, 0x45, 0x4e, 0x4b, 0x4b, 0x4e, 0x56, 0x52, 0x1f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x4e, 0x4f, 0x54, 0x4b, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1f, 0x4e, 0x45, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x56, 0x47, 0x4e, 0x4b, 0x44, 0x1f, 0x14, 0x4d, 0x48, 0x55, 0x44, 0x51, 0x52, 0x44, 0x1f, 0x48, 0x52, 0x9f, 0x40, 0x4b, 0x52, 0x4e, 0x1f, 0x59, 0x44, 0x51, 0x4e, 0x1d, 0x1f, 0x40, 0x4d, 0x43, 0x9f, 0x53, 0x47, 0x40, 0x53, 0x1f, 0x40, 0x4d, 0x58, 0x1f, 0x4f, 0x44, 0x4e, 0x4f, 0x4b, 0x44, 0x9f, 0x58, 0x4e, 0x54, 0x1f, 0x4c, 0x40, 0x58, 0x1f, 0x4c, 0x44, 0x44, 0x53, 0x1f, 0x45, 0x51, 0x4e, 0x4c, 0x9f, 0x53, 0x48, 0x4c, 0x44, 0x1f, 0x53, 0x4e, 0x1f, 0x53, 0x48, 0x4c, 0x44, 0x1f, 0x40, 0x51, 0x44, 0x9f, ], [0x4c, 0x44, 0x51, 0x44, 0x4b, 0x58, 0x1f, 0x53, 0x47, 0x44, 0x9f, 0x4f, 0x51, 0x4e, 0x43, 0x54, 0x42, 0x53, 0x52, 0x1f, 0x4e, 0x45, 0x1f, 0x40, 0x9f, 0x43, 0x44, 0x51, 0x40, 0x4d, 0x46, 0x44, 0x43, 0x9f, 0x48, 0x4c, 0x40, 0x46, 0x48, 0x4d, 0x40, 0x53, 0x48, 0x4e, 0x4d, 0x1b, 0x9f, 0x1c, 0x0, 0x43, 0x40, 0x4c, 0x52, 0x1f, 0x9f, 0x9f, 0x9f, 0x9f, ]], diff --git a/worlds/smw/Options.py b/worlds/smw/Options.py index 65ecbbd7712..a9416b633dd 100644 --- a/worlds/smw/Options.py +++ b/worlds/smw/Options.py @@ -58,7 +58,7 @@ class BowserCastleDoors(Choice): Vanilla: Front and Back Doors behave as vanilla Fast: Both doors behave as the Back Door Slow: Both doors behave as the Front Door - "Front Door" requires beating all 8 Rooms + "Front Door" rooms depend on the `bowser_castle_rooms` option "Back Door" only requires going through the dark hallway to Bowser """ display_name = "Bowser Castle Doors" @@ -68,6 +68,40 @@ class BowserCastleDoors(Choice): default = 0 +class BowserCastleRooms(Choice): + """ + How the rooms of Bowser's Castle Front Door behave + Vanilla: You can choose which rooms to enter, as in vanilla + Random Two Room: Two random rooms are chosen + Random Five Room: Five random rooms are chosen + Gauntlet: All eight rooms must be cleared + Labyrinth: Which room leads to Bowser? + """ + display_name = "Bowser Castle Rooms" + option_vanilla = 0 + option_random_two_room = 1 + option_random_five_room = 2 + option_gauntlet = 3 + option_labyrinth = 4 + default = 1 + + +class BossShuffle(Choice): + """ + How the rooms of Bowser's Castle Front Door behave + None: Bosses are not shuffled + Simple: Four Reznors and the seven Koopalings are shuffled around + Full: Each boss location gets a fully random boss + Singularity: One or two bosses are chosen and placed at every boss location + """ + display_name = "Boss Shuffle" + option_none = 0 + option_simple = 1 + option_full = 2 + option_singularity = 3 + default = 0 + + class LevelShuffle(Toggle): """ Whether levels are shuffled @@ -75,6 +109,14 @@ class LevelShuffle(Toggle): display_name = "Level Shuffle" +class ExcludeSpecialZone(Toggle): + """ + If active, this option will prevent any progression items from being placed in Special Zone levels. + Additionally, if Level Shuffle is active, Special Zone levels will not be shuffled away from their vanilla tiles. + """ + display_name = "Exclude Special Zone" + + class SwapDonutGhostHouseExits(Toggle): """ If enabled, this option will swap which overworld direction the two exits of the level at the Donut Ghost House @@ -148,6 +190,13 @@ class LiteratureTrapWeight(BaseTrapWeight): display_name = "Literature Trap Weight" +class TimerTrapWeight(BaseTrapWeight): + """ + Likelihood of a receiving a trap which causes the timer to run low + """ + display_name = "Timer Trap Weight" + + class Autosave(DefaultOnToggle): """ Whether a save prompt will appear after every level @@ -155,6 +204,25 @@ class Autosave(DefaultOnToggle): display_name = "Autosave" +class EarlyClimb(Toggle): + """ + Force Climb to appear early in the seed as a local item. + This is particularly useful to prevent BK when Level Shuffle is disabled + """ + display_name = "Early Climb" + + +class OverworldSpeed(Choice): + """ + How fast Mario moves on the overworld + """ + display_name = "Overworld Speed" + option_slow = 0 + option_vanilla = 1 + option_fast = 2 + default = 1 + + class MusicShuffle(Choice): """ Music shuffle type @@ -201,6 +269,13 @@ class BackgroundPaletteShuffle(Toggle): display_name = "Background Palette Shuffle" +class OverworldPaletteShuffle(Toggle): + """ + Whether to shuffle overworld palettes + """ + display_name = "Overworld Palette Shuffle" + + class StartingLifeCount(Range): """ How many extra lives to start the game with @@ -220,7 +295,10 @@ class StartingLifeCount(Range): "percentage_of_yoshi_eggs": PercentageOfYoshiEggs, "dragon_coin_checks": DragonCoinChecks, "bowser_castle_doors": BowserCastleDoors, + "bowser_castle_rooms": BowserCastleRooms, "level_shuffle": LevelShuffle, + "exclude_special_zone": ExcludeSpecialZone, + "boss_shuffle": BossShuffle, "swap_donut_gh_exits": SwapDonutGhostHouseExits, #"display_sent_item_popups": DisplaySentItemPopups, "display_received_item_popups": DisplayReceivedItemPopups, @@ -228,10 +306,14 @@ class StartingLifeCount(Range): "ice_trap_weight": IceTrapWeight, "stun_trap_weight": StunTrapWeight, "literature_trap_weight": LiteratureTrapWeight, + "timer_trap_weight": TimerTrapWeight, "autosave": Autosave, + "early_climb": EarlyClimb, + "overworld_speed": OverworldSpeed, "music_shuffle": MusicShuffle, "mario_palette": MarioPalette, "foreground_palette_shuffle": ForegroundPaletteShuffle, "background_palette_shuffle": BackgroundPaletteShuffle, + "overworld_palette_shuffle": OverworldPaletteShuffle, "starting_life_count": StartingLifeCount, } diff --git a/worlds/smw/Regions.py b/worlds/smw/Regions.py index 052f0f01377..fd94e0b168e 100644 --- a/worlds/smw/Regions.py +++ b/worlds/smw/Regions.py @@ -11,14 +11,6 @@ def create_regions(world, player: int, active_locations): menu_region = create_region(world, player, active_locations, 'Menu', None) yoshis_island_region = create_region(world, player, active_locations, LocationName.yoshis_island_region, None) - donut_plains_region = create_region(world, player, active_locations, LocationName.donut_plains_region, None) - vanilla_dome_region = create_region(world, player, active_locations, LocationName.vanilla_dome_region, None) - twin_bridges_region = create_region(world, player, active_locations, LocationName.twin_bridges_region, None) - forest_of_illusion_region = create_region(world, player, active_locations, LocationName.forest_of_illusion_region, None) - chocolate_island_region = create_region(world, player, active_locations, LocationName.chocolate_island_region, None) - valley_of_bowser_region = create_region(world, player, active_locations, LocationName.valley_of_bowser_region, None) - star_road_region = create_region(world, player, active_locations, LocationName.star_road_region, None) - special_zone_region = create_region(world, player, active_locations, LocationName.special_zone_region, None) yoshis_house_tile = create_region(world, player, active_locations, LocationName.yoshis_house_tile, None) @@ -472,14 +464,6 @@ def create_regions(world, player: int, active_locations): world.regions += [ menu_region, yoshis_island_region, - donut_plains_region, - vanilla_dome_region, - twin_bridges_region, - forest_of_illusion_region, - chocolate_island_region, - valley_of_bowser_region, - star_road_region, - special_zone_region, yoshis_house_tile, yoshis_house_region, yoshis_island_1_tile, @@ -824,7 +808,7 @@ def create_regions(world, player: int, active_locations): (state.has(ItemName.yellow_switch_palace, player) or state.has(ItemName.red_switch_palace, player))))) add_location_to_region(world, player, active_locations, LocationName.chocolate_island_3_region, LocationName.chocolate_island_3_dragon) add_location_to_region(world, player, active_locations, LocationName.chocolate_island_4_region, LocationName.chocolate_island_4_dragon, - lambda state: (state.has(ItemName.mario_run, player) and + lambda state: (state.has(ItemName.p_switch, player) and state.has(ItemName.progressive_powerup, player, 3))) add_location_to_region(world, player, active_locations, LocationName.chocolate_island_5_region, LocationName.chocolate_island_5_dragon, lambda state: (state.has(ItemName.mario_swim, player) or @@ -866,6 +850,8 @@ def connect_regions(world, player, level_to_tile_dict): names: typing.Dict[str, int] = {} connect(world, player, names, "Menu", LocationName.yoshis_island_region) + connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_house_tile) + connect(world, player, names, LocationName.yoshis_house_tile, LocationName.donut_plains_top_secret) connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_1_tile) connect(world, player, names, LocationName.yoshis_island_region, LocationName.yoshis_island_2_tile) diff --git a/worlds/smw/Rom.py b/worlds/smw/Rom.py index 4641141c389..d39fcc46b96 100644 --- a/worlds/smw/Rom.py +++ b/worlds/smw/Rom.py @@ -1,7 +1,7 @@ import Utils from worlds.Files import APDeltaPatch -from .Aesthetics import generate_shuffled_header_data -from .Levels import level_info_dict +from .Aesthetics import generate_shuffled_header_data, generate_shuffled_ow_palettes +from .Levels import level_info_dict, full_bowser_rooms, standard_bowser_rooms, submap_boss_rooms, ow_boss_rooms from .Names.TextBox import generate_goal_text, title_text_mapping, generate_text_box USHASH = 'cdd3c8c37322978ca8669b34bc89c804' @@ -36,19 +36,14 @@ 0xBC000F: [0x1F27, 0x1, 0x1C], # Green Switch Palace 0xBC0010: [0x1F2A, 0x1, 0x1C], # Red Switch Palace 0xBC0011: [0x1F29, 0x1, 0x1C], # Blue Switch Palace +} +trap_rom_data = { 0xBC0013: [0x0086, 0x1, 0x0E], # Ice Trap 0xBC0014: [0x18BD, 0x7F, 0x18], # Stun Trap + 0xBC0016: [0x0F31, 0x1], # Timer Trap } -music_rom_data = [ - -] - -level_music_ids = [ - -] - class SMWDeltaPatch(APDeltaPatch): hash = USHASH @@ -728,6 +723,103 @@ def handle_swap_donut_gh_exits(rom): rom.write_bytes(0x26371, bytes([0x32])) +def handle_bowser_rooms(rom, world, player): + if world.bowser_castle_rooms[player] == "random_two_room": + chosen_rooms = world.random.sample(standard_bowser_rooms, 2) + + rom.write_byte(0x3A680, chosen_rooms[0].roomID) + rom.write_byte(0x3A684, chosen_rooms[0].roomID) + rom.write_byte(0x3A688, chosen_rooms[0].roomID) + rom.write_byte(0x3A68C, chosen_rooms[0].roomID) + + for i in range(1, len(chosen_rooms)): + rom.write_byte(chosen_rooms[i-1].exitAddress, chosen_rooms[i].roomID) + + rom.write_byte(chosen_rooms[len(chosen_rooms)-1].exitAddress, 0xBD) + + elif world.bowser_castle_rooms[player] == "random_five_room": + chosen_rooms = world.random.sample(standard_bowser_rooms, 5) + + rom.write_byte(0x3A680, chosen_rooms[0].roomID) + rom.write_byte(0x3A684, chosen_rooms[0].roomID) + rom.write_byte(0x3A688, chosen_rooms[0].roomID) + rom.write_byte(0x3A68C, chosen_rooms[0].roomID) + + for i in range(1, len(chosen_rooms)): + rom.write_byte(chosen_rooms[i-1].exitAddress, chosen_rooms[i].roomID) + + rom.write_byte(chosen_rooms[len(chosen_rooms)-1].exitAddress, 0xBD) + + elif world.bowser_castle_rooms[player] == "gauntlet": + chosen_rooms = standard_bowser_rooms.copy() + world.random.shuffle(chosen_rooms) + + rom.write_byte(0x3A680, chosen_rooms[0].roomID) + rom.write_byte(0x3A684, chosen_rooms[0].roomID) + rom.write_byte(0x3A688, chosen_rooms[0].roomID) + rom.write_byte(0x3A68C, chosen_rooms[0].roomID) + + for i in range(1, len(chosen_rooms)): + rom.write_byte(chosen_rooms[i-1].exitAddress, chosen_rooms[i].roomID) + + rom.write_byte(chosen_rooms[len(chosen_rooms)-1].exitAddress, 0xBD) + elif world.bowser_castle_rooms[player] == "labyrinth": + bowser_rooms_copy = full_bowser_rooms.copy() + + entrance_point = bowser_rooms_copy.pop(0) + + world.random.shuffle(bowser_rooms_copy) + + rom.write_byte(entrance_point.exitAddress, bowser_rooms_copy[0].roomID) + for i in range(0, len(bowser_rooms_copy) - 1): + rom.write_byte(bowser_rooms_copy[i].exitAddress, bowser_rooms_copy[i+1].roomID) + + rom.write_byte(bowser_rooms_copy[len(bowser_rooms_copy)-1].exitAddress, 0xBD) + + +def handle_boss_shuffle(rom, world, player): + if world.boss_shuffle[player] == "simple": + submap_boss_rooms_copy = submap_boss_rooms.copy() + ow_boss_rooms_copy = ow_boss_rooms.copy() + + world.random.shuffle(submap_boss_rooms_copy) + world.random.shuffle(ow_boss_rooms_copy) + + for i in range(len(submap_boss_rooms_copy)): + rom.write_byte(submap_boss_rooms[i].exitAddress, submap_boss_rooms_copy[i].roomID) + + for i in range(len(ow_boss_rooms_copy)): + rom.write_byte(ow_boss_rooms[i].exitAddress, ow_boss_rooms_copy[i].roomID) + + if ow_boss_rooms[i].exitAddressAlt is not None: + rom.write_byte(ow_boss_rooms[i].exitAddressAlt, ow_boss_rooms_copy[i].roomID) + + elif world.boss_shuffle[player] == "full": + for i in range(len(submap_boss_rooms)): + chosen_boss = world.random.choice(submap_boss_rooms) + rom.write_byte(submap_boss_rooms[i].exitAddress, chosen_boss.roomID) + + for i in range(len(ow_boss_rooms)): + chosen_boss = world.random.choice(ow_boss_rooms) + rom.write_byte(ow_boss_rooms[i].exitAddress, chosen_boss.roomID) + + if ow_boss_rooms[i].exitAddressAlt is not None: + rom.write_byte(ow_boss_rooms[i].exitAddressAlt, chosen_boss.roomID) + + elif world.boss_shuffle[player] == "singularity": + chosen_submap_boss = world.random.choice(submap_boss_rooms) + chosen_ow_boss = world.random.choice(ow_boss_rooms) + + for i in range(len(submap_boss_rooms)): + rom.write_byte(submap_boss_rooms[i].exitAddress, chosen_submap_boss.roomID) + + for i in range(len(ow_boss_rooms)): + rom.write_byte(ow_boss_rooms[i].exitAddress, chosen_ow_boss.roomID) + + if ow_boss_rooms[i].exitAddressAlt is not None: + rom.write_byte(ow_boss_rooms[i].exitAddressAlt, chosen_ow_boss.roomID) + + def patch_rom(world, rom, player, active_level_dict): local_random = world.slot_seeds[player] @@ -739,18 +831,8 @@ def patch_rom(world, rom, player, active_level_dict): intro_text = generate_text_box("Bowser has stolen all of Mario's abilities. Can you help Mario travel across Dinosaur land to get them back and save the Princess from him?") rom.write_bytes(0x2A5D9, intro_text) - # Force all 8 Bowser's Castle Rooms - rom.write_byte(0x3A680, 0xD4) - rom.write_byte(0x3A684, 0xD4) - rom.write_byte(0x3A688, 0xD4) - rom.write_byte(0x3A68C, 0xD4) - rom.write_byte(0x3A705, 0xD3) - rom.write_byte(0x3A763, 0xD2) - rom.write_byte(0x3A800, 0xD1) - rom.write_byte(0x3A83D, 0xCF) - rom.write_byte(0x3A932, 0xCE) - rom.write_byte(0x3A9E1, 0xCD) - rom.write_byte(0x3AA75, 0xCC) + handle_bowser_rooms(rom, world, player) + handle_boss_shuffle(rom, world, player) # Prevent Title Screen Deaths rom.write_byte(0x1C6A, 0x80) @@ -805,6 +887,11 @@ def patch_rom(world, rom, player, active_level_dict): if world.autosave[player]: rom.write_bytes(0x20F93, bytearray([0x00])) + if world.overworld_speed[player] == "fast": + rom.write_bytes(0x21414, bytearray([0x20, 0x10])) + elif world.overworld_speed[player] == "slow": + rom.write_bytes(0x21414, bytearray([0x05, 0x05])) + # Starting Life Count rom.write_bytes(0x1E25, bytearray([world.starting_life_count[player].value - 1])) @@ -835,6 +922,8 @@ def patch_rom(world, rom, player, active_level_dict): if world.music_shuffle[player] != "none": handle_music_shuffle(rom, world, player) + generate_shuffled_ow_palettes(rom, world, player) + generate_shuffled_header_data(rom, world, player) if world.swap_donut_gh_exits[player]: diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py index 6accda9c6c1..73d6d58d46a 100644 --- a/worlds/smw/__init__.py +++ b/worlds/smw/__init__.py @@ -5,12 +5,12 @@ from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification from .Items import SMWItem, ItemData, item_table -from .Locations import SMWLocation, all_locations, setup_locations +from .Locations import SMWLocation, all_locations, setup_locations, special_zone_level_names, special_zone_dragon_coin_names from .Options import smw_options from .Regions import create_regions, connect_regions from .Levels import full_level_list, generate_level_list, location_id_to_level_id from .Rules import set_rules -from ..generic.Rules import add_rule +from ..generic.Rules import add_rule, exclusion_rules from .Names import ItemName, LocationName from .Client import SMWSNIClient from ..AutoWorld import WebWorld, World @@ -41,7 +41,7 @@ class SMWWorld(World): game: str = "Super Mario World" option_definitions = smw_options topology_present = False - data_version = 2 + data_version = 3 required_client_version = (0, 3, 5) item_name_to_id = {name: data.code for name, data in item_table.items()} @@ -74,6 +74,10 @@ def fill_slot_data(self) -> dict: return slot_data + def generate_early(self): + if self.multiworld.early_climb[self.player]: + self.multiworld.local_early_items[self.player][ItemName.mario_climb] = 1 + def generate_basic(self): itempool: typing.List[SMWItem] = [] @@ -89,6 +93,15 @@ def generate_basic(self): add_rule(self.multiworld.get_region(LocationName.chocolate_island_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 5)) add_rule(self.multiworld.get_region(LocationName.valley_of_bowser_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 6)) + if self.multiworld.exclude_special_zone[self.player]: + exclusion_pool = set() + if self.multiworld.dragon_coin_checks[self.player]: + exclusion_pool.update(special_zone_level_names) + exclusion_pool.update(special_zone_dragon_coin_names) + elif self.multiworld.number_of_yoshi_eggs[self.player].value <= 72: + exclusion_pool.update(special_zone_level_names) + exclusion_rules(self.multiworld, self.player, exclusion_pool) + total_required_locations = 96 if self.multiworld.dragon_coin_checks[self.player]: total_required_locations += 49 @@ -120,6 +133,7 @@ def generate_basic(self): trap_weights += ([ItemName.ice_trap] * self.multiworld.ice_trap_weight[self.player].value) trap_weights += ([ItemName.stun_trap] * self.multiworld.stun_trap_weight[self.player].value) trap_weights += ([ItemName.literature_trap] * self.multiworld.literature_trap_weight[self.player].value) + trap_weights += ([ItemName.timer_trap] * self.multiworld.timer_trap_weight[self.player].value) trap_count = 0 if (len(trap_weights) == 0) else math.ceil(junk_count * (self.multiworld.trap_fill_percentage[self.player].value / 100.0)) junk_count -= trap_count