diff --git a/worlds/smw/CHANGELOG.md b/worlds/smw/CHANGELOG.md index 3ea1f821e3d2..b8c991b30c5e 100644 --- a/worlds/smw/CHANGELOG.md +++ b/worlds/smw/CHANGELOG.md @@ -5,7 +5,10 @@ ### Features: -- New inventory system that allows you to get a powerup before entering a level +- Map teleport and transition shuffle + - Shuffle pipe and star destinations + - Shuffle map transitions +- New inventory system that allows you to use a powerup before entering a level - New Items - Inventory Mushroom - Inventory Fire Flower @@ -31,6 +34,10 @@ - `location_visual_indicator` - `block_collect_behavior` - `inventory_fill_percentage` + - `persistent_trap_behavior` + - `map_teleport_shuffle` + - `map_transition_shuffle` + ### Quality of Life @@ -38,6 +45,10 @@ - Current item box contents are displayed during the map gameplay - Added location groups +### Bug Fixes: + +- Several logic tweaks/fixes +- Some levels received adjustments to their vertical scroll setting diff --git a/worlds/smw/Levels.py b/worlds/smw/Levels.py index 7aa9428b9110..d5323fc8ce3f 100644 --- a/worlds/smw/Levels.py +++ b/worlds/smw/Levels.py @@ -1,7 +1,8 @@ - -from worlds.AutoWorld import World from .Names import LocationName +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from . import SMWWorld, SMWProcedurePatch class BowserRoom(): name: str @@ -207,29 +208,29 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 level_info_dict = { 0x28: SMWLevel(LocationName.yoshis_house, 0x37A76, 0x00), - 0x29: SMWLevel(LocationName.yoshis_island_1_region, 0x37A83, 0x01, SMWPath(0x08, 0x14, 0x04)), + 0x29: SMWLevel(LocationName.yoshis_island_1_region, 0x37A83, 0x01, SMWPath(0x08, 0x70, 0x04)), 0x14: SMWLevel(LocationName.yellow_switch_palace, 0x37812, 0x02), 0x2A: SMWLevel(LocationName.yoshis_island_2_region, 0x37A89, 0x03, SMWPath(0x08, 0x27, 0x04)), 0x27: SMWLevel(LocationName.yoshis_island_3_region, 0x37A69, 0x04, SMWPath(0x01, 0x26, 0x04)), 0x26: SMWLevel(LocationName.yoshis_island_4_region, 0x37A4B, 0x05, SMWPath(0x08, 0x25, 0x01)), - 0x25: SMWLevel(LocationName.yoshis_island_castle_region, 0x37A29, 0x06, SMWPath(0x08, 0x15, 0x04)), + 0x25: SMWLevel(LocationName.yoshis_island_castle_region, 0x37A29, 0x06, SMWPath(0x08, 0x72, 0x04)), 0x15: SMWLevel(LocationName.donut_plains_1_region, 0x37815, 0x07, SMWPath(0x02, 0x09, 0x04), SMWPath(0x08, 0x0A, 0x04)), 0x09: SMWLevel(LocationName.donut_plains_2_region, 0x376D3, 0x09, SMWPath(0x08, 0x04, 0x02), SMWPath(0x02, 0x08, 0x01)), 0x0A: SMWLevel(LocationName.donut_secret_1_region, 0x376E5, 0x10, SMWPath(0x08, 0x04, 0x04), SMWPath(0x01, 0x13, 0x08)), 0x08: SMWLevel(LocationName.green_switch_palace, 0x376D1, 0x28), 0x04: SMWLevel(LocationName.donut_ghost_house_region, 0x376A5, 0x0B, SMWPath(0x08, 0x03, 0x04), SMWPath(0x01, 0x05, 0x02)), - 0x13: SMWLevel(LocationName.donut_secret_house_region, 0x37807, 0x12, SMWPath(0x01, 0x2F, 0x04), SMWPath(0x04, 0x16, 0x08)), # SMW_TODO: Check this wrt pipe behavior + 0x13: SMWLevel(LocationName.donut_secret_house_region, 0x37807, 0x12, SMWPath(0x01, 0x60, 0x04), SMWPath(0x04, 0x16, 0x08)), # SMW_TODO: Check this wrt pipe behavior 0x05: SMWLevel(LocationName.donut_plains_3_region, 0x376A9, 0x0D, SMWPath(0x01, 0x06, 0x08)), 0x06: SMWLevel(LocationName.donut_plains_4_region, 0x376CB, 0x0E, SMWPath(0x01, 0x07, 0x02)), - 0x2F: SMWLevel(LocationName.donut_secret_2_region, 0x37B10, 0x14, SMWPath(0x01, 0x05, 0x04)), - 0x07: SMWLevel(LocationName.donut_plains_castle_region, 0x376CD, 0x0F, SMWPath(0x08, 0x3E, 0x04)), + 0x2F: SMWLevel(LocationName.donut_secret_2_region, 0x37B10, 0x14, SMWPath(0x01, 0x62, 0x04)), + 0x07: SMWLevel(LocationName.donut_plains_castle_region, 0x376CD, 0x0F, SMWPath(0x08, 0x74, 0x04)), 0x03: SMWLevel(LocationName.donut_plains_top_secret, 0x37685, 0xFF), 0x16: SMWLevel(LocationName.donut_plains_star_road, 0x37827, 0xFF), 0x3E: SMWLevel(LocationName.vanilla_dome_1_region, 0x37C25, 0x15, SMWPath(0x01, 0x3C, 0x04), SMWPath(0x02, 0x2D, 0x04)), 0x3C: SMWLevel(LocationName.vanilla_dome_2_region, 0x37C08, 0x17, SMWPath(0x08, 0x2B, 0x04), SMWPath(0x01, 0x3F, 0x08)), - 0x2D: SMWLevel(LocationName.vanilla_secret_1_region, 0x37AE3, 0x1D, SMWPath(0x08, 0x01, 0x02), SMWPath(0x02, 0x2C, 0x01)), + 0x2D: SMWLevel(LocationName.vanilla_secret_1_region, 0x37AE3, 0x1D, SMWPath(0x08, 0x66, 0x02), SMWPath(0x02, 0x2C, 0x01)), 0x2B: SMWLevel(LocationName.vanilla_ghost_house_region, 0x37AC8, 0x19, SMWPath(0x01, 0x2E, 0x08)), 0x2E: SMWLevel(LocationName.vanilla_dome_3_region, 0x37AEC, 0x1A, SMWPath(0x04, 0x3D, 0x08)), 0x3D: SMWLevel(LocationName.vanilla_dome_4_region, 0x37C0C, 0x1B, SMWPath(0x04, 0x40, 0x08)), @@ -237,7 +238,7 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 0x01: SMWLevel(LocationName.vanilla_secret_2_region, 0x3763C, 0x1F, SMWPath(0x01, 0x02, 0x02)), 0x02: SMWLevel(LocationName.vanilla_secret_3_region, 0x3763E, 0x20, SMWPath(0x01, 0x0B, 0x02)), 0x0B: SMWLevel(LocationName.vanilla_fortress_region, 0x37730, 0x21, SMWPath(0x01, 0x0C, 0x02)), - 0x40: SMWLevel(LocationName.vanilla_dome_castle_region, 0x37C2C, 0x1C, SMWPath(0x04, 0x0F, 0x02)), + 0x40: SMWLevel(LocationName.vanilla_dome_castle_region, 0x37C2C, 0x1C, SMWPath(0x04, 0x64, 0x02)), 0x2C: SMWLevel(LocationName.vanilla_dome_star_road, 0x37AE0, 0xFF), 0x0C: SMWLevel(LocationName.butter_bridge_1_region, 0x37734, 0x22, SMWPath(0x01, 0x0D, 0x02)), @@ -245,31 +246,31 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 0x0F: SMWLevel(LocationName.cheese_bridge_region, 0x37754, 0x25, SMWPath(0x01, 0x10, 0x02), SMWPath(0x04, 0x11, 0x08)), 0x11: SMWLevel(LocationName.soda_lake_region, 0x37784, 0x60, SMWPath(0x04, 0x12, 0x04)), 0x10: SMWLevel(LocationName.cookie_mountain_region, 0x37757, 0x27, SMWPath(0x04, 0x0E, 0x04)), - 0x0E: SMWLevel(LocationName.twin_bridges_castle_region, 0x3773A, 0x24, SMWPath(0x01, 0x42, 0x08)), + 0x0E: SMWLevel(LocationName.twin_bridges_castle_region, 0x3773A, 0x24, SMWPath(0x01, 0x76, 0x08)), 0x12: SMWLevel(LocationName.twin_bridges_star_road, 0x377F0, 0xFF), 0x42: SMWLevel(LocationName.forest_of_illusion_1_region, 0x37C78, 0x2A, SMWPath(0x01, 0x44, 0x08), SMWPath(0x02, 0x41, 0x01)), 0x44: SMWLevel(LocationName.forest_of_illusion_2_region, 0x37CAA, 0x2C, SMWPath(0x04, 0x47, 0x08), SMWPath(0x01, 0x45, 0x02)), - 0x47: SMWLevel(LocationName.forest_of_illusion_3_region, 0x37CC8, 0x2E, SMWPath(0x02, 0x41, 0x04), SMWPath(0x04, 0x20, 0x01)), + 0x47: SMWLevel(LocationName.forest_of_illusion_3_region, 0x37CC8, 0x2E, SMWPath(0x02, 0x41, 0x04), SMWPath(0x04, 0x78, 0x01)), 0x43: SMWLevel(LocationName.forest_of_illusion_4_region, 0x37CA4, 0x32, SMWPath(0x01, 0x44, 0x02), SMWPath(0x04, 0x46, 0x08)), 0x41: SMWLevel(LocationName.forest_ghost_house_region, 0x37C76, 0x30, SMWPath(0x01, 0x42, 0x02), SMWPath(0x02, 0x43, 0x08)), - 0x46: SMWLevel(LocationName.forest_secret_region, 0x37CC4, 0x34, SMWPath(0x04, 0x1F, 0x01)), + 0x46: SMWLevel(LocationName.forest_secret_region, 0x37CC4, 0x34, SMWPath(0x04, 0x7A, 0x01)), 0x45: SMWLevel(LocationName.blue_switch_palace, 0x37CAC, 0x37), 0x1F: SMWLevel(LocationName.forest_fortress_region, 0x37906, 0x35, SMWPath(0x02, 0x1E, 0x01)), 0x20: SMWLevel(LocationName.forest_castle_region, 0x37928, 0x61, SMWPath(0x04, 0x22, 0x08)), 0x1E: SMWLevel(LocationName.forest_star_road, 0x37904, 0x36), 0x22: SMWLevel(LocationName.chocolate_island_1_region, 0x37968, 0x62, SMWPath(0x02, 0x21, 0x01)), - 0x24: SMWLevel(LocationName.chocolate_island_2_region, 0x379B5, 0x46, SMWPath(0x02, 0x23, 0x01), SMWPath(0x04, 0x3B, 0x01)), + 0x24: SMWLevel(LocationName.chocolate_island_2_region, 0x379B5, 0x46, SMWPath(0x02, 0x23, 0x01), SMWPath(0x04, 0x68, 0x01)), 0x23: SMWLevel(LocationName.chocolate_island_3_region, 0x379B3, 0x48, SMWPath(0x04, 0x23, 0x08), SMWPath(0x02, 0x1B, 0x01)), 0x1D: SMWLevel(LocationName.chocolate_island_4_region, 0x378DF, 0x4B, SMWPath(0x02, 0x1C, 0x01)), 0x1C: SMWLevel(LocationName.chocolate_island_5_region, 0x378DC, 0x4C, SMWPath(0x08, 0x1A, 0x04)), 0x21: SMWLevel(LocationName.chocolate_ghost_house_region, 0x37965, 0x63, SMWPath(0x04, 0x24, 0x08)), 0x1B: SMWLevel(LocationName.chocolate_fortress_region, 0x378BF, 0x4A, SMWPath(0x04, 0x1D, 0x08)), - 0x3B: SMWLevel(LocationName.chocolate_secret_region, 0x37B97, 0x4F, SMWPath(0x02, 0x1A, 0x02)), + 0x3B: SMWLevel(LocationName.chocolate_secret_region, 0x37B97, 0x4F, SMWPath(0x02, 0x6A, 0x02)), 0x1A: SMWLevel(LocationName.chocolate_castle_region, 0x378BC, 0x4D, SMWPath(0x08, 0x18, 0x02)), - 0x18: SMWLevel(LocationName.sunken_ghost_ship_region, 0x3787E, 0x4E, SMWPath(0x08, 0x3A, 0x01)), + 0x18: SMWLevel(LocationName.sunken_ghost_ship_region, 0x3787E, 0x4E, SMWPath(0x08, 0x7C, 0x01)), 0x3A: SMWLevel(LocationName.valley_of_bowser_1_region, 0x37B7B, 0x38, SMWPath(0x02, 0x39, 0x01)), 0x39: SMWLevel(LocationName.valley_of_bowser_2_region, 0x37B79, 0x39, SMWPath(0x02, 0x38, 0x01), SMWPath(0x08, 0x35, 0x04)), 0x37: SMWLevel(LocationName.valley_of_bowser_3_region, 0x37B74, 0x3D, SMWPath(0x08, 0x33, 0x04)), @@ -305,6 +306,34 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 0x4A: SMWLevel(LocationName.special_zone_7_region, 0x37D16, 0x6B, SMWPath(0x02, 0x49, 0x01)), 0x49: SMWLevel(LocationName.special_zone_8_region, 0x37D13, 0x6C, SMWPath(0x02, 0x48, 0x01)), 0x48: SMWLevel(LocationName.special_complete, 0x37D11, 0x6D), + + 0x60: SMWLevel(LocationName.donut_plains_entrance_pipe, 0xFFFFFF, 0xFF), + 0x61: SMWLevel(LocationName.valley_donut_exit_pipe, 0xFFFFFF, 0xFF), + 0x62: SMWLevel(LocationName.valley_donut_entrance_pipe, 0xFFFFFF, 0xFF), + 0x63: SMWLevel(LocationName.donut_plains_exit_pipe, 0xFFFFFF, 0xFF), + 0x64: SMWLevel(LocationName.vanilla_dome_bottom_entrance_pipe, 0xFFFFFF, 0xFF), + 0x65: SMWLevel(LocationName.twin_bridges_exit_pipe, 0xFFFFFF, 0xFF), + 0x66: SMWLevel(LocationName.vanilla_dome_top_entrance_pipe, 0xFFFFFF, 0xFF), + 0x67: SMWLevel(LocationName.vanilla_dome_top_exit_pipe, 0xFFFFFF, 0xFF), + 0x68: SMWLevel(LocationName.chocolate_island_entrance_pipe, 0xFFFFFF, 0xFF), + 0x69: SMWLevel(LocationName.valley_chocolate_exit_pipe, 0xFFFFFF, 0xFF), + 0x6A: SMWLevel(LocationName.valley_chocolate_entrance_pipe, 0xFFFFFF, 0xFF), + 0x6B: SMWLevel(LocationName.chocolate_island_exit_pipe, 0xFFFFFF, 0xFF), + + 0x70: SMWLevel(LocationName.yi_to_ysp, 0xFFFFFF, 0xFF), + 0x71: SMWLevel(LocationName.ysp_from_yi, 0xFFFFFF, 0xFF), + 0x72: SMWLevel(LocationName.yi_to_dp, 0xFFFFFF, 0xFF), + 0x73: SMWLevel(LocationName.dp_from_yi, 0xFFFFFF, 0xFF), + 0x74: SMWLevel(LocationName.dp_to_vd, 0xFFFFFF, 0xFF), + 0x75: SMWLevel(LocationName.vd_from_dp, 0xFFFFFF, 0xFF), + 0x76: SMWLevel(LocationName.tw_to_foi, 0xFFFFFF, 0xFF), + 0x77: SMWLevel(LocationName.foi_from_tw, 0xFFFFFF, 0xFF), + 0x78: SMWLevel(LocationName.foi_to_ci, 0xFFFFFF, 0xFF), + 0x79: SMWLevel(LocationName.ci_from_foi, 0xFFFFFF, 0xFF), + 0x7A: SMWLevel(LocationName.foi_to_sr, 0xFFFFFF, 0xFF), + 0x7B: SMWLevel(LocationName.sr_from_foi, 0xFFFFFF, 0xFF), + 0x7C: SMWLevel(LocationName.ci_to_vob, 0xFFFFFF, 0xFF), + 0x7D: SMWLevel(LocationName.vob_from_ci, 0xFFFFFF, 0xFF), } full_level_list = [ @@ -1201,7 +1230,7 @@ def __init__(self, levelName: str, levelIDAddress: int, eventIDValue: int, exit1 LocationName.star_road_5_green_block_20: [0x5A, 681] } -def generate_level_list(world: World): +def generate_level_list(world: "SMWWorld"): if not world.options.level_shuffle: out_level_list = full_level_list.copy() @@ -1243,20 +1272,6 @@ def generate_level_list(world: World): shuffled_level_list.append(easy_single_levels_copy.pop(0)) shuffled_level_list.append(easy_castle_fortress_levels_copy.pop(0)) - # Donut Plains - shuffled_level_list.append(easy_double_levels_copy.pop(0)) - shuffled_level_list.append(easy_double_levels_copy.pop(0)) - shuffled_level_list.append(easy_double_levels_copy.pop(0)) - shuffled_level_list.append(0x08) - shuffled_level_list.append(easy_double_levels_copy.pop(0)) - shuffled_level_list.append(easy_double_levels_copy.pop(0)) - shuffled_level_list.append(easy_single_levels_copy.pop(0)) - shuffled_level_list.append(easy_single_levels_copy.pop(0)) - shuffled_level_list.append(easy_single_levels_copy.pop(0)) - shuffled_level_list.append(easy_castle_fortress_levels_copy.pop(0)) - shuffled_level_list.append(0x28) - shuffled_level_list.append(0x16) - single_levels_copy = (easy_single_levels_copy.copy() + hard_single_levels_copy.copy()) if not world.options.exclude_special_zone: single_levels_copy.extend(special_zone_levels_copy) @@ -1268,6 +1283,20 @@ def generate_level_list(world: World): double_levels_copy = (easy_double_levels_copy.copy() + hard_double_levels_copy.copy()) world.random.shuffle(double_levels_copy) + # Donut Plains + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(0x08) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(double_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(single_levels_copy.pop(0)) + shuffled_level_list.append(castle_fortress_levels_copy.pop(0)) + shuffled_level_list.append(0x28) + shuffled_level_list.append(0x16) + # Vanilla Dome shuffled_level_list.append(double_levels_copy.pop(0)) shuffled_level_list.append(double_levels_copy.pop(0)) diff --git a/worlds/smw/Names/LocationName.py b/worlds/smw/Names/LocationName.py index 847b724f7837..d0adb32b4a13 100644 --- a/worlds/smw/Names/LocationName.py +++ b/worlds/smw/Names/LocationName.py @@ -249,6 +249,8 @@ donut_plains_top_secret = "Top Secret Area" donut_plains_top_secret_tile = "Top Secret Area - Tile" donut_plains_star_road = "Donut Plains - Star Road" +donut_plains_entrance_pipe = "Donut Plains - Entrance Pipe" +donut_plains_exit_pipe = "Donut Plains - Exit Pipe" green_switch_palace_tile = "Green Switch Palace - Tile" @@ -273,6 +275,9 @@ vanilla_dome_castle_tile = "#3 Lemmy's Castle - Tile" vanilla_dome_castle_region = "#3 Lemmy's Castle" vanilla_dome_star_road = "Vanilla Dome - Star Road" +vanilla_dome_bottom_entrance_pipe = "Vanilla Dome - Bottom Entrance Pipe" +vanilla_dome_top_entrance_pipe = "Vanilla Dome - Top Entrance Pipe" +vanilla_dome_top_exit_pipe = "Vanilla Dome - Top Exit Pipe" red_switch_palace_tile = "Red Switch Palace - Tile" @@ -289,6 +294,7 @@ twin_bridges_castle_tile = "#4 Ludwig's Castle - Tile" twin_bridges_castle_region = "#4 Ludwig's Castle" twin_bridges_star_road = "Twin Bridges - Star Road" +twin_bridges_exit_pipe = "Twin Bridges - Exit Pipe" forest_of_illusion_1_tile = "Forest of Illusion 1 - Tile" forest_of_illusion_1_region = "Forest of Illusion 1" @@ -328,6 +334,8 @@ chocolate_fortress_region = "Chocolate Fortress" chocolate_castle_tile = "#6 Wendy's Castle - Tile" chocolate_castle_region = "#6 Wendy's Castle" +chocolate_island_entrance_pipe = "Chocolate Island - Entrance Pipe" +chocolate_island_exit_pipe = "Chocolate Island - Exit Pipe" sunken_ghost_ship_tile = "Sunken Ghost Ship - Tile" sunken_ghost_ship_region = "Sunken Ghost Ship" @@ -347,6 +355,10 @@ valley_castle_tile = "#7 Larry's Castle - Tile" valley_castle_region = "#7 Larry's Castle" valley_star_road = "Valley of Bowser - Star Road" +valley_donut_entrance_pipe = "Valley of Bowser - Donut Entrance Pipe" +valley_donut_exit_pipe = "Valley of Bowser - Donut Exit Pipe" +valley_chocolate_entrance_pipe = "Valley of Bowser - Chocolate Entrance Pipe" +valley_chocolate_exit_pipe = "Valley of Bowser - Chocolate Exit Pipe" front_door_tile = "Front Door - Tile" back_door_tile = "Back Door - Tile" @@ -388,6 +400,22 @@ special_zone_8_region = "Funky" special_complete = "Special Zone - Star Road - Complete" +yi_to_ysp = "Transition - Yoshi's Island to Yellow Switch Palace" +ysp_from_yi = "Transition - Yellow Switch Palace from Yoshi's Island" +yi_to_dp = "Transition - Yoshi's Island to Donut Plains" +dp_from_yi = "Transition - Donut Plains from Yoshi's Island" +dp_to_vd = "Transition - Donut Plains to Vanilla Dome" +vd_from_dp = "Transition - Vanilla Dome from Donut Plains" +tw_to_foi = "Transition - Twin Bridges to Forest of Illusion" +foi_from_tw = "Transition - Forest of Illusion from Twin Bridges" +foi_to_ci = "Transition - Forest of Illusion to Chocolate Island" +ci_from_foi = "Transition - Chocolate Island from Forest of Illusion" +foi_to_sr = "Transition - Forest of Illusion to Star Road" +sr_from_foi = "Transition - Star Road from Forest of Illusion" +ci_to_vob = "Transition - Chocolate Island to Valley of Bowser" +vob_from_ci = "Transition - Valley of Bowser from Chocolate Island" + + vanilla_secret_2_yoshi_block_1 = "Vanilla Secret 2 - Yoshi Block #1" vanilla_secret_2_green_block_1 = "Vanilla Secret 2 - Green Switch Palace Block #1" vanilla_secret_2_powerup_block_1 = "Vanilla Secret 2 - Powerup Block #1" diff --git a/worlds/smw/Options.py b/worlds/smw/Options.py index 0eafa06b796b..0013fe24b4c6 100644 --- a/worlds/smw/Options.py +++ b/worlds/smw/Options.py @@ -209,6 +209,26 @@ class LevelShuffle(Toggle): display_name = "Level Shuffle" +class MapTeleportShuffle(Choice): + """ + Whether map teleports (stars and pipes) are shuffled + """ + display_name = "Map Teleport Shuffle" + option_off = 0 + option_on_only_stars = 1 + option_on_only_pipes = 2 + option_on_both_same_type = 3 + option_on_both_mix = 4 + default = 0 + + +class MapTransitionShuffle(Toggle): + """ + Wheter map transitions are shuffled + """ + display_name = "Map Transition Shuffle" + + class ExcludeSpecialZone(Toggle): """ If active, this option will prevent any progression items from being placed in Special Zone levels. @@ -281,6 +301,9 @@ class PersistentTrapBehavior(Choice): option_gone_after_room_load = 0 option_gone_after_map_load = 1 option_gone_after_level_clear = 2 + option_gone_after_dying_once = 3 + option_gone_after_dying_twice = 4 + option_gone_after_dying_thrice = 5 default = 0 @@ -495,6 +518,8 @@ class EnergyLink(DefaultOnToggle): ]), OptionGroup("Level Shuffling", [ LevelShuffle, + MapTeleportShuffle, + MapTransitionShuffle, ExcludeSpecialZone, BowserCastleDoors, BowserCastleRooms, @@ -545,6 +570,8 @@ class SMWOptions(PerGameCommonOptions): bowser_castle_doors: BowserCastleDoors bowser_castle_rooms: BowserCastleRooms level_shuffle: LevelShuffle + map_teleport_shuffle: MapTeleportShuffle + map_transition_shuffle: MapTransitionShuffle exclude_special_zone: ExcludeSpecialZone boss_shuffle: BossShuffle swap_donut_gh_exits: SwapDonutGhostHouseExits diff --git a/worlds/smw/Regions.py b/worlds/smw/Regions.py index 249604987401..2ab1c1d2b811 100644 --- a/worlds/smw/Regions.py +++ b/worlds/smw/Regions.py @@ -7,8 +7,11 @@ from worlds.generic.Rules import add_rule, set_rule from worlds.AutoWorld import World +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from . import SMWWorld -def create_regions(world: World, active_locations): +def create_regions(world: "SMWWorld", active_locations): multiworld: MultiWorld = world.multiworld player: int = world.player @@ -371,6 +374,18 @@ def create_regions(world: World, active_locations): bowser_region_locations += [LocationName.bowser] bowser_region = create_region(multiworld, player, active_locations, LocationName.bowser_region, bowser_region_locations) + donut_plains_entrance_pipe = create_region(multiworld, player, active_locations, LocationName.donut_plains_entrance_pipe, None) + donut_plains_exit_pipe = create_region(multiworld, player, active_locations, LocationName.donut_plains_exit_pipe, None) + vanilla_dome_bottom_entrance_pipe = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_bottom_entrance_pipe, None) + vanilla_dome_top_entrance_pipe = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_top_entrance_pipe, None) + vanilla_dome_top_exit_pipe = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_top_exit_pipe, None) + twin_bridges_exit_pipe = create_region(multiworld, player, active_locations, LocationName.twin_bridges_exit_pipe, None) + chocolate_island_entrance_pipe = create_region(multiworld, player, active_locations, LocationName.chocolate_island_entrance_pipe, None) + chocolate_island_exit_pipe = create_region(multiworld, player, active_locations, LocationName.chocolate_island_exit_pipe, None) + valley_donut_entrance_pipe = create_region(multiworld, player, active_locations, LocationName.valley_donut_entrance_pipe, None) + valley_donut_exit_pipe = create_region(multiworld, player, active_locations, LocationName.valley_donut_exit_pipe, None) + valley_chocolate_entrance_pipe = create_region(multiworld, player, active_locations, LocationName.valley_chocolate_entrance_pipe, None) + valley_chocolate_exit_pipe = create_region(multiworld, player, active_locations, LocationName.valley_chocolate_exit_pipe, None) donut_plains_star_road = create_region(multiworld, player, active_locations, LocationName.donut_plains_star_road, None) vanilla_dome_star_road = create_region(multiworld, player, active_locations, LocationName.vanilla_dome_star_road, None) @@ -462,6 +477,20 @@ def create_regions(world: World, active_locations): [LocationName.special_zone_8_exit_1]) special_complete = create_region(multiworld, player, active_locations, LocationName.special_complete, None) + yi_to_ysp = create_region(multiworld, player, active_locations, LocationName.yi_to_ysp, None) + ysp_from_yi = create_region(multiworld, player, active_locations, LocationName.ysp_from_yi, None) + yi_to_dp = create_region(multiworld, player, active_locations, LocationName.yi_to_dp, None) + dp_from_yi = create_region(multiworld, player, active_locations, LocationName.dp_from_yi, None) + dp_to_vd = create_region(multiworld, player, active_locations, LocationName.dp_to_vd, None) + vd_from_dp = create_region(multiworld, player, active_locations, LocationName.vd_from_dp, None) + tw_to_foi = create_region(multiworld, player, active_locations, LocationName.tw_to_foi, None) + foi_from_tw = create_region(multiworld, player, active_locations, LocationName.foi_from_tw, None) + foi_to_ci = create_region(multiworld, player, active_locations, LocationName.foi_to_ci, None) + ci_from_foi = create_region(multiworld, player, active_locations, LocationName.ci_from_foi, None) + foi_to_sr = create_region(multiworld, player, active_locations, LocationName.foi_to_sr, None) + sr_from_foi = create_region(multiworld, player, active_locations, LocationName.sr_from_foi, None) + ci_to_vob = create_region(multiworld, player, active_locations, LocationName.ci_to_vob, None) + vob_from_ci = create_region(multiworld, player, active_locations, LocationName.vob_from_ci, None) # Set up the regions correctly. multiworld.regions += [ @@ -668,6 +697,18 @@ def create_regions(world: World, active_locations): back_door_tile, back_door_region, bowser_region, + donut_plains_entrance_pipe, + donut_plains_exit_pipe, + vanilla_dome_bottom_entrance_pipe, + vanilla_dome_top_entrance_pipe, + vanilla_dome_top_exit_pipe, + twin_bridges_exit_pipe, + chocolate_island_entrance_pipe, + chocolate_island_exit_pipe, + valley_donut_entrance_pipe, + valley_donut_exit_pipe, + valley_chocolate_entrance_pipe, + valley_chocolate_exit_pipe, donut_plains_star_road, vanilla_dome_star_road, twin_bridges_star_road, @@ -725,6 +766,20 @@ def create_regions(world: World, active_locations): special_zone_8_region, special_zone_8_exit_1, special_complete, + yi_to_ysp, + ysp_from_yi, + yi_to_dp, + dp_from_yi, + dp_to_vd, + vd_from_dp, + tw_to_foi, + foi_from_tw, + foi_to_ci, + ci_from_foi, + foi_to_sr, + sr_from_foi, + ci_to_vob, + vob_from_ci, ] @@ -1849,7 +1904,7 @@ def create_regions(world: World, active_locations): add_location_to_region(multiworld, player, active_locations, LocationName.star_road_5_region, LocationName.star_road_5_green_block_20, lambda state: (state.has(ItemName.green_switch_palace, player) and state.has(ItemName.yoshi_activate, player) and state.has(ItemName.mario_carry, player) and state.has(ItemName.special_world_clear, player))) -def connect_regions(world: World, level_to_tile_dict): +def connect_regions(world: "SMWWorld", level_to_tile_dict): multiworld: MultiWorld = world.multiworld player: int = world.player @@ -2084,6 +2139,14 @@ def connect_regions(world: World, level_to_tile_dict): # Connect levels to each other + should_cache = [ + LocationName.star_road_1_tile, + LocationName.star_road_2_tile, + LocationName.star_road_3_tile, + LocationName.star_road_4_tile, + ] + future_star_cache = dict() + for current_level_id, current_level_data in level_info_dict.items(): # Connect tile regions to correct level regions @@ -2093,7 +2156,8 @@ def connect_regions(world: World, level_to_tile_dict): current_tile_id = level_to_tile_dict[current_level_id] current_tile_data = level_info_dict[current_tile_id] current_tile_name = current_tile_data.levelName - if ("Star Road - " not in current_tile_name) and (" - Star Road" not in current_tile_name): + + if ("Star Road - " not in current_tile_name) and (" - Star Road" not in current_tile_name) and ("Pipe" not in current_tile_name) and ("Transition - " not in current_tile_name): current_tile_name += " - Tile" connect(world, current_tile_name, current_level_data.levelName) # Connect Exit regions to next tile regions @@ -2102,42 +2166,68 @@ def connect_regions(world: World, level_to_tile_dict): if world.options.swap_donut_gh_exits and current_tile_id == 0x04: next_tile_id = current_tile_data.exit2Path.otherLevelID next_tile_name = level_info_dict[next_tile_id].levelName - if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): + if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name) and ("Pipe" not in next_tile_name) and ("Transition - " not in next_tile_name): next_tile_name += " - Tile" + else: + if current_tile_name == LocationName.star_road_5_tile: + future_star_cache[next_tile_name] = current_tile_data.eventIDValue + else: + world.cached_connections[next_tile_name] = current_tile_data.eventIDValue current_exit_name = (current_level_data.levelName + " - Normal Exit") connect(world, current_exit_name, next_tile_name) + if current_tile_data.exit2Path: next_tile_id = current_tile_data.exit2Path.otherLevelID if world.options.swap_donut_gh_exits and current_tile_id == 0x04: next_tile_id = current_tile_data.exit1Path.otherLevelID next_tile_name = level_info_dict[next_tile_id].levelName - if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name): + if ("Star Road - " not in next_tile_name) and (" - Star Road" not in next_tile_name) and ("Pipe" not in next_tile_name) and ("Transition - " not in next_tile_name): next_tile_name += " - Tile" + else: + if current_tile_name in should_cache: + future_star_cache[next_tile_name] = current_tile_data.eventIDValue + 1 + else: + world.cached_connections[next_tile_name] = current_tile_data.eventIDValue + 1 current_exit_name = (current_level_data.levelName + " - Secret Exit") connect(world, current_exit_name, next_tile_name) - connect(world, LocationName.donut_plains_star_road, LocationName.star_road_donut) - connect(world, LocationName.star_road_donut, LocationName.donut_plains_star_road) + # Fix cached connections + for old_exit, event_id in future_star_cache.items(): + entrance = world.reverse_teleport_pairs[old_exit] + world.cached_connections[entrance] = event_id + + # Connect teleport destination tiles with level tiles connect(world, LocationName.star_road_donut, LocationName.star_road_1_tile) - connect(world, LocationName.vanilla_dome_star_road, LocationName.star_road_vanilla) - connect(world, LocationName.star_road_vanilla, LocationName.vanilla_dome_star_road) connect(world, LocationName.star_road_vanilla, LocationName.star_road_2_tile) - connect(world, LocationName.twin_bridges_star_road, LocationName.star_road_twin_bridges) - connect(world, LocationName.star_road_twin_bridges, LocationName.twin_bridges_star_road) connect(world, LocationName.star_road_twin_bridges, LocationName.star_road_3_tile) - connect(world, LocationName.forest_star_road, LocationName.star_road_forest) - connect(world, LocationName.star_road_forest, LocationName.forest_star_road) connect(world, LocationName.star_road_forest, LocationName.star_road_4_tile) - connect(world, LocationName.valley_star_road, LocationName.star_road_valley) - connect(world, LocationName.star_road_valley, LocationName.valley_star_road) connect(world, LocationName.star_road_valley, LocationName.star_road_5_tile) - connect(world, LocationName.star_road_special, LocationName.special_star_road) - connect(world, LocationName.special_star_road, LocationName.star_road_special) connect(world, LocationName.special_star_road, LocationName.special_zone_1_tile) - - connect(world, LocationName.star_road_valley, LocationName.front_door_tile) - - + connect(world, LocationName.donut_plains_exit_pipe, LocationName.donut_plains_3_tile) + connect(world, LocationName.valley_donut_exit_pipe, LocationName.donut_secret_2_tile) + connect(world, LocationName.vanilla_dome_top_exit_pipe, LocationName.vanilla_secret_2_tile) + connect(world, LocationName.twin_bridges_exit_pipe, LocationName.cheese_bridge_tile) + connect(world, LocationName.valley_chocolate_exit_pipe, LocationName.chocolate_secret_tile) + connect(world, LocationName.chocolate_island_exit_pipe, LocationName.chocolate_castle_tile) + + # Connect teleports + for entrance, exit in world.teleport_pairs.items(): + connect(world, entrance, exit) + if LocationName.yoshis_house_tile in exit: + connect(world, exit, entrance) + + # Connect transition destination "tiles" with level tiles + connect(world, LocationName.ysp_from_yi, LocationName.yellow_switch_palace_tile) + connect(world, LocationName.dp_from_yi, LocationName.donut_plains_1_tile) + connect(world, LocationName.vd_from_dp, LocationName.vanilla_dome_1_tile) + connect(world, LocationName.foi_from_tw, LocationName.forest_of_illusion_1_tile) + connect(world, LocationName.ci_from_foi, LocationName.forest_castle_tile) + connect(world, LocationName.sr_from_foi, LocationName.forest_fortress_tile) + connect(world, LocationName.vob_from_ci, LocationName.valley_of_bowser_1_tile) + + # Connect transitions + for entrance, exit in world.transition_pairs.items(): + connect(world, entrance, exit) def create_region(multiworld: MultiWorld, player: int, active_locations, name: str, locations=None): ret = Region(name, player, multiworld) @@ -2161,7 +2251,7 @@ def add_location_to_region(multiworld: MultiWorld, player: int, active_locations add_rule(location, rule) -def connect(world: World, source: str, target: str, +def connect(world: "SMWWorld", source: str, target: str, rule: typing.Optional[typing.Callable] = None): source_region: Region = world.get_region(source) target_region: Region = world.get_region(target) diff --git a/worlds/smw/Rom.py b/worlds/smw/Rom.py index f7b3bd707c48..8776628f49c8 100644 --- a/worlds/smw/Rom.py +++ b/worlds/smw/Rom.py @@ -1,10 +1,11 @@ import Utils -from worlds.AutoWorld import World, AutoWorldRegister +from worlds.AutoWorld import AutoWorldRegister from settings import get_settings from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension from .Aesthetics import generate_shuffled_ow_palettes, generate_curated_level_palette_data, generate_curated_map_palette_data, generate_shuffled_sfx 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 +from .Teleports import handle_teleport_shuffle, handle_transition_shuffle, handle_silent_events USHASH = 'cdd3c8c37322978ca8669b34bc89c804' ROM_PLAYER_LIMIT = 65535 @@ -15,6 +16,9 @@ import json import random +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from . import SMWWorld ability_rom_data = { 0xBC0003: [[0x1F1C, 0x7]], # Run 0x80 @@ -203,12 +207,6 @@ def handle_uncompressed_graphics(caller: APProcedurePatch, rom: bytes) -> bytes: order = [0x30, 0x31, 0x32, 0x33, 0x34] player_name_tiles = copy_gfx_tiles(decompressed_gfx_28, order, [4, 16]) - # Patch GFX 00 with new data - patched_gfx_00 = bsdiff4.patch(bytes(decompressed_gfx_00), caller.get_file("sprite_page_1.bsdiff4")) - patched_gfx_00 = bytearray(patched_gfx_00) - patched_gfx_01 = bsdiff4.patch(bytes(decompressed_gfx_01), caller.get_file("sprite_page_2.bsdiff4")) - patched_gfx_01 = bytearray(patched_gfx_01) - # Create inventory order = [ 0x0024,0x0024,0x0026,0x0026,0x000E,0x000E,0x0048,0x0048, @@ -217,18 +215,22 @@ def handle_uncompressed_graphics(caller: APProcedurePatch, rom: bytes) -> bytes: 0x0066,0x0066,0x0066,0x0066,0x0066,0x0066,0x0843,0x0066, ] inventory_gfx = copy_sprite_tiles(raw_sprite_graphics, order, 4) - inventory_gfx = bsdiff4.patch(bytes(inventory_gfx), caller.get_file("map_sprites.bsdiff4")) - inventory_gfx = bytearray(inventory_gfx) + + # Patch graphics with modified data + patched_sprite_graphics = bsdiff4.patch(bytes(sprite_graphics), caller.get_file("sprite_graphics.bsdiff4")) + patched_gfx_00 = bsdiff4.patch(bytes(decompressed_gfx_00), caller.get_file("sprite_page_1.bsdiff4")) + patched_gfx_01 = bsdiff4.patch(bytes(decompressed_gfx_01), caller.get_file("sprite_page_2.bsdiff4")) + patched_inventory_gfx = bsdiff4.patch(bytes(inventory_gfx), caller.get_file("map_sprites.bsdiff4")) rom[0xE0000:0xE0000 + len(decompressed_player_gfx)] = decompressed_player_gfx rom[0xE8000:0xE8000 + len(decompressed_animated_gfx)] = decompressed_animated_gfx rom[0xE6000:0xE6000 + len(player_small_tiles)] = player_small_tiles rom[0xE6400:0xE6400 + len(player_map_tiles)] = player_map_tiles rom[0xE6C00:0xE6C00 + len(player_name_tiles)] = player_name_tiles - rom[0xEC000:0xEC000 + len(inventory_gfx)] = inventory_gfx - rom[0x100000:0x100000 + len(sprite_graphics)] = sprite_graphics - rom[0x178000:0x178000 + len(patched_gfx_00)] = patched_gfx_00 - rom[0x179000:0x179000 + len(patched_gfx_01)] = patched_gfx_01 + rom[0xEC000:0xEC000 + len(patched_inventory_gfx)] = bytearray(patched_inventory_gfx) + rom[0x100000:0x100000 + len(patched_sprite_graphics)] = bytearray(patched_sprite_graphics) + rom[0x178000:0x178000 + len(patched_gfx_00)] = bytearray(patched_gfx_00) + rom[0x179000:0x179000 + len(patched_gfx_01)] = bytearray(patched_gfx_01) return bytes(rom) @@ -241,6 +243,7 @@ def generate_shuffled_header_data(caller: APProcedurePatch, rom: bytes) -> bytes from .Aesthetics import valid_foreground_palettes, valid_background_palettes, valid_background_colors rom = bytearray(rom) + random.seed(options["seed"]) for level_id in range(0, 0x200): layer1_ptr = int.from_bytes(rom[0x2E000 + level_id * 3:(0x2E000 + level_id * 3) + 3], "little") @@ -254,8 +257,6 @@ def generate_shuffled_header_data(caller: APProcedurePatch, rom: bytes) -> bytes tileset = level_header[4] & 0x0F - random.seed(options["seed"]) - if options["music_shuffle"] == 2: level_header[2] &= 0x8F level_header[2] |= (random.randint(0, 7) << 5) @@ -324,6 +325,9 @@ def replace_graphics(caller: APProcedurePatch, rom: bytes) -> bytes: if "yoshi+anim.bin" in file.namelist(): yoshi_anim_file = file.read('yoshi+anim.bin') rom[0xE8000:0xE8000 + len(yoshi_anim_file)] = yoshi_anim_file + if "sprites.bin" in file.namelist(): + sprites_file = file.read('sprites.bin') + rom[0x100000:0x100000 + len(sprites_file)] = sprites_file return bytes(rom) @@ -476,7 +480,7 @@ def handle_level_shuffle(rom, active_level_dict): rom.write_byte(0x37F00 + tile_id, level_id) -def handle_location_item_info(patch, world: World): +def handle_location_item_info(patch: SMWProcedurePatch, world: "SMWWorld"): from .Levels import location_id_to_level_id block_info = bytearray([0x00 for _ in range(582)]) @@ -512,7 +516,7 @@ def handle_location_item_info(patch, world: World): patch.write_bytes(0x88207, block_info) -def handle_music_shuffle(patch, world: World): +def handle_music_shuffle(patch: SMWProcedurePatch, world: "SMWWorld"): from .Aesthetics import generate_shuffled_level_music, generate_shuffled_ow_music, level_music_address_data, ow_music_address_data shuffled_level_music = generate_shuffled_level_music(world) @@ -525,7 +529,7 @@ def handle_music_shuffle(patch, world: World): patch.write_byte(addr, shuffled_ow_music[i]) -def handle_mario_palette(patch, world: World): +def handle_mario_palette(patch: SMWProcedurePatch, world: "SMWWorld"): from .Aesthetics import mario_palettes, fire_mario_palettes, ow_mario_palettes chosen_palette = world.options.mario_palette.value @@ -535,7 +539,7 @@ def handle_mario_palette(patch, world: World): patch.write_bytes(0x359C, bytes(ow_mario_palettes[chosen_palette])) -def handle_swap_donut_gh_exits(patch): +def handle_swap_donut_gh_exits(patch: SMWProcedurePatch): patch.write_bytes(0x2567C, bytes([0xC0])) patch.write_bytes(0x25873, bytes([0xA9])) patch.write_bytes(0x25875, bytes([0x85])) @@ -547,7 +551,7 @@ def handle_swap_donut_gh_exits(patch): patch.write_bytes(0x26371, bytes([0x32])) -def handle_bowser_rooms(patch, world: World): +def handle_bowser_rooms(patch: SMWProcedurePatch, world: "SMWWorld"): if world.options.bowser_castle_rooms == "random_two_room": chosen_rooms = world.random.sample(standard_bowser_rooms, 2) @@ -601,7 +605,7 @@ def handle_bowser_rooms(patch, world: World): patch.write_byte(bowser_rooms_copy[len(bowser_rooms_copy)-1].exitAddress, 0xBD) -def handle_boss_shuffle(patch, world: World): +def handle_boss_shuffle(patch: SMWProcedurePatch, world: "SMWWorld"): if world.options.boss_shuffle == "simple": submap_boss_rooms_copy = submap_boss_rooms.copy() ow_boss_rooms_copy = ow_boss_rooms.copy() @@ -644,11 +648,11 @@ def handle_boss_shuffle(patch, world: World): patch.write_byte(ow_boss_rooms[i].exitAddressAlt, chosen_ow_boss.roomID) -def snes_to_pc(address): +def snes_to_pc(address: int): return (address & 0x7F0000) >> 1 | (address & 0x7FFF) -def patch_rom(world: World, patch, player, active_level_dict: typing.Dict[int,int]) -> None: +def patch_rom(world: "SMWWorld", patch: SMWProcedurePatch, player: int, active_level_dict: typing.Dict[int,int]) -> None: options_dict = { "seed": world.random.getrandbits(64), "music_shuffle": world.options.music_shuffle.value, @@ -699,6 +703,16 @@ def patch_rom(world: World, patch, player, active_level_dict: typing.Dict[int,in # Handle Level Shuffle handle_level_shuffle(patch, active_level_dict) + if world.options.map_teleport_shuffle != "off": + handle_teleport_shuffle(patch, world) + + #if world.options.map_transition_shuffle: + if world.options.map_transition_shuffle: + handle_transition_shuffle(patch, world) + + if world.options.map_teleport_shuffle or world.options.map_transition_shuffle: + handle_silent_events(patch, world) + # Handle Music Shuffle if world.options.music_shuffle != "none": handle_music_shuffle(patch, world) diff --git a/worlds/smw/Teleports.py b/worlds/smw/Teleports.py new file mode 100644 index 000000000000..a7cc5177de06 --- /dev/null +++ b/worlds/smw/Teleports.py @@ -0,0 +1,565 @@ +from .Names import LocationName + +from typing import TYPE_CHECKING, Dict, List +if TYPE_CHECKING: + from . import SMWWorld, SMWProcedurePatch + + +smw_star_pairs = { + LocationName.donut_plains_star_road: LocationName.star_road_donut, + LocationName.vanilla_dome_star_road: LocationName.star_road_vanilla, + LocationName.twin_bridges_star_road: LocationName.star_road_twin_bridges, + LocationName.forest_star_road: LocationName.star_road_forest, + LocationName.valley_star_road: LocationName.star_road_valley, + LocationName.star_road_special: LocationName.special_star_road, + LocationName.special_complete: LocationName.yoshis_house_tile, +} + +smw_pipe_pairs = { + LocationName.donut_plains_entrance_pipe: LocationName.valley_donut_exit_pipe, + LocationName.valley_donut_entrance_pipe: LocationName.donut_plains_exit_pipe, + LocationName.vanilla_dome_top_entrance_pipe: LocationName.vanilla_dome_top_exit_pipe, + LocationName.vanilla_dome_bottom_entrance_pipe: LocationName.twin_bridges_exit_pipe, + LocationName.chocolate_island_entrance_pipe: LocationName.valley_chocolate_exit_pipe, + LocationName.valley_chocolate_entrance_pipe: LocationName.chocolate_island_exit_pipe, +} + +smw_offscreen_event_indexes = { + LocationName.valley_donut_exit_pipe: [0x12, 0x11, 0x03], + LocationName.donut_plains_exit_pipe: [0x14, 0x08, 0x05], + LocationName.vanilla_dome_top_exit_pipe: [0x1D, 0x0D, 0x04], + LocationName.twin_bridges_exit_pipe: [0x1C, 0x14, 0x01], + LocationName.valley_chocolate_exit_pipe: [0x47, 0x1D, 0x01], + LocationName.vanilla_dome_star_road: [0x52, 0x2C, 0x01], + LocationName.twin_bridges_star_road: [0x55, 0x2D, 0x01], + LocationName.forest_star_road: [0x58, 0x2E, 0x01], + LocationName.donut_plains_star_road: [0x5D, 0x2F, 0x01], + LocationName.star_road_special: [0x5E, 0x30, 0x01], + LocationName.special_complete: [0x6C, 0x31, 0x01], + LocationName.valley_star_road: [0x5B, 0x32, 0x04], + + LocationName.dp_from_yi: [0x06, 0x00, 0x08], + LocationName.ysp_from_yi: [0x01, 0x20, 0x01], + LocationName.vd_from_dp: [0x0F, 0x24, 0x03], + LocationName.foi_from_tw: [0x24, 0x21, 0x02], + LocationName.ci_from_foi: [0x2F, 0x15, 0x05], + LocationName.sr_from_foi: [0x34, 0x1A, 0x03], + LocationName.vob_from_ci: [0x4E, 0x1E, 0x02], + +} + +smw_teleport_data = { + LocationName.donut_plains_star_road: [0x1A, 0x78, 0x00, 0x28, 0x01], + LocationName.star_road_donut: [0x0C, 0x28, 0x0D, 0xD8, 0x01], + LocationName.vanilla_dome_star_road: [0x1E, 0x08, 0x04, 0xE8, 0x00], + LocationName.star_road_vanilla: [0x1C, 0x28, 0x0D, 0x88, 0x01], + LocationName.twin_bridges_star_road: [0x22, 0x08, 0x01, 0xF8, 0x00], + LocationName.star_road_twin_bridges: [0x20, 0x78, 0x0D, 0x68, 0x01], + LocationName.forest_star_road: [0x26, 0x48, 0x01, 0x08, 0x01], + LocationName.star_road_forest: [0x24, 0xC8, 0x0D, 0x88, 0x01], + LocationName.valley_star_road: [0x2A, 0x48, 0x09, 0x38, 0x00], + LocationName.star_road_valley: [0x32, 0xC8, 0x0D, 0xD8, 0x01], + LocationName.star_road_special: [0x2E, 0x78, 0x0D, 0x88, 0x01], + LocationName.special_star_road: [0x2C, 0x18, 0x0B, 0x38, 0x01], + LocationName.special_complete: [0x34, 0x18, 0x0B, 0x18, 0x01], + LocationName.yoshis_house_tile: [0x30, 0x68, 0x02, 0x78, 0x00], + LocationName.donut_plains_entrance_pipe: [0x12, 0x98, 0x00, 0x08, 0x01], + LocationName.valley_donut_exit_pipe: [0x04, 0x08, 0x09, 0x38, 0x00], + LocationName.valley_donut_entrance_pipe: [0x06, 0x28, 0x09, 0x18, 0x00], + LocationName.donut_plains_exit_pipe: [0x14, 0xB8, 0x00, 0xE8, 0x00], + LocationName.vanilla_dome_top_entrance_pipe: [0x02, 0x38, 0x04, 0xB8, 0x00], + LocationName.vanilla_dome_top_exit_pipe: [0x10, 0xA8, 0x00, 0x38, 0x00], + LocationName.vanilla_dome_bottom_entrance_pipe: [0x00, 0xA8, 0x04, 0x48, 0x01], + LocationName.twin_bridges_exit_pipe: [0x0E, 0x18, 0x01, 0x78, 0x00], + LocationName.chocolate_island_entrance_pipe: [0x16, 0x28 ,0x01, 0x78, 0x01], + LocationName.valley_chocolate_exit_pipe: [0x08, 0xC8, 0x09, 0x98, 0x00], + LocationName.valley_chocolate_entrance_pipe: [0x0A, 0x48, 0x09, 0x98, 0x00], + LocationName.chocolate_island_exit_pipe: [0x18, 0xA8, 0x00, 0x88, 0x01], +} + +teleport_pairs = {**smw_star_pairs, **smw_pipe_pairs} +tp_keys = list(teleport_pairs.keys()) +tp_values = list(teleport_pairs.values()) + +smw_transition_pairs = { + LocationName.yi_to_ysp: LocationName.ysp_from_yi, + LocationName.yi_to_dp: LocationName.dp_from_yi, + LocationName.dp_to_vd: LocationName.vd_from_dp, + LocationName.tw_to_foi: LocationName.foi_from_tw, + LocationName.foi_to_ci: LocationName.ci_from_foi, + LocationName.foi_to_sr: LocationName.sr_from_foi, + LocationName.ci_to_vob: LocationName.vob_from_ci, +} + +# Data order: +# 0: Offset/Index to ROM +# 1-3: Y Coords for tile +# 4-5: X Coords for tile +# 6: Submap ID +# 7-8: YX Coords >> 4 + Adjustment +# 9: Walk direction when used as destination +# 0: Up +# 1: Left +# 2: Down +# 3: Right + +smw_transition_data = { + LocationName.yi_to_ysp: [0x00, 0x00, 0x00, 0x48, 0x00, 0x01, 0x00, 0x04, 0x02], + LocationName.ysp_from_yi: [0x02, 0x50, 0x01, 0x28, 0x00, 0x00, 0x14, 0x02, 0x00], + + LocationName.yi_to_dp: [0x01, 0x00, 0x00, 0x98, 0x00, 0x01, 0x00, 0x09, 0x02], + LocationName.dp_from_yi: [0x03, 0x60, 0x01, 0x58, 0x00, 0x00, 0x15, 0x05, 0x00], + + LocationName.dp_to_vd: [0x05, 0x90, 0x00, 0xD8, 0x00, 0x00, 0x09, 0x0D, 0x02], + LocationName.vd_from_dp: [0x04, 0x50, 0x01, 0x58, 0x00, 0x02, 0x14, 0x05, 0x00], + + LocationName.tw_to_foi: [0x09, 0xB0, 0x00, 0xC8, 0x01, 0x00, 0x0A, 0x1C, 0x00], + LocationName.foi_from_tw: [0x08, 0x50, 0x01, 0x88, 0x00, 0x03, 0x15, 0x08, 0x02], + + LocationName.foi_to_ci: [0x0C, 0x00, 0x02, 0x88, 0x00, 0x03, 0x1F, 0x08, 0x00], + LocationName.ci_from_foi: [0x0D, 0x00, 0x01, 0xC8, 0x01, 0x00, 0x10, 0x1C, 0x02], + + LocationName.foi_to_sr: [0x0A, 0xE8, 0x01, 0x00, 0x00, 0x03, 0x1E, 0x00, 0x06], + LocationName.sr_from_foi: [0x0B, 0x08, 0x01, 0xA0 ,0x01, 0x00, 0x10, 0x19, 0x04], + + LocationName.ci_to_vob: [0x06, 0x50, 0x01, 0xE8, 0x00, 0x00, 0x15, 0x0E, 0x02], + LocationName.vob_from_ci: [0x07, 0xA0, 0x00, 0xE8, 0x01, 0x04, 0x09, 0x1E, 0x00], +} + +tr_keys = list(smw_transition_pairs.keys()) +tr_values = list(smw_transition_pairs.values()) + +region_mapping: Dict[str, List[str]] = { + # Let regions with excluded locations go first + # Fill everything else, with YI as a priority + LocationName.yoshis_house_tile: [ + LocationName.yi_to_ysp, + LocationName.yi_to_dp, + ], + LocationName.ysp_from_yi: [ + # dead end lol + ], + LocationName.dp_from_yi: [ + LocationName.dp_to_vd, + LocationName.donut_plains_entrance_pipe, + LocationName.donut_plains_star_road, + ], + LocationName.valley_donut_exit_pipe: [ + LocationName.valley_donut_entrance_pipe, + ], + LocationName.donut_plains_exit_pipe: [ + LocationName.dp_to_vd, + ], + LocationName.vd_from_dp: [ + LocationName.vanilla_dome_top_entrance_pipe, + LocationName.vanilla_dome_bottom_entrance_pipe, + LocationName.vanilla_dome_star_road, + ], + LocationName.twin_bridges_exit_pipe: [ + LocationName.twin_bridges_star_road, + LocationName.tw_to_foi, + ], + LocationName.vanilla_dome_top_exit_pipe: [ + LocationName.tw_to_foi, + ], + LocationName.foi_from_tw: [ + LocationName.foi_to_ci, + LocationName.foi_to_sr, + ], + LocationName.sr_from_foi: [ + LocationName.forest_star_road, + ], + LocationName.ci_from_foi: [ + LocationName.chocolate_island_entrance_pipe, + LocationName.ci_to_vob, + ], + LocationName.chocolate_island_exit_pipe: [ + LocationName.ci_to_vob, + ], + LocationName.valley_chocolate_exit_pipe: [ + LocationName.valley_chocolate_entrance_pipe, + ], + LocationName.vob_from_ci: [ + LocationName.valley_star_road, + ], + LocationName.star_road_donut: [ + LocationName.star_road_special, + ], + LocationName.star_road_vanilla: [ + LocationName.star_road_special, + ], + LocationName.star_road_twin_bridges: [ + LocationName.star_road_special, + ], + LocationName.star_road_forest: [ + LocationName.star_road_special, + ], + LocationName.star_road_valley: [ + LocationName.star_road_special, + ], + LocationName.special_star_road: [ + LocationName.special_complete, + ], + +} + +region_excluded_destinations: Dict[str, List[str]] = { + LocationName.yi_to_ysp: [ + LocationName.sr_from_foi, + LocationName.vob_from_ci, + ], + LocationName.yi_to_dp: [ + LocationName.sr_from_foi, + LocationName.vob_from_ci, + ], + LocationName.dp_to_vd: [ + ], + LocationName.tw_to_foi: [ + ], + LocationName.foi_to_ci: [], + LocationName.foi_to_sr: [], + LocationName.ci_to_vob: [], + LocationName.donut_plains_entrance_pipe: [ + LocationName.donut_plains_exit_pipe, + ], + LocationName.valley_donut_entrance_pipe: [ + LocationName.valley_donut_exit_pipe, + ], + LocationName.vanilla_dome_top_entrance_pipe: [ + LocationName.yoshis_house_tile, + ], + LocationName.vanilla_dome_bottom_entrance_pipe: [ + LocationName.yoshis_house_tile, + ], + LocationName.chocolate_island_entrance_pipe: [ + LocationName.chocolate_island_exit_pipe, + ], + LocationName.valley_chocolate_entrance_pipe: [], + LocationName.donut_plains_star_road: [], + LocationName.vanilla_dome_star_road: [], + LocationName.twin_bridges_star_road: [], + LocationName.forest_star_road: [], + LocationName.valley_star_road: [], + LocationName.star_road_special: [], + LocationName.special_complete: [ + LocationName.special_star_road, + ], +} + + +def generate_entrance_rando(world: "SMWWorld"): + # This shuffle method walks through every valid exit (starting from YI house) to assign new exits to unconnected entrances + # It attempts to swap around exits without any consideration until everything is connected + # Warning: It's very stupid and slow, feel free to refactor it LOL + # TODO: Pory + world.transition_pairs = {**smw_transition_pairs} + world.transition_data = {**smw_transition_data} + world.teleport_pairs = {**smw_star_pairs, **smw_pipe_pairs} + world.teleport_data = {**smw_teleport_data} + + local_mapping: Dict[str, str] = {} + next_exits = [LocationName.yoshis_house_tile] + processed_exits = [] + used_exits = [] + local_region_mapping = {**region_mapping} + local_excluded_destinations = {**region_excluded_destinations} + prefilled_exits: Dict[str, str] = {} + + if world.options.exclude_special_zone: + prefilled_exits[LocationName.star_road_special] = LocationName.special_star_road + prefilled_exits[LocationName.special_complete] = LocationName.yoshis_house_tile + local_region_mapping.pop(LocationName.special_star_road) + + if world.options.map_teleport_shuffle == "off": + prefilled_exits[LocationName.donut_plains_star_road] = LocationName.star_road_donut + prefilled_exits[LocationName.vanilla_dome_star_road] = LocationName.star_road_vanilla + prefilled_exits[LocationName.twin_bridges_star_road] = LocationName.star_road_twin_bridges + prefilled_exits[LocationName.forest_star_road] = LocationName.star_road_forest + prefilled_exits[LocationName.valley_star_road] = LocationName.star_road_valley + prefilled_exits[LocationName.star_road_special] = LocationName.special_star_road + prefilled_exits[LocationName.special_complete] = LocationName.yoshis_house_tile + + prefilled_exits[LocationName.donut_plains_entrance_pipe] = LocationName.valley_donut_exit_pipe + prefilled_exits[LocationName.valley_donut_entrance_pipe] = LocationName.donut_plains_exit_pipe + prefilled_exits[LocationName.vanilla_dome_top_entrance_pipe] = LocationName.vanilla_dome_top_exit_pipe + prefilled_exits[LocationName.vanilla_dome_bottom_entrance_pipe] = LocationName.twin_bridges_exit_pipe + prefilled_exits[LocationName.chocolate_island_entrance_pipe] = LocationName.valley_chocolate_exit_pipe + prefilled_exits[LocationName.valley_chocolate_entrance_pipe] = LocationName.chocolate_island_exit_pipe + + elif world.options.map_teleport_shuffle == "on_only_stars": + prefilled_exits[LocationName.donut_plains_entrance_pipe] = LocationName.valley_donut_exit_pipe + prefilled_exits[LocationName.valley_donut_entrance_pipe] = LocationName.donut_plains_exit_pipe + prefilled_exits[LocationName.vanilla_dome_top_entrance_pipe] = LocationName.vanilla_dome_top_exit_pipe + prefilled_exits[LocationName.vanilla_dome_bottom_entrance_pipe] = LocationName.twin_bridges_exit_pipe + prefilled_exits[LocationName.chocolate_island_entrance_pipe] = LocationName.valley_chocolate_exit_pipe + prefilled_exits[LocationName.valley_chocolate_entrance_pipe] = LocationName.chocolate_island_exit_pipe + + elif world.options.map_teleport_shuffle == "on_only_pipes": + prefilled_exits[LocationName.donut_plains_star_road] = LocationName.star_road_donut + prefilled_exits[LocationName.vanilla_dome_star_road] = LocationName.star_road_vanilla + prefilled_exits[LocationName.twin_bridges_star_road] = LocationName.star_road_twin_bridges + prefilled_exits[LocationName.forest_star_road] = LocationName.star_road_forest + prefilled_exits[LocationName.valley_star_road] = LocationName.star_road_valley + prefilled_exits[LocationName.star_road_special] = LocationName.special_star_road + prefilled_exits[LocationName.special_complete] = LocationName.yoshis_house_tile + + if not world.options.map_transition_shuffle: + prefilled_exits[LocationName.yi_to_ysp] = LocationName.ysp_from_yi + prefilled_exits[LocationName.yi_to_dp] = LocationName.dp_from_yi + prefilled_exits[LocationName.dp_to_vd] = LocationName.vd_from_dp + prefilled_exits[LocationName.tw_to_foi] = LocationName.foi_from_tw + prefilled_exits[LocationName.foi_to_ci] = LocationName.ci_from_foi + prefilled_exits[LocationName.foi_to_sr] = LocationName.sr_from_foi + prefilled_exits[LocationName.ci_to_vob] = LocationName.vob_from_ci + + + if world.options.map_teleport_shuffle == "off" and world.options.map_transition_shuffle: + local_excluded_destinations[LocationName.yi_to_dp].append(LocationName.ysp_from_yi) + local_excluded_destinations[LocationName.yi_to_ysp].append(LocationName.sr_from_foi) + local_excluded_destinations[LocationName.foi_to_sr].append(LocationName.ysp_from_yi) + local_excluded_destinations[LocationName.foi_to_ci].append(LocationName.sr_from_foi) + + + for entrance, exit in prefilled_exits.items(): + local_mapping[entrance] = exit + + #print (local_mapping) + + used_exits = list(prefilled_exits.values()) + + swap_count = 0 + + while len(processed_exits) != len(local_region_mapping.keys()): + if len(next_exits) == 0: + # Swap exits if we haven't met the processed exits goal + #print ("-------- SWAP ----------") + unreached_transitions = [x for x in smw_transition_pairs.values() if x not in local_mapping.values()] + unreached_teleports = [x for x in teleport_pairs.values() if x not in local_mapping.values()] + unreached_exits = unreached_transitions + unreached_teleports + #print ("UNREACHED: ", unreached_exits) + if len(unreached_exits) == 0 or swap_count >= 10: + #print (" ########################################################## RESTARTING SHUFFLE ##########################################################") + local_mapping = {} + for entrance, exit in prefilled_exits.items(): + local_mapping[entrance] = exit + next_exits = [LocationName.yoshis_house_tile] + processed_exits = [] + used_exits = list(prefilled_exits.values()) + swap_count = 0 + continue + unreached_candidate = world.random.choice(unreached_exits) + if "Transition - " in unreached_candidate: + candidates = [x for x in processed_exits if "Transition - " in x and x not in prefilled_exits.values()] + else: + if world.options.map_teleport_shuffle != "on_both_mix": + if "Pipe" in unreached_candidate: + candidates = [x for x in processed_exits if "Transition - " not in x and "Pipe" in x] + else: + candidates = [x for x in processed_exits if "Transition - " not in x and "Star Road" in x or x != LocationName.yoshis_house_tile] + else: + candidates = [x for x in processed_exits if "Transition - " not in x and x not in prefilled_exits.values()] + if len(candidates) == 0: + swap_count = 10 + continue + swap_candidate = world.random.choice(candidates) + next_exits.append(swap_candidate) + processed_exits.remove(swap_candidate) + #print ("QUEUE: ", next_exits) + + if len(processed_exits) >= 18: + swap_count += 1 + + # Removes the previous processed entrances from the cache + for entrance in local_region_mapping[swap_candidate]: + if entrance in local_mapping: + value = local_mapping.pop(entrance) + next_exits.append(value) + if value in processed_exits: + processed_exits.remove(value) + if value in used_exits: + used_exits.remove(value) + + for exit in next_exits: + #print ("------------ EXIT ----------------") + #print (f"{exit}") + next_exits.remove(exit) + if exit in processed_exits: + continue + entrances = local_region_mapping[exit] + for entrance in entrances: + if entrance in local_mapping.keys(): + selected_exit = local_mapping[entrance] + #print (f"ENTRANCE IS FULFILLED: {entrance} -> {local_mapping[entrance]}") + else: + banned_exits = used_exits.copy() + banned_exits.append(exit) + banned_exits.extend(local_excluded_destinations[entrance]) + if "Transition - " in entrance: + possible_exits = [x for x in smw_transition_pairs.values() if x not in banned_exits] + else: + if world.options.map_teleport_shuffle == "on_both_same_type": + if "Pipe" in entrance: + possible_exits = [x for x in smw_pipe_pairs.values() if x not in banned_exits] + else: + possible_exits = [x for x in smw_star_pairs.values() if x not in banned_exits] + else: + possible_exits = [x for x in teleport_pairs.values() if x not in banned_exits] + #print (exit, " -> ", entrance, " -> ", possible_exits) + if len(possible_exits) == 0: + continue + selected_exit = world.random.choice(possible_exits) + #print ("SELECTED: ", entrance, " -> ", selected_exit) + used_exits.append(selected_exit) + local_mapping[entrance] = selected_exit + next_exits.append(selected_exit) + + processed_exits.append(exit) + + #print (len(processed_exits), len(local_region_mapping.keys())) + + # Reachabilty check + if len(processed_exits) == len(local_region_mapping.keys()): + #print (" ########################################################## REACHABILTY CHECK ##########################################################") + remaining_exits = list(local_region_mapping.keys()) + check_next_exits = [LocationName.yoshis_house_tile] + + while len(remaining_exits) != 0: + cache_exits = remaining_exits.copy() + for exit in check_next_exits: + if exit not in remaining_exits: + continue + remaining_exits.remove(exit) + check_next_exits.remove(exit) + for entrance in local_region_mapping[exit]: + if entrance not in local_mapping.keys(): + continue + check_next_exits.append(local_mapping[entrance]) + #print (exit, " -> ", entrance, " -> ", local_mapping[entrance]) + #print ("PENDING: ", check_next_exits, "\n") + + if len(cache_exits) == len(remaining_exits) and len(cache_exits) != 0: + # EMERGENCY SWAP + # Marks isolated exits as unreachable + #print ("------------ EMERGENCY ----------------") + #print ("PROCESSED_EXITS: ", processed_exits) + #print ("USED_EXITS: ", used_exits) + #print ("REMAINING_EXITS: ", remaining_exits) + processed_exits = [x for x in processed_exits if x not in remaining_exits] + used_exits = [x for x in used_exits if x not in remaining_exits] + emergency_list = [x for x in processed_exits if x not in prefilled_exits.values()] + if len(emergency_list) == 0: + local_mapping = {} + for entrance, exit in prefilled_exits.items(): + local_mapping[entrance] = exit + next_exits = [LocationName.yoshis_house_tile] + processed_exits = [] + used_exits = list(prefilled_exits.values()) + #print ("PROCESSED_EXITS: ", processed_exits) + #print ("USED_EXITS: ", used_exits) + #print ("LOCAL_MAPPING: ", local_mapping) + #print (" ########################################################## RESTARTING SHUFFLE ##########################################################") + break + + emergency_swap = world.random.choice(emergency_list) + #print ("EMERGENCY_SWAP: ", emergency_swap) + processed_exits.remove(emergency_swap) + if emergency_swap in used_exits: + used_exits.remove(emergency_swap) + #print ("PROCESSED_EXITS: ", processed_exits) + #print ("USED_EXITS: ", used_exits) + #print ("LOCAL_MAPPING: ", local_mapping) + for exit, entrances in local_region_mapping.items(): + if exit not in processed_exits: + for entrance in entrances: + if entrance not in local_mapping.keys(): + continue + value = local_mapping.pop(entrance) + next_exits.append(value) + #print ("LOCAL_MAPPING: ", local_mapping) + #print (" ########################################################## RESTART REQUIRED ##########################################################") + break + + #print (" ########################################################## REACHABILTY END ##########################################################") + + for entrance, exit in local_mapping.items(): + #print (entrance, " -> " ,exit) + if "Transition - " in entrance: + world.transition_pairs[entrance] = exit + else: + world.teleport_pairs[entrance] = exit + + world.reverse_teleport_pairs = {y: x for x, y in world.teleport_pairs.items()} + world.reverse_transition_pairs = {y: x for x, y in world.transition_pairs.items()} + #print ("TELEPORTS: ", world.teleport_pairs) + #print ("TRANSITIONS: ", world.transition_pairs) + + +def handle_silent_events(patch: "SMWProcedurePatch", world: "SMWWorld"): + for connection, event_id in world.cached_connections.items(): + if connection not in smw_offscreen_event_indexes.keys(): + continue + offset = 0x8888D + smw_offscreen_event_indexes[connection][1] + size = smw_offscreen_event_indexes[connection][2] + #print (f"{offset:06X} ({smw_offscreen_event_indexes[connection][1]:02X}) | {event_id:02X} x {size} | {connection}") + patch.write_bytes(offset, bytearray([event_id for _ in range(size)])) + + +def handle_teleport_shuffle(patch: "SMWProcedurePatch", world: "SMWWorld"): + tokens = bytearray([0x00 for _ in range(0x6C)]) + + for new_entrance, new_exit in world.teleport_pairs.items(): + old_entrance = tp_keys[tp_values.index(new_exit)] + old_exit = teleport_pairs[new_entrance] + + offset = smw_teleport_data[old_entrance][0] + tokens[offset:offset+2] = bytearray(smw_teleport_data[new_entrance][1:3]) + offset = 0x36 + smw_teleport_data[old_entrance][0] + tokens[offset:offset+2] = bytearray(smw_teleport_data[new_entrance][3:5]) + offset = smw_teleport_data[old_exit][0] + tokens[offset:offset+2] = bytearray(smw_teleport_data[new_exit][1:3]) + offset = 0x36 + smw_teleport_data[old_exit][0] + tokens[offset:offset+2] = bytearray(smw_teleport_data[new_exit][3:5]) + + if new_exit in smw_offscreen_event_indexes.keys(): + offset = 0x8888D + smw_offscreen_event_indexes[new_exit][1] + size = smw_offscreen_event_indexes[new_exit][2] + patch.write_bytes(offset, bytearray([world.cached_connections[new_entrance] for _ in range(size)])) + + if world.options.map_teleport_shuffle == "on_both_mix" or world.options.map_teleport_shuffle == "on_only_stars": + patch.write_bytes(0x8888D + 0x27, bytearray([0xFF for _ in range(0x05)])) + + patch.write_bytes(0x2049D, tokens) + + +def handle_transition_shuffle(patch: "SMWProcedurePatch", world: "SMWWorld"): + tokens = bytearray([0x00 for _ in range(0x46)]) + extra_tokens = bytearray([0x00 for _ in range(0x1C)]) + + for new_entrance, new_exit in world.transition_pairs.items(): + old_entrance = tr_keys[tr_values.index(new_exit)] + old_exit = smw_transition_pairs[new_entrance] + + offset = smw_transition_data[old_entrance][0] * 0x05 + tokens[offset:offset+5] = bytearray(smw_transition_data[new_entrance][1:6]) + offset = smw_transition_data[old_entrance][0] * 0x02 + extra_tokens[offset:offset+2] = bytearray(smw_transition_data[new_entrance][6:8]) + offset = smw_transition_data[old_entrance][0] + patch.write_byte(0x889D1 + offset, smw_transition_data[new_entrance][8]) + + offset = smw_transition_data[old_exit][0] * 0x05 + tokens[offset:offset+5] = bytearray(smw_transition_data[new_exit][1:6]) + offset = smw_transition_data[old_exit][0] * 0x02 + extra_tokens[offset:offset+2] = bytearray(smw_transition_data[new_exit][6:8]) + offset = smw_transition_data[old_exit][0] + patch.write_byte(0x889D1 + offset, smw_transition_data[new_exit][8]) + + if new_exit in smw_offscreen_event_indexes.keys(): + #print (new_exit) + offset = 0x8888D + smw_offscreen_event_indexes[new_exit][1] + size = smw_offscreen_event_indexes[new_exit][2] + patch.write_bytes(offset, bytearray([world.cached_connections[new_entrance] for _ in range(size)])) + + #print (world.options.map_transition_shuffle, world.options.map_teleport_shuffle) + patch.write_bytes(0x219AA, tokens) + patch.write_bytes(0x219F0, extra_tokens) + #patch.write_byte(0x2273, 0x00) \ No newline at end of file diff --git a/worlds/smw/__init__.py b/worlds/smw/__init__.py index 35778112ee61..7bfdb5245f26 100644 --- a/worlds/smw/__init__.py +++ b/worlds/smw/__init__.py @@ -21,6 +21,7 @@ from .Regions import create_regions, connect_regions from .Rom import patch_rom, SMWProcedurePatch, USHASH from .Rules import set_rules +from .Teleports import generate_entrance_rando class SMWSettings(settings.Group): @@ -95,6 +96,8 @@ def fill_slot_data(self) -> dict: "energy_link", ) slot_data["active_levels"] = self.active_level_dict + slot_data["teleport_pairs"] = self.teleport_pairs + slot_data["transition_pairs"] = self.transition_pairs return slot_data @@ -102,6 +105,16 @@ def generate_early(self): if self.options.early_climb: self.multiworld.local_early_items[self.player][ItemName.mario_climb] = 1 + self.teleport_data = dict() + self.teleport_pairs = dict() + self.reverse_teleport_pairs = dict() + self.cached_connections = dict() + self.transition_pairs = dict() + self.reverse_transition_pairs = dict() + self.transition_data = dict() + + generate_entrance_rando(self) + self.active_level_dict = dict(zip(generate_level_list(self), full_level_list)) if hasattr(self.multiworld, "generation_is_fake"): @@ -110,13 +123,19 @@ def generate_early(self): slot_data = self.multiworld.re_gen_passthrough["Super Mario World"] for x,y in slot_data["active_levels"].items(): self.active_level_dict[int(x)] = y + self.teleport_pairs = slot_data["teleport_pairs"] + self.transition_pairs = slot_data["transition_pairs"] def interpret_slot_data(self, slot_data): local_active_levels = dict() for x, y in slot_data["active_levels"].items(): local_active_levels[x] = y - return {"active_levels": local_active_levels} + return { + "active_levels": local_active_levels, + "teleport_pairs": slot_data["teleport_pairs"], + "transition_pairs": slot_data["transition_pairs"], + } def create_regions(self): @@ -131,11 +150,14 @@ def create_regions(self): connect_regions(self, self.active_level_dict) # Add Boss Token amount requirements for Worlds - add_rule(self.multiworld.get_region(LocationName.donut_plains_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1)) - add_rule(self.multiworld.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2)) - add_rule(self.multiworld.get_region(LocationName.forest_of_illusion_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 4)) - 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)) + add_rule(self.multiworld.get_region(LocationName.yi_to_dp, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 1)) + #add_rule(self.multiworld.get_region(LocationName.vanilla_dome_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 2)) + #add_rule(self.multiworld.get_region(LocationName.forest_of_illusion_1_tile, self.player).entrances[0], lambda state: state.has(ItemName.koopaling, self.player, 4)) + #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)) + + #from Utils import visualize_regions + #visualize_regions(self.multiworld.get_region("Menu", self.player), f"./plants/world_{self.player}.puml") exclusion_pool = set() if self.options.exclude_special_zone: @@ -335,6 +357,22 @@ def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, s hint_data[self.player] = er_hint_data + + def write_spoiler(self, spoiler_handle: typing.TextIO) -> None: + print (self.options.map_teleport_shuffle) + if self.options.map_teleport_shuffle.value != 0: + spoiler_handle.write(f"\nSuper Mario World map teleport shuffle destinations for {self.multiworld.player_name[self.player]}:\n") + + for entrance, exit in self.teleport_pairs.items(): + spoiler_handle.write(f" {entrance} -> {exit}\n") + + if self.options.map_transition_shuffle.value != 0: + spoiler_handle.write(f"\nSuper Mario World map transition shuffle destinations for {self.multiworld.player_name[self.player]}:\n") + + for entrance, exit in self.transition_pairs.items(): + spoiler_handle.write(f" {entrance[13:]} -> {exit[13:]}\n") + + def create_item(self, name: str, force_non_progression=False) -> Item: data = item_table[name] diff --git a/worlds/smw/data/smw_basepatch.bsdiff4 b/worlds/smw/data/smw_basepatch.bsdiff4 index 65e664d32c09..bfcc828f697e 100644 Binary files a/worlds/smw/data/smw_basepatch.bsdiff4 and b/worlds/smw/data/smw_basepatch.bsdiff4 differ diff --git a/worlds/smw/data/smw_sa1_basepatch.bsdiff4 b/worlds/smw/data/smw_sa1_basepatch.bsdiff4 index f33394cf7e24..699e5a1cd2ba 100644 Binary files a/worlds/smw/data/smw_sa1_basepatch.bsdiff4 and b/worlds/smw/data/smw_sa1_basepatch.bsdiff4 differ diff --git a/worlds/smw/data/sprite_graphics.bsdiff4 b/worlds/smw/data/sprite_graphics.bsdiff4 index 6ee08a0e11e4..04a8b2b067a3 100644 Binary files a/worlds/smw/data/sprite_graphics.bsdiff4 and b/worlds/smw/data/sprite_graphics.bsdiff4 differ