450 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			450 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import base64
							 | 
						||
| 
								 | 
							
								import Utils
							 | 
						||
| 
								 | 
							
								import settings
							 | 
						||
| 
								 | 
							
								from copy import deepcopy
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from worlds.AutoWorld import World, WebWorld
							 | 
						||
| 
								 | 
							
								from BaseClasses import Region, Location, Item, ItemClassification, Tutorial
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from . import client
							 | 
						||
| 
								 | 
							
								from .rom import generate_output, SuperMarioLand2ProcedurePatch
							 | 
						||
| 
								 | 
							
								from .options import SML2Options
							 | 
						||
| 
								 | 
							
								from .locations import (locations, location_name_to_id, level_name_to_id, level_id_to_name, START_IDS, coins_coords,
							 | 
						||
| 
								 | 
							
								                        auto_scroll_max)
							 | 
						||
| 
								 | 
							
								from .items import items
							 | 
						||
| 
								 | 
							
								from .sprites import level_sprites
							 | 
						||
| 
								 | 
							
								from .sprite_randomizer import randomize_enemies, randomize_platforms
							 | 
						||
| 
								 | 
							
								from .logic import has_pipe_up, has_pipe_down, has_pipe_left, has_pipe_right, has_level_progression, is_auto_scroll
							 | 
						||
| 
								 | 
							
								from . import logic
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MarioLand2Settings(settings.Group):
							 | 
						||
| 
								 | 
							
								    class SML2RomFile(settings.UserFilePath):
							 | 
						||
| 
								 | 
							
								        """File name of the Super Mario Land 2 1.0 ROM"""
							 | 
						||
| 
								 | 
							
								        description = "Super Mario Land 2 - 6 Golden Coins (USA, Europe) 1.0 ROM File"
							 | 
						||
| 
								 | 
							
								        copy_to = "Super Mario Land 2 - 6 Golden Coins (USA, Europe).gb"
							 | 
						||
| 
								 | 
							
								        md5s = [SuperMarioLand2ProcedurePatch.hash]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    rom_file: SML2RomFile = SML2RomFile(SML2RomFile.copy_to)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MarioLand2WebWorld(WebWorld):
							 | 
						||
| 
								 | 
							
								    setup_en = Tutorial(
							 | 
						||
| 
								 | 
							
								        "Multiworld Setup Guide",
							 | 
						||
| 
								 | 
							
								        "A guide to playing Super Mario Land 2 with Archipelago.",
							 | 
						||
| 
								 | 
							
								        "English",
							 | 
						||
| 
								 | 
							
								        "setup_en.md",
							 | 
						||
| 
								 | 
							
								        "setup/en",
							 | 
						||
| 
								 | 
							
								        ["Alchav"]
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    tutorials = [setup_en]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MarioLand2World(World):
							 | 
						||
| 
								 | 
							
								    """Super Mario Land 2 is a classic platformer that follows Mario on a quest to reclaim his castle from the
							 | 
						||
| 
								 | 
							
								    villainous Wario. This iconic game features 32 levels, unique power-ups, and introduces Wario as Mario's
							 | 
						||
| 
								 | 
							
								    arch-rival."""  # -ChatGPT
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    game = "Super Mario Land 2"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    settings_key = "sml2_options"
							 | 
						||
| 
								 | 
							
								    settings: MarioLand2Settings
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    location_name_to_id = location_name_to_id
							 | 
						||
| 
								 | 
							
								    item_name_to_id = {item_name: ID for ID, item_name in enumerate(items, START_IDS)}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    web = MarioLand2WebWorld()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    item_name_groups = {
							 | 
						||
| 
								 | 
							
								        "Level Progression": {
							 | 
						||
| 
								 | 
							
								            item_name for item_name in items if item_name.endswith(("Progression", "Secret", "Secret 1", "Secret 2"))
							 | 
						||
| 
								 | 
							
								                                                and "Auto Scroll" not in item_name
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        "Bells": {item_name for item_name in items if "Bell" in item_name},
							 | 
						||
| 
								 | 
							
								        "Golden Coins": {"Mario Coin", "Macro Coin", "Space Coin", "Tree Coin", "Turtle Coin", "Pumpkin Coin"},
							 | 
						||
| 
								 | 
							
								        "Coins": {"1 Coin", *{f"{i} Coins" for i in range(2, 169)}},
							 | 
						||
| 
								 | 
							
								        "Powerups": {"Mushroom", "Fire Flower", "Carrot"},
							 | 
						||
| 
								 | 
							
								        "Difficulties": {"Easy Mode", "Normal Mode"},
							 | 
						||
| 
								 | 
							
								        "Auto Scroll Traps": {item_name for item_name in items
							 | 
						||
| 
								 | 
							
								                              if "Auto Scroll" in item_name and "Cancel" not in item_name},
							 | 
						||
| 
								 | 
							
								        "Cancel Auto Scrolls": {item_name for item_name in items if "Cancel Auto Scroll" in item_name},
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    location_name_groups = {
							 | 
						||
| 
								 | 
							
								        "Bosses": {
							 | 
						||
| 
								 | 
							
								            "Tree Zone 5 - Boss", "Space Zone 2 - Boss", "Macro Zone 4 - Boss",
							 | 
						||
| 
								 | 
							
								            "Pumpkin Zone 4 - Boss", "Mario Zone 4 - Boss", "Turtle Zone 3 - Boss"
							 | 
						||
| 
								 | 
							
								                   },
							 | 
						||
| 
								 | 
							
								        "Normal Exits": {location for location in locations if locations[location]["type"] == "level"},
							 | 
						||
| 
								 | 
							
								        "Secret Exits": {location for location in locations if locations[location]["type"] == "secret"},
							 | 
						||
| 
								 | 
							
								        "Bells": {location for location in locations if locations[location]["type"] == "bell"},
							 | 
						||
| 
								 | 
							
								        "Coins": {location for location in location_name_to_id if "Coin" in location}
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    options_dataclass = SML2Options
							 | 
						||
| 
								 | 
							
								    options: SML2Options
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    generate_output = generate_output
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, world, player: int):
							 | 
						||
| 
								 | 
							
								        super().__init__(world, player)
							 | 
						||
| 
								 | 
							
								        self.auto_scroll_levels = []
							 | 
						||
| 
								 | 
							
								        self.num_coin_locations = []
							 | 
						||
| 
								 | 
							
								        self.max_coin_locations = {}
							 | 
						||
| 
								 | 
							
								        self.sprite_data = {}
							 | 
						||
| 
								 | 
							
								        self.coin_fragments_required = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def generate_early(self):
							 | 
						||
| 
								 | 
							
								        self.sprite_data = deepcopy(level_sprites)
							 | 
						||
| 
								 | 
							
								        if self.options.randomize_enemies:
							 | 
						||
| 
								 | 
							
								            randomize_enemies(self.sprite_data, self.random)
							 | 
						||
| 
								 | 
							
								        if self.options.randomize_platforms:
							 | 
						||
| 
								 | 
							
								            randomize_platforms(self.sprite_data, self.random)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.marios_castle_midway_bell:
							 | 
						||
| 
								 | 
							
								            self.sprite_data["Mario's Castle"][35]["sprite"] = "Midway Bell"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.auto_scroll_chances == "vanilla":
							 | 
						||
| 
								 | 
							
								            self.auto_scroll_levels = [int(i in [19, 25, 30]) for i in range(32)]
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            self.auto_scroll_levels = [int(self.random.randint(1, 100) <= self.options.auto_scroll_chances)
							 | 
						||
| 
								 | 
							
								                                       for _ in range(32)]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.auto_scroll_levels[level_name_to_id["Mario's Castle"]] = 0
							 | 
						||
| 
								 | 
							
								        unbeatable_scroll_levels = ["Tree Zone 3", "Macro Zone 2", "Space Zone 1", "Turtle Zone 2", "Pumpkin Zone 2"]
							 | 
						||
| 
								 | 
							
								        if not self.options.shuffle_midway_bells:
							 | 
						||
| 
								 | 
							
								            unbeatable_scroll_levels.append("Pumpkin Zone 1")
							 | 
						||
| 
								 | 
							
								        for level, i in enumerate(self.auto_scroll_levels):
							 | 
						||
| 
								 | 
							
								            if i == 1:
							 | 
						||
| 
								 | 
							
								                if self.options.auto_scroll_mode in ("global_cancel_item", "level_cancel_items"):
							 | 
						||
| 
								 | 
							
								                    self.auto_scroll_levels[level] = 2
							 | 
						||
| 
								 | 
							
								                elif self.options.auto_scroll_mode == "chaos":
							 | 
						||
| 
								 | 
							
								                    if (self.options.accessibility == "full"
							 | 
						||
| 
								 | 
							
								                            and level_id_to_name[level] in unbeatable_scroll_levels):
							 | 
						||
| 
								 | 
							
								                        self.auto_scroll_levels[level] = 2
							 | 
						||
| 
								 | 
							
								                    else:
							 | 
						||
| 
								 | 
							
								                        self.auto_scroll_levels[level] = self.random.randint(1, 3)
							 | 
						||
| 
								 | 
							
								                elif (self.options.accessibility == "full"
							 | 
						||
| 
								 | 
							
								                      and level_id_to_name[level] in unbeatable_scroll_levels):
							 | 
						||
| 
								 | 
							
								                    self.auto_scroll_levels[level] = 0
							 | 
						||
| 
								 | 
							
								                if self.auto_scroll_levels[level] == 1 and "trap" in self.options.auto_scroll_mode.current_key:
							 | 
						||
| 
								 | 
							
								                    self.auto_scroll_levels[level] = 3
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def create_regions(self):
							 | 
						||
| 
								 | 
							
								        menu_region = Region("Menu", self.player, self.multiworld)
							 | 
						||
| 
								 | 
							
								        self.multiworld.regions.append(menu_region)
							 | 
						||
| 
								 | 
							
								        created_regions = []
							 | 
						||
| 
								 | 
							
								        for location_name, data in locations.items():
							 | 
						||
| 
								 | 
							
								            region_name = location_name.split(" -")[0]
							 | 
						||
| 
								 | 
							
								            if region_name in created_regions:
							 | 
						||
| 
								 | 
							
								                region = self.multiworld.get_region(region_name, self.player)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                region = Region(region_name, self.player, self.multiworld)
							 | 
						||
| 
								 | 
							
								                if region_name == "Tree Zone Secret Course":
							 | 
						||
| 
								 | 
							
								                    region_to_connect = self.multiworld.get_region("Tree Zone 2", self.player)
							 | 
						||
| 
								 | 
							
								                elif region_name == "Space Zone Secret Course":
							 | 
						||
| 
								 | 
							
								                    region_to_connect = self.multiworld.get_region("Space Zone 1", self.player)
							 | 
						||
| 
								 | 
							
								                elif region_name == "Macro Zone Secret Course":
							 | 
						||
| 
								 | 
							
								                    region_to_connect = self.multiworld.get_region("Macro Zone 1", self.player)
							 | 
						||
| 
								 | 
							
								                elif region_name == "Pumpkin Zone Secret Course 1":
							 | 
						||
| 
								 | 
							
								                    region_to_connect = self.multiworld.get_region("Pumpkin Zone 2", self.player)
							 | 
						||
| 
								 | 
							
								                elif region_name == "Pumpkin Zone Secret Course 2":
							 | 
						||
| 
								 | 
							
								                    region_to_connect = self.multiworld.get_region("Pumpkin Zone 3", self.player)
							 | 
						||
| 
								 | 
							
								                elif region_name == "Turtle Zone Secret Course":
							 | 
						||
| 
								 | 
							
								                    region_to_connect = self.multiworld.get_region("Turtle Zone 2", self.player)
							 | 
						||
| 
								 | 
							
								                elif region_name.split(" ")[-1].isdigit() and int(region_name.split(" ")[-1]) > 1:
							 | 
						||
| 
								 | 
							
								                    region_to_connect = self.multiworld.get_region(" ".join(region_name.split(" ")[:2])
							 | 
						||
| 
								 | 
							
								                                                                   + f" {int(region_name.split(' ')[2]) - 1}",
							 | 
						||
| 
								 | 
							
								                                                                   self.player)
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    region_to_connect = menu_region
							 | 
						||
| 
								 | 
							
								                region_to_connect.connect(region)
							 | 
						||
| 
								 | 
							
								                self.multiworld.regions.append(region)
							 | 
						||
| 
								 | 
							
								                created_regions.append(region_name)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if location_name == "Mario's Castle - Midway Bell" and not self.options.marios_castle_midway_bell:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								            region.locations.append(MarioLand2Location(self.player, location_name,
							 | 
						||
| 
								 | 
							
								                                                       self.location_name_to_id[location_name], region))
							 | 
						||
| 
								 | 
							
								        self.multiworld.get_region("Macro Zone Secret Course", self.player).connect(
							 | 
						||
| 
								 | 
							
								            self.multiworld.get_region("Macro Zone 4", self.player))
							 | 
						||
| 
								 | 
							
								        self.multiworld.get_region("Macro Zone 4", self.player).connect(
							 | 
						||
| 
								 | 
							
								            self.multiworld.get_region("Macro Zone Secret Course", self.player))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        castle = self.multiworld.get_region("Mario's Castle", self.player)
							 | 
						||
| 
								 | 
							
								        wario = MarioLand2Location(self.player, "Mario's Castle - Wario", parent=castle)
							 | 
						||
| 
								 | 
							
								        castle.locations.append(wario)
							 | 
						||
| 
								 | 
							
								        wario.place_locked_item(MarioLand2Item("Wario Defeated", ItemClassification.progression, None, self.player))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.coinsanity:
							 | 
						||
| 
								 | 
							
								            coinsanity_checks = self.options.coinsanity_checks.value
							 | 
						||
| 
								 | 
							
								            self.num_coin_locations = [[region, 1] for region in created_regions if region != "Mario's Castle"]
							 | 
						||
| 
								 | 
							
								            self.max_coin_locations = {region: len(coins_coords[region]) for region in created_regions
							 | 
						||
| 
								 | 
							
								                                       if region != "Mario's Castle"}
							 | 
						||
| 
								 | 
							
								            if self.options.accessibility == "full" or self.options.auto_scroll_mode == "always":
							 | 
						||
| 
								 | 
							
								                for level in self.max_coin_locations:
							 | 
						||
| 
								 | 
							
								                    if level in auto_scroll_max and self.auto_scroll_levels[level_name_to_id[level]] in (1, 3):
							 | 
						||
| 
								 | 
							
								                        if isinstance(auto_scroll_max[level], tuple):
							 | 
						||
| 
								 | 
							
								                            self.max_coin_locations[level] = min(
							 | 
						||
| 
								 | 
							
								                                auto_scroll_max[level][int(self.options.shuffle_midway_bells.value)],
							 | 
						||
| 
								 | 
							
								                                self.max_coin_locations[level])
							 | 
						||
| 
								 | 
							
								                        else:
							 | 
						||
| 
								 | 
							
								                            self.max_coin_locations[level] = min(auto_scroll_max[level], self.max_coin_locations[level])
							 | 
						||
| 
								 | 
							
								            coinsanity_checks = min(sum(self.max_coin_locations.values()), coinsanity_checks)
							 | 
						||
| 
								 | 
							
								            for i in range(coinsanity_checks - 31):
							 | 
						||
| 
								 | 
							
								                self.num_coin_locations.sort(key=lambda region: self.max_coin_locations[region[0]] / region[1])
							 | 
						||
| 
								 | 
							
								                self.num_coin_locations[-1][1] += 1
							 | 
						||
| 
								 | 
							
								            coin_locations = []
							 | 
						||
| 
								 | 
							
								            for level, coins in self.num_coin_locations:
							 | 
						||
| 
								 | 
							
								                if self.max_coin_locations[level]:
							 | 
						||
| 
								 | 
							
								                    coin_thresholds = self.random.sample(range(1, self.max_coin_locations[level] + 1), coins)
							 | 
						||
| 
								 | 
							
								                    coin_locations += [f"{level} - {i} Coin{'s' if i > 1 else ''}" for i in coin_thresholds]
							 | 
						||
| 
								 | 
							
								            for location_name in coin_locations:
							 | 
						||
| 
								 | 
							
								                region = self.multiworld.get_region(location_name.split(" -")[0], self.player)
							 | 
						||
| 
								 | 
							
								                region.locations.append(MarioLand2Location(self.player, location_name,
							 | 
						||
| 
								 | 
							
								                                                           self.location_name_to_id[location_name], parent=region))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def set_rules(self):
							 | 
						||
| 
								 | 
							
								        entrance_rules = {
							 | 
						||
| 
								 | 
							
								            "Menu -> Space Zone 1": lambda state: state.has("Hippo Bubble", self.player)
							 | 
						||
| 
								 | 
							
								                                                  or (state.has("Carrot", self.player)
							 | 
						||
| 
								 | 
							
								                                                      and not is_auto_scroll(state, self.player, "Hippo Zone")),
							 | 
						||
| 
								 | 
							
								            "Space Zone 1 -> Space Zone Secret Course": lambda state: state.has("Space Zone Secret", self.player),
							 | 
						||
| 
								 | 
							
								            "Space Zone 1 -> Space Zone 2": lambda state: has_level_progression(state, "Space Zone Progression", self.player),
							 | 
						||
| 
								 | 
							
								            "Tree Zone 1 -> Tree Zone 2": lambda state: has_level_progression(state, "Tree Zone Progression", self.player),
							 | 
						||
| 
								 | 
							
								            "Tree Zone 2 -> Tree Zone Secret Course": lambda state: state.has("Tree Zone Secret", self.player),
							 | 
						||
| 
								 | 
							
								            "Tree Zone 2 -> Tree Zone 3": lambda state: has_level_progression(state, "Tree Zone Progression", self.player, 2),
							 | 
						||
| 
								 | 
							
								            "Tree Zone 4 -> Tree Zone 5": lambda state: has_level_progression(state, "Tree Zone Progression", self.player, 3),
							 | 
						||
| 
								 | 
							
								            "Macro Zone 1 -> Macro Zone Secret Course": lambda state: state.has("Macro Zone Secret 1", self.player),
							 | 
						||
| 
								 | 
							
								            "Macro Zone Secret Course -> Macro Zone 4": lambda state: state.has("Macro Zone Secret 2", self.player),
							 | 
						||
| 
								 | 
							
								            "Macro Zone 1 -> Macro Zone 2": lambda state: has_level_progression(state, "Macro Zone Progression", self.player),
							 | 
						||
| 
								 | 
							
								            "Macro Zone 2 -> Macro Zone 3": lambda state: has_level_progression(state, "Macro Zone Progression", self.player, 2),
							 | 
						||
| 
								 | 
							
								            "Macro Zone 3 -> Macro Zone 4": lambda state: has_level_progression(state, "Macro Zone Progression", self.player, 3),
							 | 
						||
| 
								 | 
							
								            "Macro Zone 4 -> Macro Zone Secret Course": lambda state: state.has("Macro Zone Secret 2", self.player),
							 | 
						||
| 
								 | 
							
								            "Pumpkin Zone 1 -> Pumpkin Zone 2": lambda state: has_level_progression(state, "Pumpkin Zone Progression", self.player),
							 | 
						||
| 
								 | 
							
								            "Pumpkin Zone 2 -> Pumpkin Zone Secret Course 1": lambda state: state.has("Pumpkin Zone Secret 1", self.player),
							 | 
						||
| 
								 | 
							
								            "Pumpkin Zone 2 -> Pumpkin Zone 3": lambda state: has_level_progression(state, "Pumpkin Zone Progression", self.player, 2),
							 | 
						||
| 
								 | 
							
								            "Pumpkin Zone 3 -> Pumpkin Zone Secret Course 2": lambda state: state.has("Pumpkin Zone Secret 2", self.player),
							 | 
						||
| 
								 | 
							
								            "Pumpkin Zone 3 -> Pumpkin Zone 4": lambda state: has_level_progression(state, "Pumpkin Zone Progression", self.player, 3),
							 | 
						||
| 
								 | 
							
								            "Mario Zone 1 -> Mario Zone 2": lambda state: has_level_progression(state, "Mario Zone Progression", self.player),
							 | 
						||
| 
								 | 
							
								            "Mario Zone 2 -> Mario Zone 3": lambda state: has_level_progression(state, "Mario Zone Progression", self.player, 2),
							 | 
						||
| 
								 | 
							
								            "Mario Zone 3 -> Mario Zone 4": lambda state: has_level_progression(state, "Mario Zone Progression", self.player, 3),
							 | 
						||
| 
								 | 
							
								            "Turtle Zone 1 -> Turtle Zone 2": lambda state: has_level_progression(state, "Turtle Zone Progression", self.player),
							 | 
						||
| 
								 | 
							
								            "Turtle Zone 2 -> Turtle Zone Secret Course": lambda state: state.has("Turtle Zone Secret", self.player),
							 | 
						||
| 
								 | 
							
								            "Turtle Zone 2 -> Turtle Zone 3": lambda state: has_level_progression(state, "Turtle Zone Progression", self.player, 2),
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.shuffle_golden_coins == "mario_coin_fragment_hunt":
							 | 
						||
| 
								 | 
							
								            # Require the other coins just to ensure they are being added to start inventory properly,
							 | 
						||
| 
								 | 
							
								            # and so they show up in Playthrough as required
							 | 
						||
| 
								 | 
							
								            entrance_rules["Menu -> Mario's Castle"] = lambda state: (state.has_all(
							 | 
						||
| 
								 | 
							
								                ["Tree Coin", "Space Coin", "Macro Coin", "Pumpkin Coin", "Turtle Coin"], self.player)
							 | 
						||
| 
								 | 
							
								                and state.has("Mario Coin Fragment", self.player, self.coin_fragments_required))
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            entrance_rules["Menu -> Mario's Castle"] = lambda state: state.has_from_list_unique([
							 | 
						||
| 
								 | 
							
								                "Tree Coin", "Space Coin", "Macro Coin", "Pumpkin Coin", "Mario Coin", "Turtle Coin"
							 | 
						||
| 
								 | 
							
								                ], self.player, self.options.required_golden_coins)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for entrance, rule in entrance_rules.items():
							 | 
						||
| 
								 | 
							
								            self.multiworld.get_entrance(entrance, self.player).access_rule = rule
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for location in self.multiworld.get_locations(self.player):
							 | 
						||
| 
								 | 
							
								            if location.name.endswith(("Coins", "Coin")):
							 | 
						||
| 
								 | 
							
								                rule = getattr(logic, location.parent_region.name.lower().replace(" ", "_") + "_coins", None)
							 | 
						||
| 
								 | 
							
								                if rule:
							 | 
						||
| 
								 | 
							
								                    coins = int(location.name.split(" ")[-2])
							 | 
						||
| 
								 | 
							
								                    location.access_rule = lambda state, coin_rule=rule, num_coins=coins: \
							 | 
						||
| 
								 | 
							
								                        coin_rule(state, self.player, num_coins)
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                rule = getattr(logic, location.name.lower().replace(
							 | 
						||
| 
								 | 
							
								                    " - ", "_").replace(" ", "_").replace("'", ""), None)
							 | 
						||
| 
								 | 
							
								                if rule:
							 | 
						||
| 
								 | 
							
								                    location.access_rule = lambda state, loc_rule=rule: loc_rule(state, self.player)
							 | 
						||
| 
								 | 
							
								        self.multiworld.completion_condition[self.player] = lambda state: state.has("Wario Defeated", self.player)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def create_items(self):
							 | 
						||
| 
								 | 
							
								        item_counts = {
							 | 
						||
| 
								 | 
							
								            "Space Zone Progression": 1,
							 | 
						||
| 
								 | 
							
								            "Space Zone Secret": 1,
							 | 
						||
| 
								 | 
							
								            "Tree Zone Progression": 3,
							 | 
						||
| 
								 | 
							
								            "Tree Zone Secret": 1,
							 | 
						||
| 
								 | 
							
								            "Macro Zone Progression": 3,
							 | 
						||
| 
								 | 
							
								            "Macro Zone Secret 1": 1,
							 | 
						||
| 
								 | 
							
								            "Macro Zone Secret 2": 1,
							 | 
						||
| 
								 | 
							
								            "Pumpkin Zone Progression": 3,
							 | 
						||
| 
								 | 
							
								            "Pumpkin Zone Secret 1": 1,
							 | 
						||
| 
								 | 
							
								            "Pumpkin Zone Secret 2": 1,
							 | 
						||
| 
								 | 
							
								            "Mario Zone Progression": 3,
							 | 
						||
| 
								 | 
							
								            "Turtle Zone Progression": 2,
							 | 
						||
| 
								 | 
							
								            "Turtle Zone Secret": 1,
							 | 
						||
| 
								 | 
							
								            "Mushroom": 1,
							 | 
						||
| 
								 | 
							
								            "Fire Flower": 1,
							 | 
						||
| 
								 | 
							
								            "Carrot": 1,
							 | 
						||
| 
								 | 
							
								            "Space Physics": 1,
							 | 
						||
| 
								 | 
							
								            "Hippo Bubble": 1,
							 | 
						||
| 
								 | 
							
								            "Water Physics": 1,
							 | 
						||
| 
								 | 
							
								            "Super Star Duration Increase": 2,
							 | 
						||
| 
								 | 
							
								            "Mario Coin Fragment": 0,
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.shuffle_golden_coins == "mario_coin_fragment_hunt":
							 | 
						||
| 
								 | 
							
								            # There are 5 Zone Progression items that can be condensed.
							 | 
						||
| 
								 | 
							
								            item_counts["Mario Coin Fragment"] = 1 + ((5 * self.options.mario_coin_fragment_percentage) // 100)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.coinsanity:
							 | 
						||
| 
								 | 
							
								            coin_count = sum([level[1] for level in self.num_coin_locations])
							 | 
						||
| 
								 | 
							
								            max_coins = sum(self.max_coin_locations.values())
							 | 
						||
| 
								 | 
							
								            if self.options.shuffle_golden_coins == "mario_coin_fragment_hunt":
							 | 
						||
| 
								 | 
							
								                removed_coins = (coin_count * self.options.mario_coin_fragment_percentage) // 100
							 | 
						||
| 
								 | 
							
								                coin_count -= removed_coins
							 | 
						||
| 
								 | 
							
								                item_counts["Mario Coin Fragment"] += removed_coins
							 | 
						||
| 
								 | 
							
								                # Randomly remove some coin items for variety
							 | 
						||
| 
								 | 
							
								                coin_count -= (coin_count // self.random.randint(100, max(100, coin_count)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if coin_count:
							 | 
						||
| 
								 | 
							
								                coin_bundle_sizes = [max_coins // coin_count] * coin_count
							 | 
						||
| 
								 | 
							
								                remainder = max_coins - sum(coin_bundle_sizes)
							 | 
						||
| 
								 | 
							
								                for i in range(remainder):
							 | 
						||
| 
								 | 
							
								                    coin_bundle_sizes[i] += 1
							 | 
						||
| 
								 | 
							
								                for a, b in zip(range(1, len(coin_bundle_sizes), 2), range(2, len(coin_bundle_sizes), 2)):
							 | 
						||
| 
								 | 
							
								                    split = self.random.randint(1, coin_bundle_sizes[a] + coin_bundle_sizes[b] - 1)
							 | 
						||
| 
								 | 
							
								                    coin_bundle_sizes[a], coin_bundle_sizes[b] = split, coin_bundle_sizes[a] + coin_bundle_sizes[b] - split
							 | 
						||
| 
								 | 
							
								                for coin_bundle_size in coin_bundle_sizes:
							 | 
						||
| 
								 | 
							
								                    item_name = f"{coin_bundle_size} Coin{'s' if coin_bundle_size > 1 else ''}"
							 | 
						||
| 
								 | 
							
								                    if item_name in item_counts:
							 | 
						||
| 
								 | 
							
								                        item_counts[item_name] += 1
							 | 
						||
| 
								 | 
							
								                    else:
							 | 
						||
| 
								 | 
							
								                        item_counts[item_name] = 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.shuffle_golden_coins == "shuffle":
							 | 
						||
| 
								 | 
							
								            for item in self.item_name_groups["Golden Coins"]:
							 | 
						||
| 
								 | 
							
								                item_counts[item] = 1
							 | 
						||
| 
								 | 
							
								        elif self.options.shuffle_golden_coins == "mario_coin_fragment_hunt":
							 | 
						||
| 
								 | 
							
								            for item in ("Tree Coin", "Space Coin", "Macro Coin", "Pumpkin Coin", "Turtle Coin"):
							 | 
						||
| 
								 | 
							
								                self.multiworld.push_precollected(self.create_item(item))
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            for item, location_name in (
							 | 
						||
| 
								 | 
							
								                    ("Mario Coin", "Mario Zone 4 - Boss"),
							 | 
						||
| 
								 | 
							
								                    ("Tree Coin", "Tree Zone 5 - Boss"),
							 | 
						||
| 
								 | 
							
								                    ("Space Coin", "Space Zone 2 - Boss"),
							 | 
						||
| 
								 | 
							
								                    ("Macro Coin", "Macro Zone 4 - Boss"),
							 | 
						||
| 
								 | 
							
								                    ("Pumpkin Coin", "Pumpkin Zone 4 - Boss"),
							 | 
						||
| 
								 | 
							
								                    ("Turtle Coin", "Turtle Zone 3 - Boss")
							 | 
						||
| 
								 | 
							
								            ):
							 | 
						||
| 
								 | 
							
								                location = self.multiworld.get_location(location_name, self.player)
							 | 
						||
| 
								 | 
							
								                location.place_locked_item(self.create_item(item))
							 | 
						||
| 
								 | 
							
								                location.address = None
							 | 
						||
| 
								 | 
							
								                location.item.code = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.shuffle_midway_bells:
							 | 
						||
| 
								 | 
							
								            for item in [item for item in items if "Midway Bell" in item]:
							 | 
						||
| 
								 | 
							
								                if item != "Mario's Castle Midway Bell" or self.options.marios_castle_midway_bell:
							 | 
						||
| 
								 | 
							
								                    item_counts[item] = 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.difficulty_mode == "easy_to_normal":
							 | 
						||
| 
								 | 
							
								            item_counts["Normal Mode"] = 1
							 | 
						||
| 
								 | 
							
								        elif self.options.difficulty_mode == "normal_to_easy":
							 | 
						||
| 
								 | 
							
								            item_counts["Easy Mode"] = 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if self.options.shuffle_pipe_traversal == "single":
							 | 
						||
| 
								 | 
							
								            item_counts["Pipe Traversal"] = 1
							 | 
						||
| 
								 | 
							
								        elif self.options.shuffle_pipe_traversal == "split":
							 | 
						||
| 
								 | 
							
								            item_counts["Pipe Traversal - Right"] = 1
							 | 
						||
| 
								 | 
							
								            item_counts["Pipe Traversal - Left"] = 1
							 | 
						||
| 
								 | 
							
								            item_counts["Pipe Traversal - Up"] = 1
							 | 
						||
| 
								 | 
							
								            item_counts["Pipe Traversal - Down"] = 1
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            self.multiworld.push_precollected(self.create_item("Pipe Traversal"))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if any(self.auto_scroll_levels):
							 | 
						||
| 
								 | 
							
								            if self.options.auto_scroll_mode == "global_trap_item":
							 | 
						||
| 
								 | 
							
								                item_counts["Auto Scroll"] = 1
							 | 
						||
| 
								 | 
							
								            elif self.options.auto_scroll_mode == "global_cancel_item":
							 | 
						||
| 
								 | 
							
								                item_counts["Cancel Auto Scroll"] = 1
							 | 
						||
| 
								 | 
							
								            else:
							 | 
						||
| 
								 | 
							
								                for level, i in enumerate(self.auto_scroll_levels):
							 | 
						||
| 
								 | 
							
								                    if i == 3:
							 | 
						||
| 
								 | 
							
								                        item_counts[f"Auto Scroll - {level_id_to_name[level]}"] = 1
							 | 
						||
| 
								 | 
							
								                    elif i == 2:
							 | 
						||
| 
								 | 
							
								                        item_counts[f"Cancel Auto Scroll - {level_id_to_name[level]}"] = 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for item in self.multiworld.precollected_items[self.player]:
							 | 
						||
| 
								 | 
							
								            if item.name in item_counts and item_counts[item.name] > 0:
							 | 
						||
| 
								 | 
							
								                item_counts[item.name] -= 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        location_count = len(self.multiworld.get_unfilled_locations(self.player))
							 | 
						||
| 
								 | 
							
								        items_to_add = location_count - sum(item_counts.values())
							 | 
						||
| 
								 | 
							
								        if items_to_add > 0:
							 | 
						||
| 
								 | 
							
								            mario_coin_frags = 0
							 | 
						||
| 
								 | 
							
								            if self.options.shuffle_golden_coins == "mario_coin_fragment_hunt":
							 | 
						||
| 
								 | 
							
								                mario_coin_frags = (items_to_add * self.options.mario_coin_fragment_percentage) // 100
							 | 
						||
| 
								 | 
							
								                item_counts["Mario Coin Fragment"] += mario_coin_frags
							 | 
						||
| 
								 | 
							
								            item_counts["Super Star Duration Increase"] += items_to_add - mario_coin_frags
							 | 
						||
| 
								 | 
							
								        elif items_to_add < 0:
							 | 
						||
| 
								 | 
							
								            if self.options.coinsanity:
							 | 
						||
| 
								 | 
							
								                for i in range(1, 168):
							 | 
						||
| 
								 | 
							
								                    coin_name = f"{i} Coin{'s' if i > 1 else ''}"
							 | 
						||
| 
								 | 
							
								                    if coin_name in item_counts:
							 | 
						||
| 
								 | 
							
								                        amount_to_remove = min(-items_to_add, item_counts[coin_name])
							 | 
						||
| 
								 | 
							
								                        item_counts[coin_name] -= amount_to_remove
							 | 
						||
| 
								 | 
							
								                        items_to_add += amount_to_remove
							 | 
						||
| 
								 | 
							
								                        if items_to_add >= 0:
							 | 
						||
| 
								 | 
							
								                            break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            double_progression_items = ["Tree Zone Progression", "Macro Zone Progression", "Pumpkin Zone Progression",
							 | 
						||
| 
								 | 
							
								                                        "Mario Zone Progression", "Turtle Zone Progression"]
							 | 
						||
| 
								 | 
							
								            self.random.shuffle(double_progression_items)
							 | 
						||
| 
								 | 
							
								            while sum(item_counts.values()) > location_count:
							 | 
						||
| 
								 | 
							
								                if double_progression_items:
							 | 
						||
| 
								 | 
							
								                    double_progression_item = double_progression_items.pop()
							 | 
						||
| 
								 | 
							
								                    item_counts[double_progression_item] -= 2
							 | 
						||
| 
								 | 
							
								                    item_counts[double_progression_item + " x2"] = 1
							 | 
						||
| 
								 | 
							
								                    continue
							 | 
						||
| 
								 | 
							
								                if self.options.auto_scroll_mode in ("level_trap_items", "level_cancel_items",
							 | 
						||
| 
								 | 
							
								                                                     "chaos"):
							 | 
						||
| 
								 | 
							
								                    auto_scroll_item = self.random.choice([item for item in item_counts if "Auto Scroll" in item])
							 | 
						||
| 
								 | 
							
								                    level = auto_scroll_item.split("- ")[1]
							 | 
						||
| 
								 | 
							
								                    self.auto_scroll_levels[level_name_to_id[level]] = 0
							 | 
						||
| 
								 | 
							
								                    del item_counts[auto_scroll_item]
							 | 
						||
| 
								 | 
							
								                    continue
							 | 
						||
| 
								 | 
							
								                raise Exception(f"Too many items in the item pool for Super Mario Land 2 player {self.player_name}")
							 | 
						||
| 
								 | 
							
								                # item = self.random.choice(list(item_counts))
							 | 
						||
| 
								 | 
							
								                # item_counts[item] -= 1
							 | 
						||
| 
								 | 
							
								                # if item_counts[item] == 0:
							 | 
						||
| 
								 | 
							
								                #     del item_counts[item]
							 | 
						||
| 
								 | 
							
								                # self.multiworld.push_precollected(self.create_item(item))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        self.coin_fragments_required = max((item_counts["Mario Coin Fragment"]
							 | 
						||
| 
								 | 
							
								                                           * self.options.mario_coin_fragments_required_percentage) // 100, 1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for item_name, count in item_counts.items():
							 | 
						||
| 
								 | 
							
								            self.multiworld.itempool += [self.create_item(item_name) for _ in range(count)]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def fill_slot_data(self):
							 | 
						||
| 
								 | 
							
								        return {
							 | 
						||
| 
								 | 
							
								            "energy_link": self.options.energy_link.value
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def create_item(self, name: str) -> Item:
							 | 
						||
| 
								 | 
							
								        return MarioLand2Item(name, items[name], self.item_name_to_id[name], self.player)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def get_filler_item_name(self):
							 | 
						||
| 
								 | 
							
								        return "1 Coin"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def modify_multidata(self, multidata: dict):
							 | 
						||
| 
								 | 
							
								        rom_name = bytearray(f'AP{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0',
							 | 
						||
| 
								 | 
							
								                             'utf8')[:21]
							 | 
						||
| 
								 | 
							
								        rom_name.extend([0] * (21 - len(rom_name)))
							 | 
						||
| 
								 | 
							
								        new_name = base64.b64encode(bytes(rom_name)).decode()
							 | 
						||
| 
								 | 
							
								        multidata["connect_names"][new_name] = multidata["connect_names"][self.player_name]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MarioLand2Location(Location):
							 | 
						||
| 
								 | 
							
								    game = "Super Mario Land 2"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class MarioLand2Item(Item):
							 | 
						||
| 
								 | 
							
								    game = "Super Mario Land 2"
							 |