diff --git a/__init__.py b/__init__.py index 327677e..fa80708 100644 --- a/__init__.py +++ b/__init__.py @@ -12,7 +12,7 @@ from .items import item_data_table, major_item_data_table, mzm_item_name_groups, MZMItem from .locations import full_location_table, mzm_location_name_groups from .options import MZMOptions, MorphBallPlacement, mzm_option_groups -from .regions import create_regions +from .regions import create_regions_and_connections from .rom import MZMProcedurePatch, write_tokens from .rules import set_rules @@ -41,7 +41,7 @@ class MZMWeb(WebWorld): "English", "multiworld_en.md", "multiworld/en", - ["lil David, NoiseCrush"] + ["N/A"] ) tutorials = [setup] @@ -65,7 +65,7 @@ class MZMWorld(World): required_client_version = (0, 5, 0) item_name_to_id = {name: data.code for name, data in item_data_table.items()} - location_name_to_id = full_location_table + location_name_to_id = {name: data.code for name, data in full_location_table.items()} item_name_groups = mzm_item_name_groups location_name_groups = mzm_location_name_groups @@ -82,14 +82,14 @@ def generate_early(self): self.options.local_items.value.add("Nothing") def create_regions(self) -> None: - create_regions(self) + create_regions_and_connections(self) self.place_event("Kraid Defeated", "Kraid") self.place_event("Ridley Defeated", "Ridley") self.place_event("Mother Brain Defeated", "Mother Brain") self.place_event("Chozo Ghost Defeated", "Chozo Ghost") self.place_event("Mecha Ridley Defeated", "Mecha Ridley") - self.place_event("Mission Complete", "Chozodia Space Pirate's Ship") + self.place_event("Mission Accomplished!", "Chozodia Space Pirate's Ship") def create_items(self) -> None: item_pool: List[MZMItem] = [] @@ -109,7 +109,7 @@ def create_items(self) -> None: def set_rules(self) -> None: set_rules(self, full_location_table) self.multiworld.completion_condition[self.player] = lambda state: ( - state.has("Mission Complete", self.player)) + state.has("Mission Accomplished!", self.player)) def generate_output(self, output_directory: str): output_path = Path(output_directory) @@ -130,9 +130,11 @@ def fill_slot_data(self) -> Dict[str, Any]: "goal": self.options.goal.value, "unknown_items": self.options.unknown_items_always_usable.value, "layout_patches": self.options.layout_patches.value, + "logic_difficulty": self.options.logic_difficulty.value, "ibj_logic": self.options.ibj_in_logic.value, "heatruns": self.options.heatruns_lavadives.value, "walljump_logic": self.options.walljumps_in_logic.value, + "tricky_shinesparks": self.options.tricky_shinesparks.value, "death_link": self.options.death_link.value } diff --git a/client.py b/client.py index f605bc9..fa6930a 100644 --- a/client.py +++ b/client.py @@ -285,7 +285,7 @@ async def game_watcher(self, client_ctx: BizHawkClientContext) -> None: ): for location in location_table.values(): if location_flags & 1: - checked_locations.append(location) + checked_locations.append(location.code) location_flags >>= 1 # Deorem flags are in a weird arrangement, but he also drops Charge Beam so whatever just look for that check diff --git a/locations.py b/locations.py index 73d3ffd..f53130c 100644 --- a/locations.py +++ b/locations.py @@ -1,145 +1,150 @@ """ Classes and functions related to AP locations for Metroid: Zero Mission """ -from BaseClasses import Location + from .items import AP_MZM_ID_BASE -class MZMLocation(Location): - game: str = "Metroid Zero Mission" +class LocationData: + region: str + code: int + + def __init__(self, reg, id): + self.region = reg + self.code = id # Location numbers/order and some names from Biospark's MZM Randomizer. # Events in any region must be at the end of its table for the client to work correctly brinstar_location_table = { - "Brinstar Morph Ball": AP_MZM_ID_BASE + 0, - "Brinstar Morph Ball Cannon": AP_MZM_ID_BASE + 1, - "Brinstar Long Beam": AP_MZM_ID_BASE + 2, - "Brinstar Ceiling E-Tank": AP_MZM_ID_BASE + 3, - "Brinstar Missile Above Super": AP_MZM_ID_BASE + 4, - "Brinstar Super Missile": AP_MZM_ID_BASE + 5, - "Brinstar Top Missile": AP_MZM_ID_BASE + 6, - "Brinstar Speed Booster Shortcut Missile": AP_MZM_ID_BASE + 7, - "Brinstar Varia Suit": AP_MZM_ID_BASE + 8, - "Brinstar Worm drop": AP_MZM_ID_BASE + 9, - "Brinstar Acid near Varia": AP_MZM_ID_BASE + 10, - "Brinstar First Missile": AP_MZM_ID_BASE + 11, - "Brinstar Behind Hive": AP_MZM_ID_BASE + 12, - "Brinstar Under Bridge": AP_MZM_ID_BASE + 13, - "Brinstar Post-Hive Missile": AP_MZM_ID_BASE + 14, - "Brinstar Upper Pillar Missile": AP_MZM_ID_BASE + 15, - "Brinstar Behind Bombs": AP_MZM_ID_BASE + 16, - "Brinstar Bomb": AP_MZM_ID_BASE + 17, - "Brinstar Post-Hive E-Tank": AP_MZM_ID_BASE + 18, + "Brinstar Morph Ball": LocationData("Brinstar Start", AP_MZM_ID_BASE + 0), + "Brinstar Morph Ball Cannon": LocationData("Brinstar Start", AP_MZM_ID_BASE + 1), + "Brinstar Long Beam": LocationData("Brinstar Main", AP_MZM_ID_BASE + 2), + "Brinstar Ceiling E-Tank": LocationData("Brinstar Start", AP_MZM_ID_BASE + 3), + "Brinstar Main Shaft Left Alcove": LocationData("Brinstar Main", AP_MZM_ID_BASE + 4), + "Brinstar Ballspark": LocationData("Brinstar Main", AP_MZM_ID_BASE + 5), + "Brinstar Ripper Climb": LocationData("Brinstar Main", AP_MZM_ID_BASE + 6), + "Brinstar Speed Booster Shortcut": LocationData("Brinstar Main", AP_MZM_ID_BASE + 7), + "Brinstar Varia Suit": LocationData("Brinstar Top", AP_MZM_ID_BASE + 8), + "Brinstar Worm drop": LocationData("Brinstar Main", AP_MZM_ID_BASE + 9), + "Brinstar Acid near Varia": LocationData("Brinstar Top", AP_MZM_ID_BASE + 10), + "Brinstar First Missile": LocationData("Brinstar Main", AP_MZM_ID_BASE + 11), + "Brinstar Behind Hive": LocationData("Brinstar Main", AP_MZM_ID_BASE + 12), + "Brinstar Under Bridge": LocationData("Brinstar Main", AP_MZM_ID_BASE + 13), + "Brinstar Post-Hive In Wall": LocationData("Brinstar Past Hives", AP_MZM_ID_BASE + 14), + "Brinstar Upper Pillar": LocationData("Brinstar Top", AP_MZM_ID_BASE + 15), + "Brinstar Behind Bombs": LocationData("Brinstar Past Hives", AP_MZM_ID_BASE + 16), + "Brinstar Bomb": LocationData("Brinstar Past Hives", AP_MZM_ID_BASE + 17), + "Brinstar Post-Hive Pillar": LocationData("Brinstar Past Hives", AP_MZM_ID_BASE + 18) } kraid_location_table = { - "Kraid Giant Hoppers Missile": AP_MZM_ID_BASE + 19, - "Kraid Save Room Missile": AP_MZM_ID_BASE + 20, - "Kraid Crumble Block Missile": AP_MZM_ID_BASE + 21, - "Kraid Quad Ball Cannon Room": AP_MZM_ID_BASE + 22, - "Kraid Space Jump/Unknown Item 2": AP_MZM_ID_BASE + 23, - "Kraid Acid Ballspark": AP_MZM_ID_BASE + 24, - "Kraid Speed Booster": AP_MZM_ID_BASE + 25, - "Kraid Worm Missile": AP_MZM_ID_BASE + 26, - "Kraid Pillar Missile": AP_MZM_ID_BASE + 27, - "Kraid Acid Fall": AP_MZM_ID_BASE + 28, - "Kraid Worm E-Tank": AP_MZM_ID_BASE + 29, - "Kraid Speed Jump": AP_MZM_ID_BASE + 30, - "Kraid Upper Right Morph Ball Cannon": AP_MZM_ID_BASE + 31, - "Kraid": None + "Kraid Behind Giant Hoppers": LocationData("Kraid Left Shaft", AP_MZM_ID_BASE + 19), + "Kraid Save Room Tunnel": LocationData("Kraid Main", AP_MZM_ID_BASE + 20), + "Kraid Zipline Morph Jump": LocationData("Kraid Main", AP_MZM_ID_BASE + 21), + "Kraid Quad Ball Cannon Room": LocationData("Kraid Left Shaft", AP_MZM_ID_BASE + 22), + "Kraid Unknown Item Statue": LocationData("Kraid Left Shaft", AP_MZM_ID_BASE + 23), + "Kraid Acid Ballspark": LocationData("Kraid Main", AP_MZM_ID_BASE + 24), + "Kraid Speed Booster": LocationData("Kraid Bottom", AP_MZM_ID_BASE + 25), + "Kraid Under Acid Worm": LocationData("Kraid Acid Worm Area", AP_MZM_ID_BASE + 26), + "Kraid Right Hall Pillar": LocationData("Kraid Main", AP_MZM_ID_BASE + 27), + "Kraid Acid Fall": LocationData("Kraid Bottom", AP_MZM_ID_BASE + 28), + "Kraid Zipline Activator Room": LocationData("Kraid Acid Worm Area", AP_MZM_ID_BASE + 29), + "Kraid Speed Jump": LocationData("Kraid Main", AP_MZM_ID_BASE + 30), + "Kraid Upper Right Morph Ball Cannon": LocationData("Kraid Main", AP_MZM_ID_BASE + 31), + "Kraid": LocationData("Kraid Bottom", None) } norfair_location_table = { - "Norfair Lava Power Bomb": AP_MZM_ID_BASE + 32, - "Norfair Lava Missile": AP_MZM_ID_BASE + 33, - "Norfair Screw Attack": AP_MZM_ID_BASE + 34, - "Norfair Screw Attack Missile": AP_MZM_ID_BASE + 35, - "Norfair Power Grip Missile": AP_MZM_ID_BASE + 36, - "Norfair Under Crateria Elevator": AP_MZM_ID_BASE + 37, - "Norfair Wave Beam": AP_MZM_ID_BASE + 38, - "Norfair Bomb Trap": AP_MZM_ID_BASE + 39, - "Norfair Bottom Heated Room First": AP_MZM_ID_BASE + 40, #TODO: maybe rename - "Norfair Bottom Heated Room Second": AP_MZM_ID_BASE + 41, #TODO and this one - "Norfair Heated Room Under Brinstar Elevator": AP_MZM_ID_BASE + 42, - "Norfair Space Boost Missile": AP_MZM_ID_BASE + 43, # TODO maybe rename - "Norfair Space Boost Super Missile": AP_MZM_ID_BASE + 44, # TODO and this one - "Norfair Ice Beam": AP_MZM_ID_BASE + 45, - "Norfair Heated Room above Ice Beam": AP_MZM_ID_BASE + 46, - "Norfair Hi-Jump": AP_MZM_ID_BASE + 47, - "Norfair Big Room": AP_MZM_ID_BASE + 48, - "Norfair Behind Top Chozo Statue": AP_MZM_ID_BASE + 49, - "Norfair Larva Ceiling E-tank": AP_MZM_ID_BASE + 50, - "Norfair Right Shaft Lower": AP_MZM_ID_BASE + 51, - "Norfair Right Shaft Bottom": AP_MZM_ID_BASE + 52 + "Norfair Lava Dive Left": LocationData("Lower Norfair", AP_MZM_ID_BASE + 32), + "Norfair Lava Dive Right": LocationData("Lower Norfair", AP_MZM_ID_BASE + 33), + "Norfair Screw Attack": LocationData("Norfair Screw Attack Area", AP_MZM_ID_BASE + 34), + "Norfair Next to Screw Attack": LocationData("Norfair Screw Attack Area", AP_MZM_ID_BASE + 35), + "Norfair Hallway to Crateria": LocationData("Norfair Main", AP_MZM_ID_BASE + 36), + "Norfair Under Crateria Elevator": LocationData("Norfair Main", AP_MZM_ID_BASE + 37), + "Norfair Wave Beam": LocationData("Lower Norfair", AP_MZM_ID_BASE + 38), + "Norfair Bomb Trap": LocationData("Norfair Lower Right Shaft", AP_MZM_ID_BASE + 39), + "Norfair Heated Room Below Wave - Left": LocationData("Lower Norfair", AP_MZM_ID_BASE + 40), + "Norfair Heated Room Below Wave - Right": LocationData("Lower Norfair", AP_MZM_ID_BASE + 41), + "Norfair Heated Room Under Brinstar Elevator": LocationData("Norfair Lower Right Shaft", AP_MZM_ID_BASE + 42), + "Norfair Behind Lower Super Missile Door - Right": LocationData("Norfair Behind Super Door", AP_MZM_ID_BASE + 43), + "Norfair Behind Lower Super Missile Door - Left": LocationData("Norfair Behind Super Door", AP_MZM_ID_BASE + 44), + "Norfair Ice Beam": LocationData("Norfair Upper Right Shaft", AP_MZM_ID_BASE + 45), + "Norfair Heated Room above Ice Beam": LocationData("Norfair Upper Right Shaft", AP_MZM_ID_BASE + 46), + "Norfair Hi-Jump": LocationData("Norfair Lower Right Shaft", AP_MZM_ID_BASE + 47), + "Norfair Big Room": LocationData("Norfair Right Shaft", AP_MZM_ID_BASE + 48), + "Norfair Behind Top Chozo Statue": LocationData("Norfair Behind Ice Beam", AP_MZM_ID_BASE + 49), + "Norfair Larva Ceiling": LocationData("Norfair Bottom", AP_MZM_ID_BASE + 50), + "Norfair Right Shaft Near Hi-Jump": LocationData("Norfair Lower Right Shaft", AP_MZM_ID_BASE + 51), + "Norfair Right Shaft Bottom": LocationData("Norfair Bottom", AP_MZM_ID_BASE + 52) } ridley_location_table = { - "Ridley Southwest Puzzle Top": AP_MZM_ID_BASE + 53, - "Ridley Southwest Puzzle Bottom": AP_MZM_ID_BASE + 54, - "Ridley West Pillar": AP_MZM_ID_BASE + 55, - "Ridley E-Tank behind Gravity": AP_MZM_ID_BASE + 56, - "Ridley Gravity Suit/Unknown Item 3": AP_MZM_ID_BASE + 57, - "Ridley Fake Floor E-Tank": AP_MZM_ID_BASE + 58, - "Ridley Upper Ball Cannon Puzzle": AP_MZM_ID_BASE + 59, - "Ridley Lower Ball Cannon Puzzle": AP_MZM_ID_BASE + 60, - "Ridley Imago Super Missile": AP_MZM_ID_BASE + 61, - "Ridley After Sidehopper Hall Upper": AP_MZM_ID_BASE + 62, - "Ridley After Sidehopper Hall Lower": AP_MZM_ID_BASE + 63, - "Ridley Long Hall": AP_MZM_ID_BASE + 64, - "Ridley Center Pillar Missile": AP_MZM_ID_BASE + 65, - "Ridley Ball Room Missile": AP_MZM_ID_BASE + 66, - "Ridley Ball Room Super": AP_MZM_ID_BASE + 67, - "Ridley Fake Lava Missile": AP_MZM_ID_BASE + 68, - "Ridley Owl E-Tank": AP_MZM_ID_BASE + 69, - "Ridley Northeast corner Missile": AP_MZM_ID_BASE + 70, - "Ridley Bomb Puzzle": AP_MZM_ID_BASE + 71, - "Ridley Speed Jump": AP_MZM_ID_BASE + 72, - "Ridley": None + "Ridley Southwest Puzzle Top": LocationData("Ridley SW Puzzle", AP_MZM_ID_BASE + 53), + "Ridley Southwest Puzzle Bottom": LocationData("Ridley SW Puzzle", AP_MZM_ID_BASE + 54), + "Ridley West Pillar": LocationData("Ridley Left Shaft", AP_MZM_ID_BASE + 55), + "Ridley Behind Unknown Statue": LocationData("Ridley Room", AP_MZM_ID_BASE + 56), + "Ridley Unknown Item Statue": LocationData("Ridley Room", AP_MZM_ID_BASE + 57), + "Ridley Fake Floor": LocationData("Ridley Left Shaft", AP_MZM_ID_BASE + 58), + "Ridley Upper Ball Cannon Puzzle": LocationData("Central Ridley", AP_MZM_ID_BASE + 59), + "Ridley Lower Ball Cannon Puzzle": LocationData("Central Ridley", AP_MZM_ID_BASE + 60), + "Ridley Imago Super Missile": LocationData("Ridley Main", AP_MZM_ID_BASE + 61), + "Ridley After Sidehopper Hall Upper": LocationData("Central Ridley", AP_MZM_ID_BASE + 62), + "Ridley After Sidehopper Hall Lower": LocationData("Central Ridley", AP_MZM_ID_BASE + 63), + "Ridley Long Hall": LocationData("Ridley Left Shaft", AP_MZM_ID_BASE + 64), + "Ridley Center Pillar": LocationData("Central Ridley", AP_MZM_ID_BASE + 65), + "Ridley Ball Room Lower": LocationData("Central Ridley", AP_MZM_ID_BASE + 66), + "Ridley Ball Room Upper": LocationData("Central Ridley", AP_MZM_ID_BASE + 67), + "Ridley Fake Lava Under Floor": LocationData("Ridley Right Shaft", AP_MZM_ID_BASE + 68), + "Ridley Under Owls": LocationData("Central Ridley", AP_MZM_ID_BASE + 69), + "Ridley Northeast Corner": LocationData("Ridley Right Shaft", AP_MZM_ID_BASE + 70), + "Ridley Bomb Puzzle": LocationData("Ridley Speed Puzzles", AP_MZM_ID_BASE + 71), + "Ridley Speed Jump": LocationData("Ridley Speed Puzzles", AP_MZM_ID_BASE + 72), + "Ridley": LocationData("Ridley Room", None) } tourian_location_table = { - "Tourian Left of Mother Brain": AP_MZM_ID_BASE + 73, - "Tourian Under Mother Brain": AP_MZM_ID_BASE + 74, - "Mother Brain": None + "Tourian Left of Mother Brain": LocationData("Tourian", AP_MZM_ID_BASE + 73), + "Tourian Under Mother Brain": LocationData("Tourian", AP_MZM_ID_BASE + 74), + "Mother Brain": LocationData("Tourian", None) } crateria_location_table = { - "Crateria Landing Site Ballspark": AP_MZM_ID_BASE + 75, - "Crateria Power Grip": AP_MZM_ID_BASE + 76, - "Crateria Moat": AP_MZM_ID_BASE + 77, - "Crateria Statue Water": AP_MZM_ID_BASE + 78, - "Crateria Plasma Beam/Unknown Item 1": AP_MZM_ID_BASE + 79, - "Crateria East Ballspark": AP_MZM_ID_BASE + 80, - "Crateria Northeast Corner": AP_MZM_ID_BASE + 81 + "Crateria Landing Site Ballspark": LocationData("Crateria", AP_MZM_ID_BASE + 75), + "Crateria Power Grip": LocationData("Upper Crateria", AP_MZM_ID_BASE + 76), + "Crateria Moat": LocationData("Crateria", AP_MZM_ID_BASE + 77), + "Crateria Statue Water": LocationData("Upper Crateria", AP_MZM_ID_BASE + 78), + "Crateria Unknown Item Statue": LocationData("Upper Crateria", AP_MZM_ID_BASE + 79), + "Crateria East Ballspark": LocationData("Upper Crateria", AP_MZM_ID_BASE + 80), + "Crateria Northeast Corner": LocationData("Upper Crateria", AP_MZM_ID_BASE + 81) } chozodia_location_table = { - "Chozodia Upper Crateria Door": AP_MZM_ID_BASE + 82, - "Chozodia Bomb Maze": AP_MZM_ID_BASE + 83, - "Chozodia Zoomer Maze": AP_MZM_ID_BASE + 84, - "Chozodia Ruins Near Upper Crateria Door": AP_MZM_ID_BASE + 85, - "Chozodia Chozo Ghost Area Morph Tunnel Above Water": AP_MZM_ID_BASE + 86, - "Chozodia Chozo Ghost Area Underwater": AP_MZM_ID_BASE + 87, - "Chozodia Under Chozo Ghost Area Water": AP_MZM_ID_BASE + 88, - "Chozodia Glass Tube E-Tank": AP_MZM_ID_BASE + 89, - "Chozodia Lava Super": AP_MZM_ID_BASE + 90, - "Chozodia Original Power Bomb": AP_MZM_ID_BASE + 91, - "Chozodia Next to Original Power Bomb": AP_MZM_ID_BASE + 92, - "Chozodia Glass Tube Power Bomb": AP_MZM_ID_BASE + 93, - "Chozodia Chozo Ghost Area Long Shinespark": AP_MZM_ID_BASE + 94, - "Chozodia Shortcut Super": AP_MZM_ID_BASE + 95, - "Chozodia Workbot Super": AP_MZM_ID_BASE + 96, - "Chozodia Mothership Ceiling Near ZSS Start": AP_MZM_ID_BASE + 97, - "Chozodia Under Mecha Ridley Hallway": AP_MZM_ID_BASE + 98, - "Chozodia Southeast Corner In Hull": AP_MZM_ID_BASE + 99, - "Chozo Ghost": None, - "Mecha Ridley": None, - "Chozodia Space Pirate's Ship": None + "Chozodia Upper Crateria Door": LocationData("Chozodia Ruins", AP_MZM_ID_BASE + 82), + "Chozodia Bomb Maze": LocationData("Chozodia Under Tube", AP_MZM_ID_BASE + 83), + "Chozodia Zoomer Maze": LocationData("Chozodia Under Tube", AP_MZM_ID_BASE + 84), + "Chozodia Ruins East of Upper Crateria Door": LocationData("Chozodia Ruins", AP_MZM_ID_BASE + 85), + "Chozodia Chozo Ghost Area Morph Tunnel Above Water": LocationData("Chozodia Ruins Test Area", AP_MZM_ID_BASE + 86), + "Chozodia Chozo Ghost Area Underwater": LocationData("Chozodia Ruins Test Area", AP_MZM_ID_BASE + 87), + "Chozodia Triple Crawling Pirates": LocationData("Chozodia Ruins", AP_MZM_ID_BASE + 88), + "Chozodia Left of Glass Tube": LocationData("Chozodia Under Tube", AP_MZM_ID_BASE + 89), + "Chozodia Lava Dive": LocationData("Chozodia Ruins Test Area", AP_MZM_ID_BASE + 90), + "Chozodia Original Power Bomb": LocationData("Chozodia Original Power Bomb Room", AP_MZM_ID_BASE + 91), + "Chozodia Next to Original Power Bomb": LocationData("Chozodia Original Power Bomb Room", AP_MZM_ID_BASE + 92), + "Chozodia Right of Glass Tube": LocationData("Chozodia Under Tube", AP_MZM_ID_BASE + 93), + "Chozodia Chozo Ghost Area Long Shinespark": LocationData("Chozodia Ruins Test Area", AP_MZM_ID_BASE + 94), + "Chozodia Pirate Pitfall Trap": LocationData("Chozodia Mothership Central", AP_MZM_ID_BASE + 95), + "Chozodia Behind Workbot": LocationData("Chozodia Mothership Central", AP_MZM_ID_BASE + 96), + "Chozodia Ceiling Near Map Station": LocationData("Chozodia Mothership Central", AP_MZM_ID_BASE + 97), + "Chozodia Under Mecha Ridley Hallway": LocationData("Chozodia Mecha Ridley Hallway", AP_MZM_ID_BASE + 98), + "Chozodia Southeast Corner In Hull": LocationData("Chozodia Mothership Central", AP_MZM_ID_BASE + 99), + "Chozo Ghost": LocationData("Chozodia Ruins Test Area", None), + "Mecha Ridley": LocationData("Chozodia Mecha Ridley Hallway", None), + "Chozodia Space Pirate's Ship": LocationData("Chozodia Mecha Ridley Hallway", None) } -full_location_table: dict[str, int] = { +full_location_table = { **brinstar_location_table, **kraid_location_table, **norfair_location_table, diff --git a/logic.py b/logic.py index 84393be..eaa74f9 100644 --- a/logic.py +++ b/logic.py @@ -1,309 +1,891 @@ """ -Functions used to describe Metroid: Zero Mission logic rules in rules.py +Functions used to describe Metroid: Zero Mission logic rules """ +from __future__ import annotations + +import builtins +import functools +from typing import TYPE_CHECKING, Any, Callable, NamedTuple from BaseClasses import CollectionState -from .options import Goal, MZMOptions + +if TYPE_CHECKING: + from . import MZMWorld + + +class Requirement(NamedTuple): + rule: Callable[[MZMWorld, CollectionState], bool] + + def create_rule(self, world: MZMWorld): + return functools.partial(self.rule, world) + + @classmethod + def item(cls, item: str, count: int = 1): + return cls(lambda world, state: state.has(item, world.player, count)) + + @classmethod + def location(cls, location: str): + return cls(lambda world, state: state.can_reach_location(location, world.player)) + + @classmethod + def entrance(cls, entrance: str): + return cls(lambda world, state: state.can_reach_entrance(entrance, world.player)) + + @classmethod + def setting_enabled(cls, setting: str): + return cls(lambda world, _: getattr(world.options, setting)) + + @classmethod + def setting_is(cls, setting: str, value: Any): + return cls(lambda world, _: getattr(world.options, setting) == value) + + @classmethod + def setting_atleast(cls, setting: str, value: int): + return cls(lambda world, _: getattr(world.options, setting) >= value) + + +def all(*args: Requirement): + return Requirement(lambda world, state: builtins.all(req.rule(world, state) for req in args)) + + +def any(*args: Requirement): + return Requirement(lambda world, state: builtins.any(req.rule(world, state) for req in args)) + + +KraidBoss = Requirement.item("Kraid Defeated") +RidleyBoss = Requirement.item("Ridley Defeated") +MotherBrainBoss = Requirement.item("Mother Brain Defeated") +ChozoGhostBoss = Requirement.item("Chozo Ghost Defeated") +MechaRidleyBoss = Requirement.item("Mecha Ridley Defeated") +CanReachLocation = lambda n: Requirement.location(n) +CanReachEntrance = lambda n: Requirement.entrance(n) + +UnknownItem1 = Requirement.location("Crateria Unknown Item Statue") +UnknownItem2 = Requirement.location("Kraid Unknown Item Statue") +UnknownItem3 = Requirement.location("Ridley Unknown Item Statue") + +CanUseUnknownItems = any( + Requirement.setting_enabled("unknown_items_always_usable"), + ChozoGhostBoss, +) +LayoutPatches = Requirement.setting_enabled("layout_patches") + +EnergyTanks = lambda n: Requirement.item("Energy Tank", n) +MissileTanks = lambda n: Requirement.item("Missile Tank", n) +SuperMissileTanks = lambda n: Requirement.item("Super Missile Tank", n) +PowerBombTanks = lambda n: Requirement.item("Power Bomb Tank", n) +LongBeam = Requirement.item("Long Beam") +ChargeBeam = Requirement.item("Charge Beam") +IceBeam = Requirement.item("Ice Beam") +WaveBeam = Requirement.item("Wave Beam") +PlasmaBeam = all( + Requirement.item("Plasma Beam"), + CanUseUnknownItems, +) +Bomb = Requirement.item("Bomb") +VariaSuit = Requirement.item("Varia Suit") +GravitySuit = all( + Requirement.item("Gravity Suit"), + CanUseUnknownItems +) +MorphBall = Requirement.item("Morph Ball") +SpeedBooster = Requirement.item("Speed Booster") +HiJump = Requirement.item("Hi-Jump") +ScrewAttack = Requirement.item("Screw Attack") +SpaceJump = all( + Requirement.item("Space Jump"), + CanUseUnknownItems +) +PowerGrip = Requirement.item("Power Grip") + +Missiles = any( + MissileTanks(1), + SuperMissileTanks(1), +) +MissileCount = lambda n: Requirement( + # TODO: account for Hard + lambda world, state: + 5 * state.count("Missile Tank", world.player) + 2 * state.count("Super Missile Tank", world.player) >= n +) +SuperMissiles = SuperMissileTanks(1) +PowerBombs = PowerBombTanks(1) +PowerBombCount = lambda n: PowerBombTanks(n // 2) # TODO: account for Hard + +# Various morph/bomb rules +CanRegularBomb = all( + MorphBall, + Bomb +) +CanBombTunnelBlock = all( + MorphBall, + any( + Bomb, + PowerBombTanks(1), + ), +) +CanSingleBombBlock = any( + CanBombTunnelBlock, + ScrewAttack +) +CanBallCannon = CanRegularBomb +CanBallspark = all( + MorphBall, + SpeedBooster, + HiJump, +) +CanBallJump = all( + MorphBall, + any( + Bomb, + HiJump + ) +) +CanLongBeam = any( + LongBeam, + MissileCount(1), + CanBombTunnelBlock, +) + +# Logic option rules +AdvancedLogic = Requirement.setting_atleast("logic_difficulty", 1) +CanIBJ = all( + Requirement.setting_atleast("ibj_in_logic", 1), + CanRegularBomb, +) +CanHorizontalIBJ = all( + CanIBJ, + Requirement.setting_atleast("ibj_in_logic", 2) +) +CanWallJump = Requirement.setting_atleast("walljumps_in_logic", 1) +CanTrickySparks = all( + Requirement.setting_enabled("tricky_shinesparks"), + SpeedBooster, +) +Hellrun = lambda n: all( + Requirement.setting_enabled("heatruns_lavadives"), + EnergyTanks(n), +) + +# Miscellaneous rules +CanFly = any( # infinite vertical + CanIBJ, + SpaceJump +) +CanFlyWall = any( # infinite vertical with a usable wall + CanFly, + CanWallJump +) +CanVertical = any( # fka can_hj_sj_ibj_or_grip + HiJump, + PowerGrip, + CanFly +) +CanVerticalWall = any( + CanVertical, + CanWallJump +) +CanHiGrip = all( + HiJump, + PowerGrip +) +CanEnterHighMorphTunnel = any( + CanIBJ, + all( + MorphBall, + PowerGrip + ) +) +CanEnterMediumMorphTunnel = any( + CanEnterHighMorphTunnel, + all( + MorphBall, + HiJump + ) +) +# Kraid ziplines +Ziplines = CanReachEntrance("Kraid Main -> Acid Worm Area") +ChozodiaCombat = all( + any( + IceBeam, + PlasmaBeam + ), + EnergyTanks(4) +) + +# Goal +ReachedGoal = any( + all( + Requirement.setting_is("goal", 0) + ), + all( + Requirement.setting_is("goal", 1), + MotherBrainBoss, + ChozoGhostBoss + ), +) + + +# Regional connection requirements + +# brinstar main to past-hives, top to past-hives is different +def brinstar_past_hives(): + return all( + MorphBall, + Missiles, + any( + AdvancedLogic, + MissileCount(10), + SuperMissiles, + LongBeam, + IceBeam, + WaveBeam, + PlasmaBeam, + ScrewAttack + ) + ) -# TODO: Add missing logic options -# TODO: Fine tune boss logic +def brinstar_main_to_brinstar_top(): + return any( + all( + CanSingleBombBlock, + CanBallJump + ), + all( + AdvancedLogic, + IceBeam, + CanWallJump, + PowerBombs + ) # truly cursed strat + ) -def _get_options(state: CollectionState, player: int) -> MZMOptions: - return state.multiworld.worlds[player].options +def brinstar_pasthives_to_brinstar_top(): + return all( + any( + CanFly, + all( + HiJump, + IceBeam, + CanWallJump + ) + ), + CanBallJump + ) -def has_missiles(state: CollectionState, player: int) -> bool: - return state.has_any({"Missile Tank", "Super Missile Tank"}, player) +# this works for now. it's kind of tricky, cause all you need just to get there is PBs and bombs, +# but to actually do anything (including get to ship) you need IBJ/speed/sj. it only checks for speed +# for now since the only thing you'd potentially need this entrance for is Landing Site Ballspark +# (this assumption changes if/when entrance/elevator rando happens) +def brinstar_crateria_ballcannon(): + return all( + PowerBombs, + CanBallCannon, + CanVertical, + SpeedBooster + ) -def has_missile_count(state: CollectionState, player: int, required_missiles: int) -> int: - # TODO: somehow account for Hard mode - missilecount = (state.count("Missile Tank", player) * 5) + (state.count("Super Missile Tank", player) * 2) - return missilecount >= required_missiles +# used for the items in this area as well as determining whether the ziplines can be activated +def kraid_upper_right(): + return all( + Missiles, + CanBallCannon, + any( + CanHorizontalIBJ, + PowerGrip, + all( + IceBeam, + CanBallJump + ) + ) + ) -def has_super_missiles(state: CollectionState, player: int) -> bool: - return state.has("Super Missile Tank", player) +# access to lower kraid +def kraid_left_shaft_access(): + return all( + any( + CanHorizontalIBJ, + PowerGrip, + HiJump + ), + CanBallJump, + CanBombTunnelBlock, + any( + Ziplines, + SpaceJump, + all( + GravitySuit, + any( + CanTrickySparks, + CanIBJ + ) + ), + all( # Acid Worm Skip + AdvancedLogic, + PowerGrip + ) + ) + ) -def has_power_bombs(state: CollectionState, player: int): - return state.has("Power Bomb Tank", player) +def kraid_left_shaft_to_bottom(): + return UnknownItem2 -def power_bomb_count(state: CollectionState, player: int, required_pbs: int): - pbcount = (state.count("Power Bomb Tank", player) * 2) - return pbcount >= required_pbs +def kraid_bottom_to_lower_norfair(): + return all( + ScrewAttack, + PowerBombs, + Missiles, + MorphBall + ) -def can_regular_bomb(state: CollectionState, player: int): - return state.has_all({"Morph Ball", "Bomb"}, player) +def norfair_main_to_crateria(): + return all( + MorphBall, + any( + CanLongBeam, + CanBallspark + ) + ) -# this may be different from can_regular_bomb later and also just reads easier in some rules -def can_ballcannon(state: CollectionState, player: int): - return state.has_all({"Morph Ball", "Bomb"}, player) +def norfair_right_shaft_access(): + return any( + CanVertical, + SpeedBooster + ) -def can_bomb_block(state: CollectionState, player: int) -> bool: - return (state.has("Morph Ball", player) - and state.has_any({"Bomb", "Power Bomb Tank"}, player)) +def norfair_upper_right_shaft(): + return any( + CanVerticalWall, + IceBeam + ) -# checked for ability to clear blocks that would require long beam to hit -def can_long_beam(state: CollectionState, player: int) -> bool: - return (state.has("Long Beam", player) - or has_missile_count(state, player, 3) - or can_bomb_block(state, player)) +def norfair_behind_ice_beam(): + return all( + any( + CanLongBeam, + WaveBeam + ), + MorphBall, + any( + all( + PowerGrip, + any( + CanWallJump, + SpaceJump, + IceBeam + ) + ), + CanIBJ, + all( + IceBeam, + HiJump + ) + ) + ) -def can_hi_jump(state: CollectionState, player: int) -> bool: - return state.has("Hi-Jump", player) or can_space_jump(state, player) or can_ibj(state, player) +def norfair_behind_ice_to_bottom(): + return all( + Missiles, + CanBombTunnelBlock, + any( + PowerGrip, + CanHorizontalIBJ, + all( + IceBeam, + CanBallJump + ) + ), + any( + all( + AdvancedLogic, + PowerBombs, + HiJump + ), + all( + PowerGrip, + any( + CanWallJump, + SpaceJump + ) + ) + ) + ) -# this particular combination is extremely common -def can_hj_sj_ibj_or_grip(state: CollectionState, player: int) -> bool: - return can_hi_jump(state, player) or state.has("Power Grip", player) +def norfair_lower_right_shaft(): + return any( + ScrewAttack, + all( + SpeedBooster, + CanBallCannon + ) + ) -def can_ballspark(state: CollectionState, player: int) -> bool: - return state.has_all({"Morph Ball", "Hi-Jump", "Speed Booster"}, player) +def norfair_lower_right_shaft_to_lower_norfair(): + return all( + Missiles, + CanBombTunnelBlock, + any( + SpaceJump, + CanWallJump, + all( + Bomb, + any( + PowerGrip, + CanHorizontalIBJ, + all( + AdvancedLogic, + SuperMissiles, + IceBeam + ) + ) + ), + ), + any( + VariaSuit, + Hellrun(6) + ), + any( + SpaceJump, + CanHorizontalIBJ, + all( + CanSingleBombBlock, + SpeedBooster + ) + ) + ) -def can_space_jump(state: CollectionState, player: int) -> bool: - return (state.has("Space Jump", player) - and (state.has("Chozo Ghost Defeated", player) - or _get_options(state, player).unknown_items_always_usable.value) +def lower_norfair_to_screwattack(): + return any( + CanTrickySparks, + all( + ScrewAttack, + any( + CanWallJump, + SpaceJump ) + ), + all( + MissileCount(5), + any( + CanFlyWall, + all( + AdvancedLogic, + IceBeam, + HiJump + ) + ) + ) + ) -def can_traverse_heat(state: CollectionState, player: int) -> bool: - return state.has("Varia Suit", player) +def screw_to_lower_norfair(): + return any( + MissileCount(4), + ScrewAttack + ) -def can_gravity_suit(state: CollectionState, player: int) -> bool: - return (state.has("Gravity Suit", player) - and (state.has("Chozo Ghost Defeated", player) - or _get_options(state, player).unknown_items_always_usable.value) +def lower_norfair_to_kraid(): + return all( + ScrewAttack, + PowerBombs, + Missiles, + any( + CanIBJ, + PowerGrip, + all( + HiJump, + IceBeam + ), + all( + CanTrickySparks, + CanBallspark ) + ) + ) -# currently used for both heated rooms AND lava dives -def hellrun(state: CollectionState, player: int, required_etanks: int) -> bool: - return (_get_options(state, player).heatruns_lavadives.value - and (state.count("Energy Tank", player) >= required_etanks)) +# The two items in Lower Norfair behind the Super Missile door right under the Screw Attack area +def lower_norfair_to_spaceboost_room(): + return all( + SuperMissiles, + any( + SpeedBooster, + Bomb, + PowerBombCount(2), + all( + WaveBeam, + LongBeam, + any( + PowerGrip, + all( + GravitySuit, + HiJump + ) + ) + ) + ), + CanVertical + ) -def can_ibj(state: CollectionState, player: int) -> bool: - return _get_options(state, player).ibj_in_logic.value and can_regular_bomb(state, player) +def lower_norfair_to_bottom_norfair(): + return all( + MissileCount(2), + SpeedBooster, + any( + VariaSuit, + Hellrun(1) + ), + any( + WaveBeam, + CanTrickySparks + ), + CanEnterMediumMorphTunnel + ) -def can_walljump(state: CollectionState, player: int) -> bool: - return _get_options(state, player).walljumps_in_logic.value +def bottom_norfair_to_ridley(): + return any( + all( + MissileCount(6), # Covers the case where you only have Supers; 1 normal missile is enough from drops + any( + IceBeam, + AdvancedLogic + ) + ), + PowerBombs + ) -def can_tricky_sparks(state: CollectionState, player: int) -> bool: - return False and state.has("Speed Booster", player) # TODO: add option +def bottom_norfair_to_screw(): + return all( + RidleyBoss, + SpeedBooster, + CanBallCannon, + any( + IceBeam, + CanVerticalWall + ) + ) -def brinstar_past_hives(state: CollectionState, player: int) -> bool: - return (state.has("Morph Ball", player) - and (has_missile_count(state, player, 10) - or state.has("Super Missile Tank", player) - or state.has_any({"Long Beam", "Ice Beam", "Wave Beam", "Plasma Beam", "Screw Attack"}, player))) +def ridley_main_to_left_shaft(): + return all( + SuperMissiles, + any( + CanVerticalWall, + IceBeam + ), + any( + VariaSuit, + Hellrun(1), + all( + CanFly, + CanBombTunnelBlock + ) + ), + MorphBall + ) -# used for the items in this area as well as determining whether the ziplines can be activated -# it's technically possible to do the climb after ballcannon with just hi-jump using tight morph jumps -# TODO: add solo hi-jump logic once an option for advanced tricks is added -def kraid_upper_right(state: CollectionState, player: int) -> bool: - return (has_missiles(state, player) - and can_ballcannon(state, player) - and (can_ibj(state, player) - or can_walljump(state, player) - or can_space_jump(state, player) - or state.has("Power Grip", player)) - ) and (can_ibj(state, player) - or state.has("Power Grip", player) - or (state.has_all({"Ice Beam", "Hi-Jump"}, player))) - - -# access to lower kraid via left shaft -# TODO: add logic for acid worm skip -def kraid_left_shaft_access(state: CollectionState, player: int) -> bool: - return ((can_ibj(state, player) or state.has_any({"Power Grip", "Hi-Jump"}, player)) - and can_bomb_block(state, player) - and (can_regular_bomb(state, player) or state.has("Hi-Jump", player)) - and (kraid_upper_right(state, player) - or can_space_jump(state, player) - or (can_gravity_suit(state, player) and can_tricky_sparks(state, player)) - ) +# shortcut to the right of elevator +def ridley_main_to_right_shaft(): + return all( + Missiles, + any( + CanIBJ, + all( + PowerGrip, + CanBombTunnelBlock, + any( + SpaceJump, + HiJump, + IceBeam + ) ) + ) + ) -def norfair_right_shaft_access(state: CollectionState, player: int) -> bool: - return can_hj_sj_ibj_or_grip(state, player) or state.has("Speed Booster", player) +def ridley_left_shaft_to_sw_puzzle(): + return all( + SpeedBooster, + any( + PowerGrip, + SpaceJump + ), + any( + PowerGrip, + PowerBombs, + all( + LongBeam, + WaveBeam + ) + ) + ) -def norfair_upper_right_shaft(state: CollectionState, player: int) -> bool: - return (norfair_right_shaft_access(state, player) - and (can_hj_sj_ibj_or_grip(state, player) - or state.has("Ice Beam", player) - or can_walljump(state, player)) - ) +# The alcove to the right of the right shaft +def ridley_speed_puzzles_access(): + return all( + SpeedBooster, + any( + CanVerticalWall, + IceBeam + ) + ) -# used for one item and the ridley shortcut -def norfair_behind_ice_beam(state: CollectionState, player: int) -> bool: - return (norfair_upper_right_shaft(state, player) - and (can_long_beam(state, player) or state.has("Wave Beam", player)) - and ((state.has("Ice Beam", player) and state.has_any({"Hi-Jump", "Power Grip"}, - player)) # to get up the rippers - or can_ibj(state, player) - or (can_walljump(state, player) and state.has("Power Grip", player))) +# getting into the gap at the start of "ball room" and subsequently into the general area of ridley himself +def ridley_right_shaft_to_central(): + return CanEnterMediumMorphTunnel + + +# Ridley, Unknown 3, and the item behind Unknown 3 +def ridley_central_to_ridley_room(): + return all( + any( + AdvancedLogic, + all( + MissileCount(40), + EnergyTanks(3), + ) + ), + any( + CanFly, + all( + IceBeam, + CanVerticalWall ) + ) + ) -def norfair_lower_right_shaft(state: CollectionState, player: int) -> bool: - return (norfair_right_shaft_access(state, player) - and (state.has("Screw Attack", player) - or (state.has("Speed Booster", player) and can_ballcannon(state, player))) - ) +# Getting to Unknown 1 and everything above +def crateria_main_to_crateria_upper(): + return CanBallJump + + +# Upper Crateria door to Ruins, the two items right by it, and the Triple Crawling Pirates +def crateria_upper_to_chozo_ruins(): + return all( + PowerBombs, + MorphBall, + Missiles, + any( + CanFly, + CanReachLocation("Crateria Northeast Corner") + ), + any( + MotherBrainBoss, + Requirement.setting_is("chozodia_access", 0) + ) + ) +# Ruins to Chozo Ghost, the three items in that general area, and the lava dive item +def chozo_ruins_to_ruins_test(): + return all( + MorphBall, + PowerBombs, + any( + Bomb, + PowerBombCount(2) + ), + any( + AdvancedLogic, + ChozodiaCombat + ), + any( # Required to exit the Ruins Test + all( + AdvancedLogic, + CanHiGrip, + CanWallJump + ), + CanIBJ, + Requirement.item("Space Jump") # Need SJ to escape, but it doesn't need to be active yet + ), + CanEnterMediumMorphTunnel # Required to exit the Ruins Test + ) -def norfair_to_save_behind_hijump(state: CollectionState, player: int) -> bool: - return (norfair_lower_right_shaft(state, player) - and has_missiles(state, player) - and can_bomb_block(state, player) - and (can_ibj(state, player) - or (has_power_bombs(state, player) and state.has("Hi-Jump", player)) - or (state.has("Bomb", player) and state.has_any({"Hi-Jump", "Power Grip"}, player)) - ) - and (can_hj_sj_ibj_or_grip(state, player) or can_walljump(state, player) - or state.has("Ice Beam", player)) - and (can_traverse_heat(state, player) - or hellrun(state, player, 6)) - and (can_ibj(state, player) - or can_space_jump(state, player) - or (state.has("Speed Booster", player) - and (can_bomb_block(state, player) or state.has("Screw Attack", player)) - ) - ) - ) +def chozo_ruins_to_chozodia_tube(): + return any( + all( # Getting up to the tube is doable with just walljumps but tricky enough to be advanced imo + AdvancedLogic, + CanWallJump + ), + CanFly + ) -def norfair_shortcut(state: CollectionState, player: int) -> bool: - return (norfair_behind_ice_beam(state, player) - and has_missiles(state, player) - and (can_ibj(state, player) - or (state.has("Power Grip", player) - and (can_space_jump(state, player) or can_walljump(state, player)) - and can_bomb_block(state, player))) - ) +# Specifically getting to the room with Crateria Upper Door location. Might need another empty region for region rando +def chozodia_tube_to_chozo_ruins(): + return all( + any( + CanFlyWall, + CanHiGrip + ), + CanBombTunnelBlock + ) -def norfair_bottom_right_shaft(state: CollectionState, player: int) -> bool: - return ((norfair_to_save_behind_hijump(state, player) - and has_missile_count(state, player, 4) - and state.has_all({"Wave Beam", "Speed Booster"}, player) - ) - or (norfair_shortcut(state, player))) +def crateria_to_under_tube(): + return all( + PowerBombs, + MorphBall, + any( # To get to the save station and warp out + SpeedBooster, + CanFlyWall, + CanHiGrip + ), + any( + MotherBrainBoss, + Requirement.setting_is("chozodia_access", 0) + ) + ) -def ridley_left_shaft_access(state: CollectionState, player: int) -> bool: - return (has_super_missiles(state, player) - and (can_hj_sj_ibj_or_grip(state, player) or can_walljump(state, player) - or state.has("Ice Beam", player) or can_bomb_block(state, player)) - and (can_traverse_heat(state, player) or hellrun(state, player, 1) - or (can_space_jump(state, player) and can_bomb_block(state, player))) - ) +def under_tube_to_tube(): + return any( + SpeedBooster, + all( + CanFly, + PowerBombs, + ChozoGhostBoss # Change if basepatch makes the tube breakable before Charlie + ) + ) -# going the "intended" way, to the left of the elevator, down, and back to the right to get to the right shaft -def ridley_longway_right_shaft_access(state: CollectionState, player: int) -> bool: - return (ridley_left_shaft_access(state, player) - and (state.has("Power Grip", player) - and (can_hj_sj_ibj_or_grip(state, player) or can_walljump(state, player))) - ) + +def under_tube_to_crateria(): + return any( + CanIBJ, + all( + PowerGrip, + CanFlyWall + ), + all( + CanTrickySparks, + CanBallspark + ) + ) + + +def tube_to_under_tube(): + return all( + ChozoGhostBoss, + PowerBombs + ) -# taking the shortcut, to the right of the elevator and up the hole -def ridley_shortcut_right_shaft_access(state: CollectionState, player: int) -> bool: - return (has_missiles(state, player) - and (can_ibj(state, player) - or (state.has("Power Grip", player) - and can_bomb_block(state, player) - and (state.has_any({"Ice Beam", "Hi-Jump"}, player) - or can_space_jump(state, player))) - ) +def chozodia_tube_to_mothership_central(): + return all( + any( + AdvancedLogic, + ChozodiaCombat + ), + any( + CanFly, + all( + CanWallJump, + HiJump ) + ) + ) -# getting into the gap at the start of "ball room" and subsequently into the general area of ridley himself -def ridley_central_access(state: CollectionState, player: int) -> bool: - return ( - (ridley_shortcut_right_shaft_access(state, player) or ridley_longway_right_shaft_access(state, player)) - and (can_ibj(state, player) or state.has_any({"Hi-Jump", "Power Grip"}, player)) - ) - - -# ice/plasma beam makes dealing with the pirates a ton easier. etanks keeps you from needing to go too deep before you have -# a decent chance to survive. the rest is the strictest requirement to get in and back out of the chozo ghost area -def chozodia_ghost_from_upper_crateria_door(state: CollectionState, player: int) -> bool: - return ( - state.has_any({"Ice Beam", "Plasma Beam"}, player) and (state.count("Energy Tank", player) >= 4) - and has_missiles(state, player) - and (can_walljump(state, player) or can_ibj(state, player) - or can_space_jump(state, player)) - and state.count("Power Bomb Tank", player) >= 2 - ) - - -def chozodia_glass_tube_from_crateria_door(state: CollectionState, player: int) -> bool: - return ( - # from upper door - (state.has_any({"Ice Beam", "Plasma Beam"}, player) - and state.count("Energy Tank", player) >= 2 - and has_missiles(state, player) - and has_power_bombs(state, player) - and (can_space_jump(state, player) or can_ibj(state, player))) - # from lower door - or (state.has_any({"Ice Beam", "Plasma Beam"}, player) - and state.count("Energy Tank", player) >= 2 - and (can_ibj(state, player) - or can_space_jump(state, player) - or has_power_bombs(state, player) - ) - and has_missile_count(state, player, 6) - and has_power_bombs(state, player) - ) +def mothership_central_to_cockpit(): + return all( + any( + Bomb, + PowerBombCount(2) + ), + any( + ScrewAttack, + MissileCount(5) + ), + any( + SuperMissiles, + PowerGrip, + CanFly + ), + any( + AdvancedLogic, + EnergyTanks(6) + ) ) -# from the ruins to the save station next to the big room with all the tripwires -def chozodia_tube_to_mothership_central(state: CollectionState, player: int) -> bool: - return (chozodia_glass_tube_from_crateria_door(state, player) - and state.count("Energy Tank", player) >= 6 - and (can_ibj(state, player) - or can_space_jump(state, player) - or (state.has("Hi-Jump", player) - and (can_walljump(state, player) or state.has("Power Grip", player))) - ) +def cockpit_to_original_pb(): + return all( + any( + CanWallJump, + HiJump, + PowerGrip, + SpaceJump + ), # cannot IBJ to escape to cockpit + any( + CanIBJ, + all( + PowerGrip, + any( + CanFlyWall, + HiJump + ) + ), + all( + AdvancedLogic, + IceBeam, + CanBallJump ) + ) + ) -def chozodia_to_cockpit(state: CollectionState, player: int) -> bool: - return (chozodia_tube_to_mothership_central(state, player) - and (can_space_jump(state, player) - or can_ibj(state, player) - or (can_walljump(state, player) and state.has("Hi-Jump", player)) +def cockpit_to_mecha_ridley(): + return all( + CanBombTunnelBlock, + any( + CanIBJ, + PowerGrip, + all( + AdvancedLogic, + IceBeam, + HiJump ) - and (state.has("Bomb", player) or state.count("Power Bomb Tank", player) >= 2) + ), + CanBallJump, + any( + PowerBombCount(2), + all( + Bomb, + PowerBombs + ), + all( + AdvancedLogic, + any( + CanIBJ, + all( + PowerGrip, + any( + HiJump, + SpaceJump, + CanWallJump + ) + ) + ) ) - - -def goal(state: CollectionState, player: int) -> bool: - goal = _get_options(state, player).goal - if goal == Goal.option_bosses: - return state.has_all({"Mother Brain Defeated", "Chozo Ghost Defeated"}, player) - return True + ) + ) diff --git a/options.py b/options.py index ddd8a63..dde15ae 100644 --- a/options.py +++ b/options.py @@ -25,7 +25,7 @@ class Goal(Choice): class ChozodiaAccess(Choice): """ - Open: You can access Chozodia by using a Power Bomb to open the doors. + Open: You can access Chozodia at any time by using a Power Bomb to open the doors. Closed: You must defeat Mother Brain to access Chozodia. """ display_name = "Chozodia Access" @@ -56,17 +56,36 @@ class StartWithMaps(DefaultOnToggle): display_name = "Start with Maps" -class IBJInLogic(Toggle): +class LogicDifficulty(Choice): + """ + Determines the general difficulty of the game's logic. On advanced difficulty, more niche techniques and game + knowledge may be required to collect items or progress, and you may be required to complete areas or bosses + with the minimum required resources. Examples of "tricks" this may put in logic include entering invisible tunnels, + jump extends, and Acid Worm skip. + + Other specific tricks (such as difficult Shinesparks and horizontal IBJ) have individual difficulty settings that + this does not affect. + """ + display_name = "Logic Difficulty" + option_normal = 0 + option_advanced = 1 + + +class IBJInLogic(Choice): """ Allows for using IBJ (infinite bomb jumping) in logic. - Enabling this option may require you to traverse long vertical or horizontal distances using only bombs. + Enabling this option may require you to traverse long vertical and/or horizontal distances using only bombs. If disabled, this option does not disable performing IBJ, but it will never be logically required. """ display_name = "IBJ In Logic" + option_none = 0 + option_vertical_only = 1 + option_horizontal_and_vertical = 2 +# TODO: split into none/simple/advanced class HeatRunsAndLavaDives(Toggle): """ Allows for traversing heated rooms and acid/lava dives without the appropriate suit(s) in logic. @@ -88,9 +107,20 @@ class WalljumpsInLogic(DefaultOnToggle): display_name = "Wall Jumps In Logic" +class TrickyShinesparks(Toggle): + """ + If enabled, logic will include long, difficult, and/or unintuitive Shinesparks as valid methods of collecting + items or traversing areas that normally would not require an advanced Shinespark to collect. + + This has no effect on long Shinespark puzzles which are the intended way of collecting an item, such as the long + Shinespark chain in Chozodia near the Chozo Ghost room. + """ + display_name = "Tricky Shinesparks" + + class LayoutPatches(DefaultOnToggle): """ - Modify the layout of a few rooms to reduce softlocks. + Slightly modify the layout of some rooms to reduce softlocks. NOTE: You can warp to the starting room from any save station or Samus' ship by holding L+R while selecting "No" when asked to save. """ @@ -160,9 +190,11 @@ class JunkFillWeights(OptionDict): StartWithMaps, ]), OptionGroup("Logic", [ + LogicDifficulty, IBJInLogic, HeatRunsAndLavaDives, WalljumpsInLogic, + TrickyShinesparks ]), OptionGroup("Cosmetic", [ FastItemBanners, @@ -184,9 +216,11 @@ class MZMOptions(PerGameCommonOptions): layout_patches: LayoutPatches morph_ball: MorphBallPlacement start_with_maps: StartWithMaps + logic_difficulty: LogicDifficulty ibj_in_logic: IBJInLogic heatruns_lavadives: HeatRunsAndLavaDives walljumps_in_logic: WalljumpsInLogic + tricky_shinesparks: TrickyShinesparks fast_item_banners: FastItemBanners display_nonlocal_items: DisplayNonLocalItems start_inventory_from_pool: StartInventoryPool diff --git a/regions.py b/regions.py index 49cbe75..90913cb 100644 --- a/regions.py +++ b/regions.py @@ -1,94 +1,122 @@ from __future__ import annotations -from typing import TYPE_CHECKING -from BaseClasses import Region -from .locations import (brinstar_location_table, kraid_location_table, norfair_location_table, - ridley_location_table, tourian_location_table, crateria_location_table, - chozodia_location_table, MZMLocation) -from . import logic +from typing import TYPE_CHECKING, Dict, List, Optional +from BaseClasses import Region, Location, MultiWorld +from .logic import * +from .locations import full_location_table if TYPE_CHECKING: from . import MZMWorld -# TODO: Split regions up into sub-regions based on shared-access logic rules - -def create_regions(world: MZMWorld): - # create all regions and populate with locations - menu = Region("Menu", world.player, world.multiworld) - world.multiworld.regions.append(menu) - - brinstar = Region("Brinstar", world.player, world.multiworld) - brinstar.add_locations(brinstar_location_table, MZMLocation) - world.multiworld.regions.append(brinstar) - - kraid = Region("Kraid", world.player, world.multiworld) - kraid.add_locations(kraid_location_table, MZMLocation) - world.multiworld.regions.append(kraid) - - norfair = Region("Norfair", world.player, world.multiworld) - norfair.add_locations(norfair_location_table, MZMLocation) - world.multiworld.regions.append(norfair) - - ridley = Region("Ridley", world.player, world.multiworld) - ridley.add_locations(ridley_location_table, MZMLocation) - world.multiworld.regions.append(ridley) - - tourian = Region("Tourian", world.player, world.multiworld) - tourian.add_locations(tourian_location_table, MZMLocation) - world.multiworld.regions.append(tourian) - - crateria = Region("Crateria", world.player, world.multiworld) - crateria.add_locations(crateria_location_table, MZMLocation) - world.multiworld.regions.append(crateria) - - chozodia = Region("Chozodia", world.player, world.multiworld) - chozodia.add_locations(chozodia_location_table, MZMLocation) - world.multiworld.regions.append(chozodia) - - mission_complete = Region("Mission Complete", world.player, world.multiworld) - world.multiworld.regions.append(mission_complete) - - menu.connect(brinstar) - - brinstar.connect(norfair, "Brinstar-Norfair elevator", - lambda state: logic.can_bomb_block(state, world.player)) - - brinstar.connect(kraid, "Brinstar-Kraid elevator", - lambda state: logic.can_bomb_block(state, world.player) or state.has("Screw Attack", world.player)) - - # this works for now. it's kind of tricky, cause all you need just to get there is PBs and bombs, - # but to actually do anything (including get to ship) you need IBJ/speed/sj. it only checks for speed - # since the only thing you'd potentially need this entrance for is Landing Site Ballspark - brinstar.connect(crateria, "Brinstar-Crateria ball cannon", lambda state: ( - logic.has_power_bombs(state, world.player) - and logic.can_ballcannon(state, world.player) - and logic.can_hj_sj_ibj_or_grip(state, world.player) - and state.has("Speed Booster", world.player) - )) - - brinstar.connect(tourian, "Brinstar-Tourian elevator", - lambda state: state.has_all({"Kraid Defeated", "Ridley Defeated"}, world.player)) - - norfair.connect(crateria, "Norfair-Crateria elevator", - lambda state: logic.can_long_beam(state, world.player)) - - norfair.connect(ridley, "Norfair-Ridley elevator", lambda state: ( - ((logic.norfair_to_save_behind_hijump(state, world.player) - and logic.has_missile_count(state, world.player, 4) - and state.has_all({"Wave Beam", "Speed Booster"}, world.player) - ) - or logic.norfair_shortcut(state, world.player)) - and (logic.has_missile_count(state, world.player, 6) - or logic.has_power_bombs(state, world.player)) - )) - - crateria.connect(chozodia, "Crateria-Chozodia Upper Door", lambda state: ( - logic.has_power_bombs(state, world.player) - and (logic.can_ibj(state, world.player) - or logic.can_space_jump(state, world.player) - or (state.has("Speed Booster", world.player) and logic.can_walljump(state, world.player))) - and (state.has("Mother Brain Defeated", world.player) or not world.options.chozodia_access.value))) - - crateria.connect(chozodia, "Crateria-Chozodia Lower Door", lambda state: ( - logic.has_power_bombs(state, world.player) - and (state.has("Mother Brain Defeated", world.player) or not world.options.chozodia_access.value))) +def create_region(world: MultiWorld, player: int, region_name: str): + region = Region(region_name, player, world) + + for location_name, location_data in full_location_table.items(): + if location_data.region == region_name: + location = Location(player, location_name, location_data.code, region) + location.game = world.game.get(player) + region.locations.append(location) + + world.regions.append(region) + + +def connect(world: MultiWorld, player: int, entrance_name: str, source: str, target: str, + rule: Optional[Requirement] = None): + + source_region = world.get_region(source, player) + target_region = world.get_region(target, player) + source_region.connect(target_region, entrance_name, rule) + + +def create_regions_and_connections(world: MZMWorld): + player = world.player + multiworld = world.multiworld + + create_region(multiworld, player, "Menu") + create_region(multiworld, player, "Brinstar Start") + create_region(multiworld, player, "Brinstar Main") + create_region(multiworld, player, "Brinstar Top") + create_region(multiworld, player, "Brinstar Past Hives") + create_region(multiworld, player, "Kraid Main") + create_region(multiworld, player, "Kraid Acid Worm Area") + create_region(multiworld, player, "Kraid Left Shaft") + create_region(multiworld, player, "Kraid Bottom") + create_region(multiworld, player, "Norfair Main") + create_region(multiworld, player, "Norfair Right Shaft") + create_region(multiworld, player, "Norfair Upper Right Shaft") + create_region(multiworld, player, "Norfair Behind Ice Beam") + create_region(multiworld, player, "Norfair Lower Right Shaft") + create_region(multiworld, player, "Lower Norfair") + create_region(multiworld, player, "Norfair Screw Attack Area") + create_region(multiworld, player, "Norfair Behind Super Door") + create_region(multiworld, player, "Norfair Bottom") + create_region(multiworld, player, "Ridley Main") + create_region(multiworld, player, "Ridley Left Shaft") + create_region(multiworld, player, "Ridley SW Puzzle") + create_region(multiworld, player, "Ridley Right Shaft") + create_region(multiworld, player, "Ridley Speed Puzzles") + create_region(multiworld, player, "Central Ridley") + create_region(multiworld, player, "Ridley Room") + create_region(multiworld, player, "Tourian") + create_region(multiworld, player, "Crateria") + create_region(multiworld, player, "Upper Crateria") + create_region(multiworld, player, "Chozodia Ruins") + create_region(multiworld, player, "Chozodia Ruins Test Area") + create_region(multiworld, player, "Chozodia Glass Tube") + create_region(multiworld, player, "Chozodia Under Tube") + create_region(multiworld, player, "Chozodia Mothership Central") + create_region(multiworld, player, "Chozodia Mothership Cockpit") + create_region(multiworld, player, "Chozodia Original Power Bomb Room") + create_region(multiworld, player, "Chozodia Mecha Ridley Hallway") + create_region(multiworld, player, "Mission Accomplished!") + + connect(multiworld, player, "Game Start", "Menu", "Brinstar Start") + connect(multiworld, player, "Brinstar Start -> Main Shaft", "Brinstar Start", "Brinstar Main", MorphBall.create_rule(world)) + connect(multiworld, player, "Brinstar Main -> Brinstar Top", "Brinstar Main", "Brinstar Top", brinstar_main_to_brinstar_top().create_rule(world)) + connect(multiworld, player, "Brinstar Main -> Past Hives", "Brinstar Main", "Brinstar Past Hives", brinstar_past_hives().create_rule(world)) + connect(multiworld, player, "Brinstar Past Hives -> Top", "Brinstar Past Hives", "Brinstar Top", brinstar_pasthives_to_brinstar_top().create_rule(world)) + connect(multiworld, player, "Brinstar Top -> Past Hives", "Brinstar Top", "Brinstar Past Hives", CanEnterMediumMorphTunnel.create_rule(world)) + connect(multiworld, player, "Brinstar -> Kraid Elevator", "Brinstar Start", "Kraid Main", CanSingleBombBlock.create_rule(world)) + connect(multiworld, player, "Brinstar -> Norfair Elevator", "Brinstar Main", "Norfair Main", CanBombTunnelBlock.create_rule(world)) + connect(multiworld, player, "Brinstar -> Tourian Elevator", "Brinstar Main", "Tourian", all(MorphBall, KraidBoss, RidleyBoss).create_rule(world)) + connect(multiworld, player, "Brinstar -> Crateria Ballcannon", "Brinstar Start", "Upper Crateria", brinstar_crateria_ballcannon().create_rule(world)) + connect(multiworld, player, "Kraid Main -> Acid Worm Area", "Kraid Main", "Kraid Acid Worm Area", kraid_upper_right().create_rule(world)) + connect(multiworld, player, "Kraid Main -> Left Shaft", "Kraid Main", "Kraid Left Shaft", kraid_left_shaft_access().create_rule(world)) + connect(multiworld, player, "Kraid Left Shaft -> Bottom", "Kraid Left Shaft", "Kraid Bottom", kraid_left_shaft_to_bottom().create_rule(world)) + connect(multiworld, player, "Kraid -> Lower Norfair Shortcut", "Kraid Bottom", "Lower Norfair", kraid_bottom_to_lower_norfair().create_rule(world)) + connect(multiworld, player, "Norfair -> Crateria Elevator", "Norfair Main", "Crateria", norfair_main_to_crateria().create_rule(world)) + connect(multiworld, player, "Norfair Elevator -> Right Shaft", "Norfair Main", "Norfair Right Shaft", norfair_right_shaft_access().create_rule(world)) + connect(multiworld, player, "Norfair Right Shaft -> Upper", "Norfair Right Shaft", "Norfair Upper Right Shaft", norfair_upper_right_shaft().create_rule(world)) + connect(multiworld, player, "Norfair Upper Right -> Behind Ice Beam", "Norfair Upper Right Shaft", "Norfair Behind Ice Beam", norfair_behind_ice_beam().create_rule(world)) + connect(multiworld, player, "Norfair Ridley Shortcut", "Norfair Behind Ice Beam", "Norfair Bottom", norfair_behind_ice_to_bottom().create_rule(world)) + connect(multiworld, player, "Norfair Right Shaft -> Lower Shaft", "Norfair Right Shaft", "Norfair Lower Right Shaft", norfair_lower_right_shaft().create_rule(world)) + connect(multiworld, player, "Norfair Right Shaft -> Lower Norfair", "Norfair Lower Right Shaft", "Lower Norfair", norfair_lower_right_shaft_to_lower_norfair().create_rule(world)) + connect(multiworld, player, "Lower Norfair -> Screw Attack", "Lower Norfair", "Norfair Screw Attack Area", lower_norfair_to_screwattack().create_rule(world)) + connect(multiworld, player, "Lower Norfair -> Behind Super Missile Door", "Lower Norfair", "Norfair Behind Super Door", lower_norfair_to_spaceboost_room().create_rule(world)) + connect(multiworld, player, "Lower Norfair -> Kraid", "Lower Norfair", "Kraid Bottom", lower_norfair_to_kraid().create_rule(world)) + connect(multiworld, player, "Lower Norfair -> Bottom", "Lower Norfair", "Norfair Bottom", lower_norfair_to_bottom_norfair().create_rule(world)) + connect(multiworld, player, "Norfair -> Ridley Elevator", "Norfair Bottom", "Ridley Main", bottom_norfair_to_ridley().create_rule(world)) + connect(multiworld, player, "Norfair Bottom -> Screw Attack", "Norfair Bottom", "Norfair Screw Attack Area", bottom_norfair_to_screw().create_rule(world)) + connect(multiworld, player, "Norfair Screw Attack -> Lower Norfair", "Norfair Screw Attack Area", "Lower Norfair", screw_to_lower_norfair().create_rule(world)) + connect(multiworld, player, "Ridley Elevator -> Left Shaft", "Ridley Main", "Ridley Left Shaft", ridley_main_to_left_shaft().create_rule(world)) + connect(multiworld, player, "Ridley Elevator -> Right Shaft Shortcut", "Ridley Main", "Ridley Right Shaft", ridley_main_to_right_shaft().create_rule(world)) + connect(multiworld, player, "Ridley Left Shaft -> SW Puzzle", "Ridley Left Shaft", "Ridley SW Puzzle", ridley_left_shaft_to_sw_puzzle().create_rule(world)) + connect(multiworld, player, "Ridley Left Shaft -> Right Shaft", "Ridley Left Shaft", "Ridley Right Shaft") + connect(multiworld, player, "Ridley Right Shaft -> Speed Puzzles", "Ridley Right Shaft", "Ridley Speed Puzzles", ridley_speed_puzzles_access().create_rule(world)) + connect(multiworld, player, "Ridley Right Shaft -> Central", "Ridley Right Shaft", "Central Ridley", ridley_right_shaft_to_central().create_rule(world)) + connect(multiworld, player, "Ridley Central -> Ridley's Room", "Central Ridley", "Ridley Room", ridley_central_to_ridley_room().create_rule(world)) + connect(multiworld, player, "Tourian Escape -> Chozodia", "Tourian", "Chozodia Ruins Test Area", MotherBrainBoss.create_rule(world)) + connect(multiworld, player, "Crateria -> Upper", "Crateria", "Upper Crateria", crateria_main_to_crateria_upper().create_rule(world)) + connect(multiworld, player, "Crateria -> Chozodia Lower Door", "Crateria", "Chozodia Under Tube", crateria_to_under_tube().create_rule(world)) + connect(multiworld, player, "Crateria -> Chozodia Upper Door", "Upper Crateria", "Chozodia Ruins", crateria_upper_to_chozo_ruins().create_rule(world)) + connect(multiworld, player, "Chozo Ruins -> Chozo Ruins Test", "Chozodia Ruins", "Chozodia Ruins Test Area", chozo_ruins_to_ruins_test().create_rule(world)) + connect(multiworld, player, "Chozo Ruins Test -> Chozo Ruins", "Chozodia Ruins Test Area", "Chozodia Ruins", ChozoGhostBoss.create_rule(world)) + connect(multiworld, player, "Chozo Ruins -> Glass Tube", "Chozodia Ruins", "Chozodia Glass Tube", chozo_ruins_to_chozodia_tube().create_rule(world)) + connect(multiworld, player, "Chozodia Under Tube -> Crateria", "Chozodia Under Tube", "Crateria", under_tube_to_crateria().create_rule(world)) + connect(multiworld, player, "Chozodia Under Tube -> Glass Tube", "Chozodia Under Tube", "Chozodia Glass Tube", under_tube_to_tube().create_rule(world)) + connect(multiworld, player, "Chozodia Glass Tube -> Under Tube", "Chozodia Glass Tube", "Chozodia Under Tube", tube_to_under_tube().create_rule(world)) + connect(multiworld, player, "Chozodia Glass Tube -> Chozo Ruins", "Chozodia Glass Tube", "Chozodia Ruins", chozodia_tube_to_chozo_ruins().create_rule(world)) + connect(multiworld, player, "Chozozia Glass Tube -> Mothership Central", "Chozodia Glass Tube", "Chozodia Mothership Central", chozodia_tube_to_mothership_central().create_rule(world)) + connect(multiworld, player, "Chozodia Mothership -> Cockpit", "Chozodia Mothership Central", "Chozodia Mothership Cockpit", mothership_central_to_cockpit().create_rule(world)) + connect(multiworld, player, "Chozodia Cockpit -> Original PB", "Chozodia Mothership Cockpit", "Chozodia Original Power Bomb Room", cockpit_to_original_pb().create_rule(world)) + connect(multiworld, player, "Chozodia Cockpit -> Mecha Ridley", "Chozodia Mothership Cockpit", "Chozodia Mecha Ridley Hallway", cockpit_to_mecha_ridley().create_rule(world)) diff --git a/rules.py b/rules.py index c0ed7e3..1007092 100644 --- a/rules.py +++ b/rules.py @@ -6,572 +6,830 @@ from __future__ import annotations from typing import TYPE_CHECKING + +from .logic import * from worlds.generic.Rules import add_rule -from . import logic if TYPE_CHECKING: from . import MZMWorld -# Logic pass 1. Probably scuffed in edge cases but it seems to work. -# TODO: prep for potential elevator/start rando by re-writing logic to not assume vanilla regional access rules -def set_rules(world: MZMWorld, locations): - player = world.player +brinstar_start = { + "Brinstar Morph Ball": None, + "Brinstar Morph Ball Cannon": CanBallCannon, + "Brinstar Ceiling E-Tank": any( + all( + IceBeam, + RidleyBoss + ), + CanFly, + all( + MorphBall, + CanTrickySparks + ) + ), + } - brinstar_access_rules = { - "Brinstar Morph Ball Cannon": lambda state: logic.can_regular_bomb(state, player), - "Brinstar Long Beam": lambda state: (state.has("Morph Ball", player) - and (logic.can_long_beam(state, player) - or world.options.layout_patches.value)), - "Brinstar Ceiling E-Tank": - lambda state: (state.has("Ice Beam", player) and state.has("Ridley Defeated", player)) or - logic.can_space_jump(state, player) - or logic.can_ibj(state, player), - "Brinstar Missile Above Super": - lambda state: (logic.can_bomb_block(state, player) - and (logic.can_space_jump(state, player) - or state.has("Ice Beam", player) - or state.has_all({"Hi-Jump", "Power Grip"}, player) - or logic.can_ibj(state, player) - or logic.can_walljump(state, player) - ) - ), - "Brinstar Super Missile": lambda state: logic.can_ballspark(state, player), - "Brinstar Top Missile": - lambda state: (state.has_all({"Morph Ball", "Power Grip"}, player) - and (state.has("Ice Beam", player) - or logic.can_space_jump(state, player) - or logic.can_walljump(state, player)) - ) - or logic.can_ibj(state, player), # needs a rewrite - "Brinstar Speed Booster Shortcut Missile": - lambda state: (logic.can_bomb_block(state, player) - and logic.can_ballspark(state, player) - and (logic.can_walljump(state, player) or logic.can_hj_sj_ibj_or_grip(state, player)) - ), - "Brinstar Varia Suit": - lambda state: (logic.can_ibj(state, player) - or logic.can_space_jump(state, player) - or state.has_all({"Power Grip", "Hi-Jump"}, player)) - and (logic.can_regular_bomb(state, player) - or state.has_all({"Morph Ball", "Hi-Jump"}, player)) - and ((logic.can_ibj(state, player) - or state.has("Power Grip", player) - or (state.has("Hi-Jump", player) - and (logic.can_walljump(state, player) - or logic.can_gravity_suit(state, player)) - ) - ) - and logic.can_bomb_block(state, player) - ), - "Brinstar Worm drop": - lambda state: (state.has("Morph Ball", player) - and logic.has_missiles(state, player)), - "Brinstar Acid near Varia": - lambda state: ((logic.can_ibj(state, player) - or logic.can_space_jump(state, player) - or state.has_all({"Power Grip", "Hi-Jump"}, player)) - and (logic.can_regular_bomb(state, player) - or state.has_all({"Morph Ball", "Hi-Jump"}, player)) - and (logic.can_long_beam(state, player) - and (logic.hellrun(state, player, 2) - or state.has("Varia Suit", player) - or logic.can_gravity_suit(state, player) - ) - ) - ), - "Brinstar First Missile": lambda state: state.has("Morph Ball", player), - "Brinstar Behind Hive": - lambda state: (state.has("Morph Ball", player) - and logic.has_missile_count(state, player, 5)), - "Brinstar Under Bridge": - lambda state: (logic.has_missiles(state, player) - and logic.can_bomb_block(state, player)), - "Brinstar Post-Hive Missile": lambda state: logic.brinstar_past_hives(state, player), - "Brinstar Upper Pillar Missile": - lambda state: ((logic.can_bomb_block(state, player) and state.has_any({"Bomb", "Hi-Jump"}, player)) - or (logic.brinstar_past_hives(state, player) - and (logic.can_ibj(state, player) - or (logic.can_space_jump(state, player) - and state.has_any({"Bomb", "Hi-Jump"}, player)) - or (logic.can_walljump(state, player) - and state.has_all({"Hi-Jump", "Ice Beam"}, player))) - ) - ), - "Brinstar Behind Bombs": - lambda state: (logic.brinstar_past_hives(state, player) - and (logic.can_bomb_block(state, player) - and state.has_any({"Bomb", "Hi-Jump"}, player)) - ), - "Brinstar Bomb": - lambda state: logic.brinstar_past_hives(state, player), - "Brinstar Post-Hive E-Tank": - lambda state: logic.brinstar_past_hives(state, player) - } - - # TODO: add norfair-kraid backdoor logic - kraid_access_rules = { - "Kraid Giant Hoppers Missile": lambda state: ( - logic.kraid_left_shaft_access(state, player) - and (logic.can_ibj(state, player) - or state.has_all({"Morph Ball", "Power Grip"}, player)) - ), - "Kraid Save Room Missile": lambda state: logic.can_bomb_block(state, player), - "Kraid Crumble Block Missile": lambda state: ( - logic.kraid_upper_right(state, player) - and state.has("Morph Ball", player) - and state.has_any({"Bomb", "Hi-Jump"}, player) - ), - "Kraid Quad Ball Cannon Room": lambda state: ( # there are trickier ways to add later - logic.kraid_left_shaft_access(state, player) - and logic.kraid_upper_right(state, player) - and logic.has_missiles(state, player) - ), - "Kraid Space Jump/Unknown Item 2": lambda state: ( - logic.kraid_left_shaft_access(state, player) - and (state.has("Bomb", player) or logic.power_bomb_count(state, player, 2)) - and (state.has_any({"Power Grip", "Hi-Jump"}, player) or logic.can_ibj(state, player)) - and logic.has_missiles(state, player) # to get out - ), - "Kraid Acid Ballspark": lambda state: ( - (logic.can_ibj(state, player) or state.has_any({"Power Grip", "Hi-Jump"}, player)) - and logic.can_bomb_block(state, player) - and (logic.can_regular_bomb(state, player) or state.has("Hi-Jump", player)) - and (logic.can_gravity_suit(state, player) and logic.can_ballspark(state, player)) - ), - "Kraid Speed Booster": lambda state: state.has("Kraid Defeated", player), - "Kraid Worm Missile": lambda state: ( - logic.kraid_upper_right(state, player) - and logic.has_missile_count(state, player, 20) - and logic.can_bomb_block(state, player) - and (logic.can_hj_sj_ibj_or_grip(state, player) or logic.can_walljump(state, player)) - ), - "Kraid Pillar Missile": lambda state: logic.has_missiles(state, player), - "Kraid Acid Fall": lambda state: ( - state.can_reach("Kraid Space Jump/Unknown Item 2", "Location", player) - and state.has("Morph Ball", player) - ), - "Kraid Worm E-Tank": lambda state: logic.kraid_upper_right(state, player), - "Kraid Speed Jump": lambda state: (logic.has_missiles(state, player) - and state.has("Speed Booster", player)), - "Kraid Upper Right Morph Ball Cannon": lambda state: logic.has_missiles(state, player) - and logic.can_ballcannon(state, player), - "Kraid": lambda state: ( - state.can_reach("Kraid Space Jump/Unknown Item 2", "Location", player) - and logic.has_missile_count(state, player, 20) - and (state.count("Energy Tank", player) >= 1) - and (state.has_all({"Hi-Jump", "Power Grip"}, player) - or state.has("Speed Booster", player) - or logic.can_space_jump(state, player) - or logic.can_ibj(state, player) - or logic.can_walljump(state, player)) - ) +brinstar_main = { + "Brinstar Long Beam": all( + MorphBall, + any( + CanLongBeam, + LayoutPatches, + ) + ), + "Brinstar Main Shaft Left Alcove": all( + CanSingleBombBlock, + any( + CanFlyWall, + IceBeam, + CanHiGrip + ) + ), + "Brinstar Ballspark": CanBallspark, + "Brinstar Ripper Climb": any( + all( + CanEnterHighMorphTunnel, + any( + IceBeam, + CanFlyWall + ) + ), + CanIBJ, + all( + CanBallspark, + CanTrickySparks, + AdvancedLogic # mzmr says this is doable but i might make it require screw + space, it's so hard + ) + ), + "Brinstar Speed Booster Shortcut": all( + any( + CanBallspark, + all( + AdvancedLogic, + CanBallJump + ) + ), + CanBombTunnelBlock, + CanVerticalWall, + ), + "Brinstar Worm drop": all( + MorphBall, + Missiles + ), + "Brinstar First Missile": MorphBall, + "Brinstar Behind Hive": all( + MorphBall, + Missiles), + "Brinstar Under Bridge": all( + Missiles, + CanSingleBombBlock + ), } - norfair_access_rules = { - "Norfair Lava Power Bomb": lambda state: ( - logic.norfair_to_save_behind_hijump(state, player) - and logic.has_missile_count(state, player, 5) - and logic.can_gravity_suit(state, player) - and (logic.can_space_jump(state, player) or logic.can_ibj(state, player)) - ), - "Norfair Lava Missile": lambda state: ( - logic.norfair_to_save_behind_hijump(state, player) - and logic.has_missile_count(state, player, 3) - and (logic.can_bomb_block(state, player) or state.has("Wave Beam", player)) - and (logic.can_gravity_suit(state, player) - or (state.has("Varia Suit", player) and logic.hellrun(state, player, 5)) - or logic.hellrun(state, player, 9)) - and (logic.can_ibj(state, player) or logic.can_space_jump(state, player) - or logic.can_walljump(state, player) or state.has_any({"Hi-Jump", "Power Grip"}, player)) - ), - "Norfair Screw Attack": lambda state: ( # there's an obnoxious enemy freeze you can do here too but ehhh - logic.norfair_to_save_behind_hijump(state, player) - and (logic.can_tricky_sparks(state, player) - or (state.has("Screw Attack", player) - and (logic.can_space_jump(state, player) - or logic.can_walljump(state, player))) - or (logic.has_missile_count(state, player, 5) - and (logic.can_walljump(state, player) - or logic.can_ibj(state, player) - or logic.can_space_jump(state, player)) - ) - ) - and (logic.can_hj_sj_ibj_or_grip(state, player) or state.has("Ice Beam", player)) - ), - "Norfair Screw Attack Missile": lambda state: ( - logic.norfair_to_save_behind_hijump(state, player) - and state.has("Screw Attack", player) - and (logic.can_tricky_sparks(state, player) - or logic.can_space_jump(state, player) - or logic.can_walljump(state, player) - or (logic.has_missile_count(state, player, 5) - and logic.can_ibj(state, player))) - and (logic.can_hj_sj_ibj_or_grip(state, player) or state.has("Ice Beam", player)) - ), - "Norfair Power Grip Missile": lambda state: ( - logic.can_ibj(state, player) - or (state.has("Power Grip", player) - or state.has_all({"Hi-Jump", "Ice Beam"}, player)) - ), - "Norfair Under Crateria Elevator": lambda state: ( - (logic.can_long_beam(state, player) - or logic.can_ballspark(state, player) - ) - and (state.has("Power Grip", player) or logic.can_ibj(state, player)) - ), - "Norfair Wave Beam": lambda state: ( - logic.norfair_to_save_behind_hijump(state, player) - and logic.has_missile_count(state, player, 4)), - "Norfair Bomb Trap": lambda state: ( - logic.norfair_lower_right_shaft(state, player) - and logic.has_super_missiles(state, player) - and (logic.can_traverse_heat(state, player) - or logic.hellrun(state, player, 4) - or (state.has("Speed Booster", player) - and logic.hellrun(state, player, 1)) - ) - and (state.has_all({"Morph Ball", "Bomb"}, player) - or (logic.can_space_jump(state, player) - and logic.has_power_bombs(state, player)) - ) - ), - "Norfair Bottom Heated Room First": lambda state: logic.norfair_to_save_behind_hijump(state, player), - "Norfair Bottom Heated Room Second": lambda state: logic.norfair_to_save_behind_hijump(state, player), - "Norfair Heated Room Under Brinstar Elevator": lambda state: ( - logic.norfair_lower_right_shaft(state, player) - and logic.has_super_missiles(state, player) - and (logic.can_traverse_heat(state, player) - or logic.hellrun(state, player, 4) - or (state.has("Speed Booster", player) and logic.hellrun(state, player, 1)) - ) - ), - "Norfair Space Boost Missile": lambda state: ( # TODO may need to rename, and double check this later - logic.norfair_to_save_behind_hijump(state, player) - and logic.has_super_missiles(state, player) - and (state.has("Speed Booster", player) - or (logic.can_bomb_block(state, player) - or (state.has_all({"Long Beam", "Wave Beam"}, player)) - and (state.has("Power Grip", player) - or (logic.can_gravity_suit(state, player) and state.has("Hi-Jump", player))) - ) - ) - and (logic.can_hj_sj_ibj_or_grip(state, player) or logic.can_walljump(state, player)) - and ((logic.can_ibj(state, player) and logic.can_gravity_suit(state, player)) - or (logic.can_space_jump(state, player) and state.has("Power Grip", player)) - or (state.has("Ice Beam", player) and (state.has_any({"Power Grip", "Hi-Jump", "Bomb"}, player))) - ) - ), - "Norfair Space Boost Super Missile": lambda state: ( # TODO may need to rename, and double check this later - logic.norfair_to_save_behind_hijump(state, player) - and logic.has_super_missiles(state, player) - and (state.has("Speed Booster", player) - or (logic.can_bomb_block(state, player) - or (state.has_all({"Long Beam", "Wave Beam"}, player)) - and (state.has("Power Grip", player) - or (logic.can_gravity_suit(state, player) and state.has("Hi-Jump", player))) - ) - ) - and (logic.can_hj_sj_ibj_or_grip(state, player) or logic.can_walljump(state, player)) - and (logic.can_ibj(state, player) or logic.can_space_jump(state, player) - or state.has_all({"Ice Beam", "Hi-Jump"}, player) - or (logic.can_gravity_suit(state, player) and logic.can_walljump(state, player) - and state.has("Hi-Jump", player))) - ), - "Norfair Ice Beam": lambda state: (logic.norfair_upper_right_shaft(state, player)), - "Norfair Heated Room above Ice Beam": lambda state: ( - logic.norfair_upper_right_shaft(state, player) - and (logic.can_traverse_heat(state, player) or logic.hellrun(state, player, 1)) - ), - "Norfair Hi-Jump": - lambda state: logic.norfair_lower_right_shaft(state, player) and logic.has_missiles(state, player), - "Norfair Big Room": lambda state: ( - # there's also a way to do this with hi-jump, grip, a walljump, and a jump extend - state.has("Speed Booster", player) - or (logic.norfair_right_shaft_access(state, player) - and (logic.can_ibj(state, player) - or logic.can_space_jump(state, player) - or (state.has("Ice Beam", player) - and (state.has_any({"Hi-Jump", "Power Grip"}, player) - or logic.can_walljump(state, player)) - ) - ) +brinstar_top = { + "Brinstar Varia Suit": all( + any( + SpaceJump, + CanHorizontalIBJ, + CanHiGrip, + CanTrickySparks + ), + CanBallJump, + any( + CanIBJ, + PowerGrip, + all( + HiJump, + any( + CanWallJump, + GravitySuit ) + ) + ), + CanBombTunnelBlock + ), + "Brinstar Acid near Varia": all( + any( + SpaceJump, + CanHorizontalIBJ, + CanHiGrip, + CanTrickySparks + ), + CanBallJump, + CanLongBeam, + any( + VariaSuit, + GravitySuit, + Hellrun(2), + ) + ), + "Brinstar Upper Pillar": None + } + +brinstar_pasthives = { + "Brinstar Post-Hive In Wall": None, + "Brinstar Behind Bombs": all( + CanBombTunnelBlock, + CanBallJump + ), + "Brinstar Bomb": None, + "Brinstar Post-Hive Pillar": None + } + + +kraid_main = { + "Kraid Save Room Tunnel": CanBombTunnelBlock, + "Kraid Zipline Morph Jump": all( + Ziplines, + CanBallJump + ), + "Kraid Acid Ballspark": all( + any( + CanIBJ, + HiJump, + PowerGrip + ), + CanBombTunnelBlock, + CanBallJump, + GravitySuit, + CanBallspark + ), + "Kraid Right Hall Pillar": Missiles, + "Kraid Speed Jump": all( + Missiles, + SpeedBooster + ), + "Kraid Upper Right Morph Ball Cannon": all( + Missiles, + CanBallCannon + ) + } + +kraid_acidworm_area = { + "Kraid Under Acid Worm": all( + MissileCount(20), + CanSingleBombBlock, + CanVerticalWall ), - "Norfair Behind Top Chozo Statue": lambda state: logic.norfair_behind_ice_beam(state, player), - "Norfair Larva Ceiling E-tank": lambda state: ( - logic.norfair_to_save_behind_hijump(state, player) - and logic.has_missile_count(state, player, 4) - and state.has_all({"Wave Beam", "Speed Booster"}, player) - ), - "Norfair Right Shaft Lower": lambda state: ( - logic.norfair_lower_right_shaft(state, player) - and (logic.can_ibj(state, player) - or (state.has("Power Grip", player) - and (logic.can_hi_jump(state, player) or logic.can_walljump(state, player)))) - ), - "Norfair Right Shaft Bottom": lambda state: ( - logic.norfair_bottom_right_shaft(state, player) - and (logic.can_hj_sj_ibj_or_grip(state, player) or logic.can_walljump(state, player) - or state.has("Ice Beam", player)) + "Kraid Zipline Activator Room": None + } + +# past acid worm skip +kraid_left_shaft = { + "Kraid Behind Giant Hoppers": CanEnterHighMorphTunnel, + "Kraid Quad Ball Cannon Room": all( + CanBombTunnelBlock, + Ziplines, + Missiles + ), # there are some other seriously degen ways too + "Kraid Unknown Item Statue": all( + any( + Bomb, + PowerBombCount(2) + ), + any( + PowerGrip, + HiJump, + CanIBJ, + all( + IceBeam, + Bomb + ) + ), + Missiles # required for escape - covers both cases of only hijump or only grip + ) + } + +# req either unknown 2 or norfair backdoor +# 3 locations: Unknown 2 + Speed + Kraid + Acid Fall +# Connects back to Kraid main and Norfair +kraid_bottom = { + "Kraid Speed Booster": any( + KraidBoss, + SpeedBooster + ), + "Kraid Acid Fall": None, + "Kraid": all( + any( + UnknownItem2, + SpeedBooster + ), + any( + all( + MissileCount(20), + EnergyTanks(1), + ), + all( + AdvancedLogic, + MissileTanks(1) + ) + ), + any( + SpeedBooster, + CanHiGrip, + CanFlyWall + ) # to escape, or to get to the upper door if you take the speed booster exit into the room + ) + } + +norfair_main = { + "Norfair Hallway to Crateria": any( + PowerGrip, + CanIBJ, + all( + IceBeam, + CanEnterMediumMorphTunnel + ) + ), + "Norfair Under Crateria Elevator": all( + any( + CanLongBeam, + CanBallspark + ), + CanEnterHighMorphTunnel ) } - ridley_access_rules = { - "Ridley Southwest Puzzle Top": lambda state: ( - (logic.ridley_longway_right_shaft_access(state, player) - or logic.ridley_shortcut_right_shaft_access(state, player)) - and state.has("Speed Booster", player) - and (state.has("Power Grip", player) or logic.can_space_jump(state, player)) - and (state.has("Power Grip", player) or logic.has_power_bombs(state, player) - or state.has_all({"Long Beam", "Wave Beam"}, player)) - and (logic.has_missile_count(state, player, 5) - and (logic.can_walljump(state, player) or logic.can_space_jump(state, player) - or state.has("Power Grip", player))) - ), - "Ridley Southwest Puzzle Bottom": lambda state: ( - (logic.ridley_longway_right_shaft_access(state, player) - or logic.ridley_shortcut_right_shaft_access(state, player)) - and state.has("Speed Booster", player) - and (state.has("Power Grip", player) or logic.can_space_jump(state, player)) - and (state.has("Power Grip", player) or logic.has_power_bombs(state, player) - or state.has_all({"Long Beam", "Wave Beam"}, player)) - ), - "Ridley West Pillar": - lambda state: logic.ridley_longway_right_shaft_access(state, player), - "Ridley E-Tank behind Gravity": - lambda state: state.can_reach("Ridley Gravity Suit/Unknown Item 3", "Location", player), - "Ridley Gravity Suit/Unknown Item 3": lambda state: ( - logic.ridley_central_access(state, player) - and logic.has_missile_count(state, player, 40) - and (state.count("Energy Tank", player) >= 3) - and ((state.has("Ice Beam", player) and state.has_any({"Hi-Jump", "Power Grip"}, player)) - or logic.can_ibj(state, player) or logic.can_space_jump(state, player))), - "Ridley Fake Floor E-Tank": - lambda state: (logic.ridley_left_shaft_access(state, player) - and (logic.can_hj_sj_ibj_or_grip(state, player) or logic.can_walljump(state, player)) - and logic.can_bomb_block(state, player)), - "Ridley Upper Ball Cannon Puzzle": lambda state: ( - logic.ridley_central_access(state, player) - and logic.can_ballcannon(state, player) - and ((logic.can_walljump(state, player) and state.has("Power Grip", player)) - or state.has("Hi-Jump", player) or logic.can_ibj(state, player)) - ), - "Ridley Lower Ball Cannon Puzzle": lambda state: ( - logic.ridley_central_access(state, player) - and logic.can_ballcannon(state, player) - and (state.has_any({"Hi-Jump", "Bomb"}, player) or logic.can_walljump(state, player) - or logic.can_space_jump(state, player)) - ), - "Ridley Imago Super Missile": lambda state: ( - (logic.can_hj_sj_ibj_or_grip(state, player) or logic.can_walljump(state, player)) - and (logic.has_missile_count(state, player, 20) or state.has("Charge Beam", player)) - ), - "Ridley After Sidehopper Hall Upper": lambda state: logic.ridley_central_access(state, player), - "Ridley After Sidehopper Hall Lower": lambda state: logic.ridley_central_access(state, player), - "Ridley Long Hall": lambda state: ( - logic.ridley_longway_right_shaft_access(state, player) - or logic.ridley_shortcut_right_shaft_access(state, player) - ), - "Ridley Center Pillar Missile": lambda state: logic.ridley_central_access(state, player), - "Ridley Ball Room Missile": lambda state: logic.ridley_central_access(state, player), - "Ridley Ball Room Super": lambda state: ( - logic.ridley_central_access(state, player) and logic.has_super_missiles(state, player) - and (logic.can_ibj(state, player) or logic.can_walljump(state, player) - or logic.can_space_jump(state, player) or state.has_all({"Hi-Jump", "Power Grip"}, player)) - ), - "Ridley Fake Lava Missile": lambda state: ( - logic.ridley_central_access(state, player) - and (state.has("Wave Beam", player) or logic.can_bomb_block(state, player)) - and logic.can_ibj(state, player) or state.has("Power Grip", player)), - "Ridley Owl E-Tank": lambda state: logic.ridley_central_access(state, player), - "Ridley Northeast Corner Missile": lambda state: ( - logic.has_missiles(state, player) - and (logic.can_ibj(state, player) - or (state.has("Power Grip", player) and logic.has_power_bombs(state, player) - and state.has_any({"Ice Beam", "Hi-Jump"}, player))) - and (logic.can_bomb_block(state, player) or state.has("Screw Attack", player)) - ), # can also do this without ice beam using hj, grip, and walljumps - "Ridley Bomb Puzzle": lambda state: ( - (logic.ridley_longway_right_shaft_access(state, player) - or logic.ridley_shortcut_right_shaft_access(state, player)) - and state.has_all({"Speed Booster", "Bomb", "Power Grip"}, player) - and (logic.can_walljump(state, player) or state.has("Hi-Jump", player) - or logic.can_space_jump(state, player)) - ), - "Ridley Speed Jump": lambda state: ( - (logic.ridley_longway_right_shaft_access(state, player) - or logic.ridley_shortcut_right_shaft_access(state, player)) - and state.has_all({"Wave Beam", "Speed Booster"}, player) - ), - "Ridley": lambda state: state.can_reach("Ridley Gravity Suit/Unknown Item 3", "Location", player) - } - - tourian_access_rules = { - "Tourian Left of Mother Brain": lambda state: ( - state.has_all({"Chozo Ghost Defeated", "Speed Booster"}, player) - and logic.can_space_jump(state, player)), - "Tourian Under Mother Brain": lambda state: (state.has("Mother Brain Defeated", player) - and logic.has_super_missiles(state, player)), - "Mother Brain": lambda state: ( - state.has_all({"Ice Beam", "Bomb"}, player) # only bomb will unlatch Metroids - and logic.has_missile_count(state, player, 40) - and state.count("Energy Tank", player) >= 4 - and logic.can_hj_sj_ibj_or_grip(state, player) - and (state.has("Speed Booster", player) - or logic.can_space_jump(state, player) - or logic.can_ibj(state, player) - or (state.has("Hi-Jump", player) and logic.can_walljump(state, player)) - ) +norfair_right_shaft = { + "Norfair Big Room": any( + SpeedBooster, + CanFly, + all( + IceBeam, + CanVerticalWall + ), + all( # this method requires some jump extends + AdvancedLogic, + CanHiGrip, + CanWallJump + ) ) } - crateria_access_rules = { - "Crateria Landing Site Ballspark": lambda state: ( - state.has("Chozo Ghost Defeated", player) and logic.can_ballspark(state, player) - and (logic.can_gravity_suit(state, player) - or state.can_reach_entrance("Brinstar-Crateria ball cannon", player))), - "Crateria Power Grip": lambda state: ( - state.has_any({"Bomb", "Hi-Jump"}, player) and logic.can_hj_sj_ibj_or_grip(state, player)), - "Crateria Statue Water": - lambda state: state.can_reach("Crateria Plasma Beam/Unknown Item 1", "Location", player), - "Crateria Plasma Beam/Unknown Item 1": lambda state: state.has_any({"Bomb", "Hi-Jump"}, player), - "Crateria East Ballspark": lambda state: ( - logic.can_ballspark(state, player) - and (logic.can_space_jump(state, player) or logic.can_walljump(state, player)) - ), - "Crateria Northeast Corner": lambda state: ( - state.has("Speed Booster", player) - and (logic.can_space_jump(state, player) or logic.can_walljump(state, player) - or logic.can_tricky_sparks(state, player)) +norfair_upper_right = { + "Norfair Ice Beam": None, + "Norfair Heated Room above Ice Beam": any( + VariaSuit, + Hellrun(1) ) } - chozodia_access_rules = { - "Chozodia Upper Crateria Door": lambda state: ( - state.can_reach_entrance("Crateria-Chozodia Upper Door", player) - and logic.has_missiles(state, player) - and (logic.can_walljump(state, player) or logic.can_ibj(state, player) - or logic.can_space_jump(state, player)) - ), - "Chozodia Bomb Maze": lambda state: ( - (logic.can_ibj(state, player) or logic.can_ballspark(state, player) - or (state.has("Power Grip", player) - and (logic.can_walljump(state, player) or logic.can_space_jump(state, player)))) - and (state.has("Bomb", player) or state.count("Power Bomb Tank", player) >= 3) - and (state.has("Bomb", player) or state.has("Hi-Jump", player)) - and (state.has("Hi-Jump", player) or logic.can_ibj(state, player) - or (state.has("Power Grip", player) and logic.can_walljump(state, player))) - ), - "Chozodia Zoomer Maze": lambda state: ( - (logic.can_ibj(state, player) or logic.can_ballspark(state, player) - or (state.has("Power Grip", player) - and (logic.can_walljump(state, player) or logic.can_space_jump(state, player)))) - and (logic.can_ibj(state, player) - or (state.has("Hi-Jump", player) and state.has_any({"Speed Booster", "Power Grip"}, player))) - ), - "Chozodia Ruins Near Upper Crateria Door": lambda state: ( - state.can_reach_entrance("Crateria-Chozodia Upper Door", player) - and logic.has_missiles(state, player) - and (logic.can_walljump(state, player) or logic.can_ibj(state, player) - or logic.can_space_jump(state, player)) - and logic.has_power_bombs(state, player) - ), - "Chozodia Chozo Ghost Area Morph Tunnel Above Water": lambda state: ( - # The room leading to this item is inaccessible until the Chozo Ghost is defeated - state.has("Chozo Ghost Defeated", player) and state.has_any({"Hi-Jump", "Bomb"}, player) - ), - "Chozodia Chozo Ghost Area Underwater": lambda state: ( - # This item does not really exist until the Chozo Ghost is defeated - state.has("Chozo Ghost Defeated", player) - and logic.can_gravity_suit(state, player) and state.has("Speed Booster", player) - ), - "Chozodia Under Chozo Ghost Area Water": lambda state: ( - logic.chozodia_ghost_from_upper_crateria_door(state, player) - or state.has("Mother Brain Defeated", player)), - "Chozodia Glass Tube E-Tank": lambda state: ( - (logic.can_ibj(state, player) or logic.can_ballspark(state, player) - or (state.has("Power Grip", player) - and (logic.can_walljump(state, player) or logic.can_space_jump(state, player)))) - and logic.has_missile_count(state, player, 6) - and state.has("Speed Booster", player) - ), - "Chozodia Lava Super": lambda state: ( # This room is inaccessible until the Chozo Ghost is defeated - state.has("Chozo Ghost Defeated", player) - and ((logic.can_gravity_suit(state, player) - and ((state.has("Power Grip", player) and state.has_any({"Hi-Jump", "Bomb"}, player)) - or logic.can_ibj(state, player))) - or (state.has_all({"Hi-Jump", "Power Grip"}, player) - and (state.has("Varia Suit", player) - or logic.hellrun(state, player, 6)))) - and (logic.can_walljump(state, player) - or (logic.can_gravity_suit(state, player) - and (logic.can_ibj(state, player) or logic.can_space_jump(state, player)))) - ), - "Chozodia Original Power Bomb": lambda state: logic.chozodia_to_cockpit(state, player), - "Chozodia Next to Original Power Bomb": lambda state: ( - logic.chozodia_to_cockpit(state, player) - and logic.has_power_bombs(state, player) - and (logic.can_space_jump(state, player) or logic.can_ibj(state, player)) - ), - "Chozodia Glass Tube Power Bomb": lambda state: logic.chozodia_glass_tube_from_crateria_door(state, player), - "Chozodia Chozo Ghost Area Long Shinespark": lambda state: ( - # The room leading to this item is inaccessible until the Chozo Ghost is defeated - state.has_all({"Chozo Ghost Defeated", "Speed Booster"}, player) - and logic.can_gravity_suit(state, player) - ), - "Chozodia Shortcut Super": lambda state: ( - # you can also do this with screw and not need ibj/wj/sj but that's for advanced logic later - (logic.chozodia_tube_to_mothership_central(state, player) - or state.has("Chozo Ghost Defeated", player)) - and state.has_any({"Super Missile Tank", "Power Bomb Tank"}, player) - and (logic.can_ibj(state, player) or logic.can_walljump(state, player) - or logic.can_space_jump(state, player)) - ), - "Chozodia Workbot Super": lambda state: ( - (logic.chozodia_tube_to_mothership_central(state, player) - or state.has("Chozo Ghost Defeated", player)) - and logic.has_missile_count(state, player, 5) - ), - "Chozodia Mothership Ceiling Near ZSS Start": - lambda state: logic.chozodia_tube_to_mothership_central(state, player) - or state.has_all({"Chozo Ghost Defeated", "Power Bomb Tank"}, player), - "Chozodia Under Mecha Ridley Hallway": lambda state: ( - # you can also get here without PBs but we'll save that for advanced logic later - logic.chozodia_to_cockpit(state, player) - and (state.has("Power Grip", player) or logic.can_ibj(state, player)) - and state.has_all({"Power Bomb Tank", "Speed Booster"}, player) - ), - "Chozodia Southeast Corner In Hull": - lambda state: logic.chozodia_tube_to_mothership_central(state, player) - or state.has_all({"Chozo Ghost Defeated", "Power Bomb Tank"}, player), - "Chozo Ghost": lambda state: ( - state.has("Mother Brain Defeated", player) - and (logic.can_ibj(state, player) - or (logic.can_space_jump(state, player) and state.has_any({"Power Grip", "Hi-Jump"}, player))) - ), # Extra requirements needed to escape--can also do it without SJ using tight WJs and hj+grip - "Mecha Ridley": lambda state: ( - logic.chozodia_to_cockpit(state, player) - and logic.has_missile_count(state, player, 40) - and logic.has_power_bombs(state, player) # Or can skip them by flying to the tunnel - and logic.goal(state, player) - ), - "Chozodia Space Pirate's Ship": lambda state: state.has_all({"Mecha Ridley Defeated", "Plasma Beam"}, player) - } - - access_rules = { - **brinstar_access_rules, - **kraid_access_rules, - **norfair_access_rules, - **ridley_access_rules, - **tourian_access_rules, - **crateria_access_rules, - **chozodia_access_rules +norfair_behind_ice = { + "Norfair Behind Top Chozo Statue": None, + } + +norfair_lowerrightshaft = { + "Norfair Bomb Trap": all( + any( + Bomb, + all( + SpaceJump, + PowerBombs + ) + ), + CanReachLocation("Norfair Heated Room Under Brinstar Elevator") + ), + "Norfair Heated Room Under Brinstar Elevator": all( + SuperMissiles, + any( + VariaSuit, + Hellrun(4), + all( + SpeedBooster, + Hellrun(1) + ) + ) + ), + "Norfair Hi-Jump": Missiles, + "Norfair Right Shaft Near Hi-Jump": all( + CanEnterHighMorphTunnel, + any( + HiJump, + CanWallJump + ) + ) + } + +lower_norfair = { + "Norfair Lava Dive Left": all( + MissileCount(7), + GravitySuit, + CanFly + ), + "Norfair Lava Dive Right": all( + MissileCount(5), + any( + GravitySuit, + all( + VariaSuit, + Hellrun(5) + ), + Hellrun(9) + ), + any( + CanBombTunnelBlock, + WaveBeam + ), + CanVerticalWall, + ), + "Norfair Wave Beam": MissileCount(4), + "Norfair Heated Room Below Wave - Left": all( + CanVerticalWall, + any( + VariaSuit, + Hellrun(2) + ), + any( + CanIBJ, + HiJump, + PowerGrip, + all( + IceBeam, + Bomb + ) + ) + ), + "Norfair Heated Room Below Wave - Right": all( + CanVerticalWall, + any( + VariaSuit, + Hellrun(2) + ) + ), + } + +norfair_screwattack = { + "Norfair Screw Attack": None, + "Norfair Next to Screw Attack": ScrewAttack, + } + +norfair_behind_superdoor = { + "Norfair Behind Lower Super Missile Door - Left": all( + any( + all( + CanIBJ, + GravitySuit + ), + all( + SpaceJump, + PowerGrip + ), + all( + IceBeam, + any( + CanEnterMediumMorphTunnel, + Bomb + ), + CanReachLocation("Norfair Behind Lower Super Missile Door - Right"), + ) + ), + any( + SpeedBooster, + CanBallJump + ) + ), + "Norfair Behind Lower Super Missile Door - Right": any( + CanFly, + all( + HiJump, + any( + IceBeam, + all( + GravitySuit, + CanWallJump + ) + ) + ) + ) + } + +norfair_bottom = { + "Norfair Larva Ceiling": CanReachEntrance("Lower Norfair -> Bottom"), + "Norfair Right Shaft Bottom": all( + any( + CanVerticalWall, + IceBeam + ), + CanBallJump + ) + } + +ridley_main = { + "Ridley Imago Super Missile": all( + CanVerticalWall, + any( + MissileCount(20), + all( + AdvancedLogic, + MissileTanks(1) # Imago does not drop super refills + ), + ChargeBeam + ) + ) + } + +ridley_left_shaft = { + "Ridley West Pillar": None, + "Ridley Fake Floor": None, + "Ridley Long Hall": None + } + +ridley_sw_puzzle = { + "Ridley Southwest Puzzle Top": all( + MissileCount(5), + any( + CanWallJump, + PowerGrip, + SpaceJump + ) + ), + "Ridley Southwest Puzzle Bottom": None } +ridley_right_shaft = { + "Ridley Northeast Corner": any( + CanFly, + all( + AdvancedLogic, + CanWallJump, + HiJump # disable hi-jump mid walljump to get this, might be possible without + ), + all( + IceBeam, + any( + CanWallJump, + CanHiGrip + ) + ) + ) + } + +ridley_right_speed_puzzles = { + "Ridley Bomb Puzzle": all( + Bomb, + PowerGrip, + any( + CanWallJump, + SpaceJump + ) + ), + "Ridley Speed Jump": WaveBeam + } + +ridley_central = { + "Ridley Upper Ball Cannon Puzzle": all( + any( + HiJump, + all( + CanWallJump, + any( + PowerGrip, + SpaceJump + ) + ) + ), + any( + CanBallCannon, + LayoutPatches # TODO: make this layout patch + ) + ), + "Ridley Lower Ball Cannon Puzzle": all( + any( + PowerBombs, + PowerGrip, + all( + WaveBeam, + any( + CanWallJump, + SpaceJump + ) + ) + ), + any( + CanBallCannon, + all( + LayoutPatches, + any( + HiJump, + SpaceJump, + CanWallJump + ) + ) + ) + ), + "Ridley After Sidehopper Hall Upper": None, + "Ridley After Sidehopper Hall Lower": None, + "Ridley Center Pillar": None, + "Ridley Ball Room Lower": None, + "Ridley Ball Room Upper": all( + SuperMissiles, + any( + CanFlyWall, + CanHiGrip + ) + ), + "Ridley Fake Lava Under Floor": all( + any( + WaveBeam, + CanBombTunnelBlock + ), + CanEnterHighMorphTunnel + ), + "Ridley Under Owls": None, + } + +ridley_room = { + "Ridley Behind Unknown Statue": UnknownItem3, + "Ridley Unknown Item Statue": None, + "Ridley": UnknownItem3, + } + +tourian = { + "Tourian Left of Mother Brain": all( + ChozoGhostBoss, + MotherBrainBoss, + SpeedBooster, + any( + SpaceJump, + CanTrickySparks + ) + ), + "Tourian Under Mother Brain": all( + MotherBrainBoss, + SuperMissiles, + CanEnterMediumMorphTunnel # to escape + ), + "Mother Brain": all( + IceBeam, + CanRegularBomb, # only bomb can unlatch metroids + any( + AdvancedLogic, + all( + MissileCount(40), + EnergyTanks(4), + ) + ), + CanVertical, # to get through escape shaft + any( # to get to ship + SpeedBooster, + CanFly, + all( + HiJump, + CanWallJump + ) + ) + ) + } + +crateria_main = { + "Crateria Landing Site Ballspark": all( + ChozoGhostBoss, + MotherBrainBoss, + CanBallspark, + CanBallJump, + any( + GravitySuit, + CanReachEntrance("Brinstar -> Crateria Ballcannon") + ) + ), + "Crateria Moat": None + } + +crateria_upper = { + "Crateria Power Grip": CanVertical, + "Crateria Statue Water": UnknownItem1, + "Crateria Unknown Item Statue": None, + "Crateria East Ballspark": all( + CanBallspark, + any( + CanReachEntrance("Crateria -> Chozodia Upper Door"), + CanReachLocation("Crateria Northeast Corner") + ) + ), + "Crateria Northeast Corner": all( + SpeedBooster, + any( + SpaceJump, + CanWallJump, + CanTrickySparks + ) + ) + } + +chozodia_ruins_crateria_entrance = { + "Chozodia Upper Crateria Door": + CanReachEntrance("Crateria -> Chozodia Upper Door"), # Specifically need to access this entrance, not just the region as it's one-way + "Chozodia Ruins East of Upper Crateria Door": Missiles, + "Chozodia Triple Crawling Pirates": all( # Rename to Triple Crawling Pirates + any( + Bomb, + PowerBombCount(2) + ), + any( + CanHiGrip, + CanFlyWall + ), + any( + AdvancedLogic, + ChozodiaCombat + ) + ), + } + +chozodia_ruins_test = { + "Chozodia Chozo Ghost Area Morph Tunnel Above Water": all( + ChozoGhostBoss, # The room leading to this item is inaccessible until the Chozo Ghost is defeated + Missiles, + CanBallJump + ), + "Chozodia Chozo Ghost Area Underwater": all( + ChozoGhostBoss, # This item is fake until the Chozo Ghost is defeated + SpeedBooster, + GravitySuit + ), + "Chozodia Chozo Ghost Area Long Shinespark": all( + ChozoGhostBoss, # The room leading to this item is inaccessible until the Chozo Ghost is defeated + SpeedBooster, + GravitySuit + ), + "Chozodia Lava Dive": all( # TODO split this lavadive into regular/advanced? current values are close to bare minimum + ChozoGhostBoss, + any( + GravitySuit, + all( + Hellrun(4), + VariaSuit, + CanHiGrip + ), + all( + AdvancedLogic, + Hellrun(6), + CanHiGrip + ) + ), + CanEnterHighMorphTunnel, + CanBallJump, + any( + CanWallJump, + all( + GravitySuit, + CanFly + ) + ) + ), + "Chozo Ghost": None # Regional access requirements should cover what is needed + } + +chozodia_under_tube = { + "Chozodia Bomb Maze": all( + MorphBall, + any( + CanIBJ, + all( + CanBallspark, + CanTrickySparks + ), + all( + PowerGrip, + any( + CanWallJump, + SpaceJump + ) + ) + ), + any( + Bomb, + PowerBombCount(3) + ), + CanBallJump + ), + "Chozodia Zoomer Maze": any( + CanIBJ, + all( + PowerGrip, + CanBallJump + ), + all( + CanBallspark, + CanTrickySparks + ) + ), + "Chozodia Left of Glass Tube": all( + SpeedBooster, + CanReachEntrance("Chozodia Glass Tube -> Chozo Ruins") # Required to access a save station after collecting to warp if necessary + ), + "Chozodia Right of Glass Tube": all( + PowerBombs, + CanFly + ) + } + +chozodia_mothership = { + "Chozodia Pirate Pitfall Trap": all( + Missiles, + SuperMissiles, + any( + ScrewAttack, + MissileCount(5) + ), + any( + all( + CanBombTunnelBlock, + CanFlyWall + ), + all( + AdvancedLogic, # doable without falling down using screw, but can get softlocked without infinite vertical + CanSingleBombBlock + ) + ) + ), + "Chozodia Behind Workbot": MissileCount(5), + "Chozodia Ceiling Near Map Station": all( + Missiles, + any( + PowerBombs, + ScrewAttack, + MissileCount(6) + ) + ), + "Chozodia Southeast Corner In Hull": PowerBombs + } + +chozodia_pb_area = { + "Chozodia Original Power Bomb": None, + "Chozodia Next to Original Power Bomb": all( + PowerBombs, + CanFly + ) + } + +chozodia_mecha_ridley_hall = { + "Chozodia Under Mecha Ridley Hallway": SpeedBooster, + "Mecha Ridley": all( + PlasmaBeam, + any( + MissileCount(40), + all( + AdvancedLogic, + Missiles, + any( + HiJump, + CanWallJump + ) + ) + ), + CanEnterHighMorphTunnel, # To escape + ReachedGoal + ), + "Chozodia Space Pirate's Ship": MechaRidleyBoss +} + +access_rules = { + **brinstar_start, + **brinstar_main, + **brinstar_top, + **brinstar_pasthives, + **kraid_main, + **kraid_acidworm_area, + **kraid_left_shaft, + **kraid_bottom, + **norfair_main, + **norfair_right_shaft, + **norfair_upper_right, + **norfair_behind_ice, + **norfair_lowerrightshaft, + **lower_norfair, + **norfair_screwattack, + **norfair_behind_superdoor, + **norfair_bottom, + **ridley_main, + **ridley_left_shaft, + **ridley_sw_puzzle, + **ridley_right_shaft, + **ridley_right_speed_puzzles, + **ridley_central, + **ridley_room, + **tourian, + **crateria_main, + **crateria_upper, + **chozodia_ruins_crateria_entrance, + **chozodia_ruins_test, + **chozodia_under_tube, + **chozodia_mothership, + **chozodia_pb_area, + **chozodia_mecha_ridley_hall + } + + +def set_rules(world: MZMWorld, locations): + player = world.player + for i in locations: location = world.multiworld.get_location(i, player) + try: - add_rule(location, access_rules[i]) + if access_rules[i]: + add_rule(location, access_rules[i].create_rule(world)) except KeyError: continue