diff --git a/worlds/AM2R/__init__.py b/worlds/AM2R/__init__.py index e69de29bb2d1..e85717f33ce1 100644 --- a/worlds/AM2R/__init__.py +++ b/worlds/AM2R/__init__.py @@ -0,0 +1,158 @@ +import os +import sys +from typing import Dict, List, Set, Tuple, TextIO, Union, Iterable, Optional, Any + +from .items import item_table, filler_items, get_item_names_per_category +from .locations import get_location_datas, EventId +from .regions import create_regions_and_locations +from BaseClasses import Region, Entrance, Tutorial, Item, ItemClassification, MultiWorld +from .options import AM2R_options, get_option_value, is_option_enabled +from worlds.AutoWorld import World, WebWorld + + +# todo something intelligent + +class AM2RWeb(WebWorld): + theme = "partyTime" + tutorials = [ + Tutorial( + tutorial_name="Multiworld Setup Tutorial", + description="A guide to setting up the Archipelago AM2R software on your computer. This guide covers " + "single-player, multiworld, and related software.", + language="English", + file_name="AM2R_en.md", + link="AM2R/en", + authors=["Zed"] + ) + ] + + +class AM2RWorld(World): + """ + AM2R is a remake of the classic Metroid 2 game for the Game Boy that tries its best to keep the feel + of the original as well as filling in some gaps to more closely tie into Metroid Fusion and brings some + items from there as well. + """ + game = "AM2R" + data_version = 0 + web = AM2RWeb() + option_definitions = AM2R_options + + item_name_to_id = {name: data.code for name, data in item_table.items()} + location_name_to_id = {location.name: location.code for location in get_location_datas(None, None)} + item_name_groups = get_item_names_per_category() + + def __init__(self, world: MultiWorld, player: int): + super().__init__(world, player) + + def generate_early(self) -> None: + return + + def create_regions(self) -> None: + create_regions_and_locations(self.multiworld, self.player) + + def create_items(self) -> None: + self.create_and_assign_event_items() + + excluded_items: Set[str] = self.get_excluded_items() + + self.multiworld.itempool += self.get_item_pool(excluded_items) + + def set_rules(self) -> None: + victory: str + victory = "The Last Metroid is in Captivity" + self.multiworld.completion_condition[self.player] = lambda state: state.has(victory, self.player) + + def fill_slot_data(self) -> Dict[str, object]: + slot_data: Dict[str, object] = {} + + for option_name in AM2R_options: + slot_data[option_name] = self.get_option_value(option_name) + + return slot_data + + def write_spoiler_header(self, spoiler_handle: TextIO) -> None: + return # for if I ever need to write something into the + + def create_item(self, name: str) -> Item: + data = item_table[name] + + if data.useful: + classification = ItemClassification.useful + elif data.progression: + classification = ItemClassification.progression + elif data.trap: + classification = ItemClassification.trap + else: + classification = ItemClassification.filler + + item = Item(name, classification, data.code, self.player) + + if not item.advancement: + return item + + def get_filler_item_name(self) -> str: + trap_chance: int = self.get_option_value("Trap Fill Percentage") + enabled_traps: List[str] = self.get_option_value("Traps") + + if self.multiworld.random.random() < (trap_chance / 100) and enabled_traps: + return self.multiworld.random.choice(enabled_traps) + else: + return self.multiworld.random.choice(filler_items) + + def get_excluded_items(self) -> Set[str]: # Set[str] + excluded_items: Set[str] = set() + + if self.get_option_value("Metroids are Checks") < 2: + excluded_items.add("Omega Metroid") + if self.get_option_value("Metroids are Checks") < 1: + excluded_items.add("Metroid") + excluded_items.add("Omega Metroid") + + for item in self.multiworld.precollected_items[self.player]: + if item.name not in self.item_name_groups["Ammo"]: + excluded_items.add(item.name) + return excluded_items + + def place_locked_items(self, excluded_items: Set[str], location: str, item:str) -> None: + excluded_items.add(item) + + item = self.create_item(item) + + self.multiworld.get_location(location, self.player).place_locked_item(item) + + def get_item_pool(self, excluded_items: Set[str]) -> List[Item]: + pool: List[Item] = [] + + for name, data in item_table.items(): + if name not in excluded_items: + for _ in range(data.count): + item = self.create_item(name) + pool.append(item) + + for _ in range(len(self.multiworld.get_unfilled_locations(self.player)) - len(pool)): + item = self.create_item(self.get_filler_item_name()) + pool.append(item) + + return pool + + def create_and_assign_event_items(self) -> None: + for location in self.multiworld.get_locations(self.player): + if location.address == EventId: + item = Item(location.name, ItemClassification.progression, EventId, self.player) + location.place_locked_item(item) + + def get_personal_items(self) -> Dict[int, int]: + personal_items: Dict[int, int] = {} + + for location in self.multiworld.get_locations(self.player): + if location.address and location.item and location.item.code and location.item.player == self.player: + personal_items[location.address] = location.item.code + + return personal_items + + def is_option_enabled(self, option: str) -> bool: + return is_option_enabled(self.multiworld, self.player, option) + + def get_option_value(self, option: str) -> Union[int, Dict, List]: + return get_option_value(self.multiworld, self.player, option) diff --git a/worlds/AM2R/docs/README.md b/worlds/AM2R/docs/README.md new file mode 100644 index 000000000000..be2c25689aca --- /dev/null +++ b/worlds/AM2R/docs/README.md @@ -0,0 +1,3 @@ +# AM2R +## Bug reporting +If you are playing this mod and are encountering bugs **DO NOT** report them to the Archipelago server this is not offical support from them and they may or may not know what to do instead you can report them to the dedicated mod thread on the [AM2R Server](https://discord.gg/YTQnkAJ). diff --git a/worlds/AM2R/docs/sprites/BeamCores.png b/worlds/AM2R/docs/sprites/BeamCores.png deleted file mode 100644 index 2357a1e345dc..000000000000 Binary files a/worlds/AM2R/docs/sprites/BeamCores.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/Ice.png b/worlds/AM2R/docs/sprites/Ice.png deleted file mode 100644 index 9872483bf0c8..000000000000 Binary files a/worlds/AM2R/docs/sprites/Ice.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/LongBeam.gif b/worlds/AM2R/docs/sprites/LongBeam.gif deleted file mode 100644 index db9d965830ad..000000000000 Binary files a/worlds/AM2R/docs/sprites/LongBeam.gif and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/Spazer.png b/worlds/AM2R/docs/sprites/Spazer.png deleted file mode 100644 index 2be40118cf46..000000000000 Binary files a/worlds/AM2R/docs/sprites/Spazer.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/The Beam Orb.gif b/worlds/AM2R/docs/sprites/The Beam Orb.gif deleted file mode 100644 index 82fafb613a62..000000000000 Binary files a/worlds/AM2R/docs/sprites/The Beam Orb.gif and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/Wave.png b/worlds/AM2R/docs/sprites/Wave.png deleted file mode 100644 index 5a8c0dc9ad7d..000000000000 Binary files a/worlds/AM2R/docs/sprites/Wave.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/charge.png b/worlds/AM2R/docs/sprites/charge.png deleted file mode 100644 index b2619c45d245..000000000000 Binary files a/worlds/AM2R/docs/sprites/charge.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/long beam/LongBeam.png b/worlds/AM2R/docs/sprites/long beam/LongBeam.png deleted file mode 100644 index d0d0fa1f1672..000000000000 Binary files a/worlds/AM2R/docs/sprites/long beam/LongBeam.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/long beam/LongBeam1.png b/worlds/AM2R/docs/sprites/long beam/LongBeam1.png deleted file mode 100644 index d0d0fa1f1672..000000000000 Binary files a/worlds/AM2R/docs/sprites/long beam/LongBeam1.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/long beam/LongBeam2.png b/worlds/AM2R/docs/sprites/long beam/LongBeam2.png deleted file mode 100644 index 0558dd09985c..000000000000 Binary files a/worlds/AM2R/docs/sprites/long beam/LongBeam2.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/long beam/LongBeam3.png b/worlds/AM2R/docs/sprites/long beam/LongBeam3.png deleted file mode 100644 index 04d8a7a2427b..000000000000 Binary files a/worlds/AM2R/docs/sprites/long beam/LongBeam3.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/long beam/LongBeam4.png b/worlds/AM2R/docs/sprites/long beam/LongBeam4.png deleted file mode 100644 index f0c25e0d558f..000000000000 Binary files a/worlds/AM2R/docs/sprites/long beam/LongBeam4.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/long beam/LongBeam5.png b/worlds/AM2R/docs/sprites/long beam/LongBeam5.png deleted file mode 100644 index f0c25e0d558f..000000000000 Binary files a/worlds/AM2R/docs/sprites/long beam/LongBeam5.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/long beam/LongBeam6.png b/worlds/AM2R/docs/sprites/long beam/LongBeam6.png deleted file mode 100644 index 04d8a7a2427b..000000000000 Binary files a/worlds/AM2R/docs/sprites/long beam/LongBeam6.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/long beam/LongBeam7.png b/worlds/AM2R/docs/sprites/long beam/LongBeam7.png deleted file mode 100644 index 0558dd09985c..000000000000 Binary files a/worlds/AM2R/docs/sprites/long beam/LongBeam7.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/long beam/LongBeam8.png b/worlds/AM2R/docs/sprites/long beam/LongBeam8.png deleted file mode 100644 index d0d0fa1f1672..000000000000 Binary files a/worlds/AM2R/docs/sprites/long beam/LongBeam8.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/plasma.png b/worlds/AM2R/docs/sprites/plasma.png deleted file mode 100644 index bf48e8fd05fb..000000000000 Binary files a/worlds/AM2R/docs/sprites/plasma.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/rezapS.png b/worlds/AM2R/docs/sprites/rezapS.png deleted file mode 100644 index c0749dd5f55f..000000000000 Binary files a/worlds/AM2R/docs/sprites/rezapS.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_0.png b/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_0.png deleted file mode 100644 index 24fc9cce6fb2..000000000000 Binary files a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_0.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_1.png b/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_1.png deleted file mode 100644 index 8ea3a93f9b25..000000000000 Binary files a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_1.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_2.png b/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_2.png deleted file mode 100644 index 4515f6945241..000000000000 Binary files a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_2.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_3.png b/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_3.png deleted file mode 100644 index a341694d4273..000000000000 Binary files a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_3.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_4.png b/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_4.png deleted file mode 100644 index 24fc9cce6fb2..000000000000 Binary files a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_4.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_5.png b/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_5.png deleted file mode 100644 index 312e6d3c49c2..000000000000 Binary files a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_5.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_6.png b/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_6.png deleted file mode 100644 index afba32db6fc8..000000000000 Binary files a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_6.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_7.png b/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_7.png deleted file mode 100644 index 1b5dc3c8e2dd..000000000000 Binary files a/worlds/AM2R/docs/sprites/sItemArchipelago/sItemArchipelago_7.png and /dev/null differ diff --git a/worlds/AM2R/docs/sprites/speed.png b/worlds/AM2R/docs/sprites/speed.png deleted file mode 100644 index 568cd174ded8..000000000000 Binary files a/worlds/AM2R/docs/sprites/speed.png and /dev/null differ diff --git a/worlds/AM2R/items.py b/worlds/AM2R/items.py index d5ce027eded1..7c34703cec8a 100644 --- a/worlds/AM2R/items.py +++ b/worlds/AM2R/items.py @@ -1,42 +1,58 @@ -from BaseClasses import Item, ItemClassification -import typing - - -class ItemData(typing.NamedTuple): - code: typing.Optional[int] - classification: any - - -class AM2RItem(Item): - game: str = "AM2R" - - -item_table = { - "Missile": ItemData(1, ItemClassification.filler), - "Super_Missile": ItemData(1, ItemClassification.filler), - "Power_Bomb": ItemData(1, ItemClassification.filler), - "Energy_Tank": ItemData(1, ItemClassification.filler), - # "Morph_ball": ItemData(1, ItemClassification.progression) - # "Power_Grip": ItemData(1, ItemClassification.progression) - "Bombs": ItemData(1, ItemClassification.progression), - "Spider_Ball": ItemData(1, ItemClassification.progression), - "Hi_Jump": ItemData(1, ItemClassification.progression), - "Spring_Ball": ItemData(1, ItemClassification.progression), - "Space_Jump": ItemData(1, ItemClassification.progression), - "Speed_Booster": ItemData(1, ItemClassification.progression), - "Screw_Attack": ItemData(1, ItemClassification.progression), - "Varia_Suit": ItemData(1, ItemClassification.useful), - "Gravity_Suit": ItemData(1, ItemClassification.progression), - "Charge_Beam": ItemData(1, ItemClassification.useful), - "Wave_Beam": ItemData(1, ItemClassification.useful), - "Spazer": ItemData(1, ItemClassification.useful), - "Plasma_Beam": ItemData(1, ItemClassification.useful), - "Ice_Beam": ItemData(1, ItemClassification.progression), - "Equipment_Trap": ItemData(1, ItemClassification.trap), - "Freeze_Trap": ItemData(1, ItemClassification.trap), - "Short_Beam": ItemData(1, ItemClassification.trap), - "EMP_Trap": ItemData(1, ItemClassification.trap), - "Metroid": ItemData(1, ItemClassification.progression), - "The Galaxy is at Peace": ItemData(1, ItemClassification.progression) +from typing import Dict, Set, Tuple, NamedTuple + + +class ItemData(NamedTuple): + category: str + code: int + count: int = 1 + progression: bool = False + useful: bool = False + trap: bool = False + + +item_table: Dict[str, ItemData] = { + "Missile": ItemData("Ammo", 8678000, 44), + "Super Missile": ItemData("Ammo", 8678001, 10), + "Power Bomb": ItemData("Ammo", 8678002, 10), + "Energy Tank": ItemData("Ammo", 8678003, 10), + # "Arm Cannon": ItemData("Equipment", 8678004, progression=True), + # "Morph Ball": ItemData("Equipment", 8678005, progression=True), + # "Power Grip": ItemData("Equipment", 8678006, progression=True), + "Bombs": ItemData("Equipment", 8678007, progression=True), + "Spider Ball": ItemData("Equipment", 8678008, progression=True), + "Hi Jump": ItemData("Equipment", 8678009, progression=True), + "Spring Ball": ItemData("Equipment", 8678010, progression=True), + "Space Jump": ItemData("Equipment", 8678011, progression=True), + "Speed Booster": ItemData("Equipment", 8678012, progression=True), + "Screw Attack": ItemData("Equipment", 8678013, progression=True), + "Varia Suit": ItemData("Equipment", 8678014, useful=True), + "Gravity Suit": ItemData("Equipment", 8678015, progression=True), + "Charge Beam": ItemData("Beam", 8678016, useful=True), + "Wave Beam": ItemData("Beam", 8678017, useful=True), + "Spazer": ItemData("Beam", 8678018, useful=True), + "Plasma Beam": ItemData("Beam", 8678019, useful=True), + "Ice Beam": ItemData("Beam", 8678020, progression=True), + "Equipment Trap": ItemData("Trap", 8678021, 0, trap=True), + "Freeze Trap": ItemData("Trap", 8678022, 0, trap=True), + "Short Beam": ItemData("Trap", 8678023, 0, trap=True), + "EMP Trap": ItemData("Trap", 8678024, 0, trap=True), + "Metroid": ItemData("Metroid", 8678025, progression=True), + "Omega Metroid": ItemData("Metroid", 8678026, progression=True), + # "The Baby": ItemData("Tiny", 8678027), } +filler_items: Tuple[str, ...] = ( + "Missile", + "Super Missile", + "Power Bomb", + "Energy Tank" +) + + +def get_item_names_per_category() -> Dict[str, Set[str]]: + categories: Dict[str, Set[str]] = {} + + for name, data in item_table.items(): + categories.setdefault(data.category, set()).add(name) + + return categories diff --git a/worlds/AM2R/locations.py b/worlds/AM2R/locations.py index 03f3a6aab719..30cbfb07dd52 100644 --- a/worlds/AM2R/locations.py +++ b/worlds/AM2R/locations.py @@ -1,120 +1,194 @@ -from BaseClasses import Location -import typing +from typing import List, Tuple, Optional, Callable, NamedTuple +from BaseClasses import MultiWorld, CollectionState +from .options import is_option_enabled +from .rules import AM2RLogic -class LocationData(typing.NamedTuple): - id: typing.Optional[int] +EventId: Optional[int] = None + + +class LocationData(NamedTuple): region: str + name: str + code: Optional[int] + rule: Callable[[CollectionState], bool] = lambda state: True + +def get_location_datas(world: Optional[MultiWorld], player: Optional[int]): + location_table = [LocationData, ...] + logic = AM2RLogic(world, player) + + location_table: List[LocationData] = [ + LocationData("Main Caves", "Main Caves: Vertical Spike Room Upper", 8680000, lambda state: state.has("Spider Ball", player) and logic.AM2R_can_bomb(state)), # spider + bomb + LocationData("Main Caves", "Main Caves: Vertical Spike Room Lower", 8680001, logic.AM2R_can_bomb), # bomb + LocationData("Main Caves", "Main Caves: Crumble Spike Room", 8680002, logic.AM2R_can_jump), # jump + LocationData("Main Caves", "Main Caves: Maze", 8680003), + LocationData("Main Caves", "Main Caves: Shinespark Before the drop", 8680004, lambda state: state.has("Speed Booster", player)), # speed + LocationData("Main Caves", "Main Caves: Shinespark After the drop", 8680005, lambda state: state.has("Speed Booster", player)), # speed + + LocationData("Golden Temple", "Golden Temple: Bombs", 8680006), + LocationData("Golden Temple", "Golden Temple: Missile below Bombs", 8680007, logic.AM2R_can_bomb), # bomb + LocationData("Golden Temple", "Golden Temple: Hidden Energy Tank", 8680008, logic.AM2R_can_bomb), # bomb + LocationData("Golden Temple", "Golden Temple: Charge Beam", 8680009), + LocationData("Golden Temple", "Golden Temple: Armory 1", 8680010), + LocationData("Golden Temple", "Golden Temple: Armory 2", 8680011), + LocationData("Golden Temple", "Golden Temple: Armory 3", 8680012), + LocationData("Golden Temple", "Golden Temple: Armory Missile False Wall ", 8680013, logic.AM2R_can_bomb), # bomb + LocationData("Golden Temple", "Golden Temple: Puzzle Room Missle 1", 8680014), + LocationData("Golden Temple", "Golden Temple: Puzzle Room Missle 2", 8680015), + LocationData("Golden Temple", "Golden Temple: Puzzle Room Energy Tank", 8680016), + LocationData("Golden Temple", "Golden Temple: Spider Ball", 8680017), + LocationData("Golden Temple", "Golden Temple: Celling Missile", 8680018, lambda state: state.has("Speed Booster", player) or logic.AM2R_can_spider), # canspider + LocationData("Golden Temple", "Golden Temple: EMP room", 8680019, lambda state: state.has("Super Missile", player) and logic.AM2R_has_ballspark), # super + ballspark + + LocationData("Guardian", "Guardian: Up Above", 8680020, lambda state: logic.AM2R_can_bomb(state) and logic.AM2R_can_schmove(state)), # bomb + schmove + LocationData("Guardian", "Guardian: Behind The Door", 8680021, lambda state: state.has("Power Bomb", player) and logic.AM2R_can_schmove(state)), # PB + schmove + + LocationData("Hydro Station", "Hydro Station: Clif", 8680022), + LocationData("Hydro Station", "Hydro Station: Morph Tunnel", 8680023), + LocationData("Hydro Station", "Hydro Station: Turbine Room", 8680024, logic.AM2R_can_bomb), # bomb + LocationData("Hydro Station", "Hydro Station: Not so Secret Tunel", 8680025, logic.AM2R_can_schmove), # schmove + LocationData("Hydro Station", "Hydro Station: Water puzzle Beside Varia", 8680026, logic.AM2R_can_bomb), # bomb + LocationData("Hydro Station", "Hydro Station: Varia Suit", 8680027, logic.AM2R_can_bomb), # bomb + LocationData("Hydro Station", "Hydro Station: EMP room", 8680028, lambda state: state.has("Super Missile", player) and state.has("Speed Booster", player)), # super + speed + + LocationData("Arachnus", "Arachnus: Boss", 8680029), + + LocationData("Inner Hydro Station", "Hydro Station: Wave Beam", 8680030, logic.AM2R_can_bomb), + LocationData("Inner Hydro Station", "Hydro Station: Below Tower Pipe Upper", 8680031, lambda state: logic.AM2R_can_schmove(state) and logic.AM2R_can_bomb(state)), # schmove + LocationData("Inner Hydro Station", "Hydro Station: Below Tower Pipe Lower", 8680032, logic.AM2R_can_bomb), + LocationData("Inner Hydro Station", "Hydro Station: Dead End Missile ", 8680033, logic.AM2R_can_bomb), + LocationData("Inner Hydro Station", "Hydro Station: Hi Jump", 8680034), + LocationData("Inner Hydro Station", "Hydro Station: Behind Hi Jump Upper", 8680035, lambda state: logic.AM2R_can_schmove(state) and logic.AM2R_can_bomb(state)), + LocationData("Inner Hydro Station", "Hydro Station: Behind Hi Jump", 8680036, logic.AM2R_can_bomb), + + LocationData("Hydro Nest", "Hydro Nest: Below the Walkway", 8680037, logic.AM2R_can_bomb), # Bomb + LocationData("Hydro Nest", "Hydro Nest: Speed Celling", 8680038, lambda state: state.has("Speed Booster", player)), # speed + LocationData("Hydro Nest", "Hydro Nest: Behind the Wall", 8680039, lambda state: state.has("Power Bomb", player) and (state.has("Screw Attack") or state.has("Speed Booster"))), # PB + screw/speed + + LocationData("Industrial Complex Nest", "Industrial Complex: Above Save", 8680040), + LocationData("Industrial Complex Nest", "Industrial Complex: EMP Room", 8680041, lambda state: state.has("Power Bomb", player) and state.has("Super Missile", player)), # PB + super + LocationData("Industrial Complex Nest", "Industrial Complex Nest: Nest Shinespark", 8680042, lambda state: state.has("Super Missile", player) and state.has("Speed Booster", player)), # super + schmove + + LocationData("Pre Industrial Complex", "Industrial Complex: In the Sand", 8680043), + LocationData("Pre Industrial Complex", "Industrial Complex: Complex Side After Tunnel", 8680044, lambda state: state.has("Speed Booster", player) or logic.AM2R_can_spider(state)), + LocationData("Pre Industrial Complex", "Industrial Complex: Complex Side Tunnel", 8680045, lambda state: state.has("Speed Booster", player) or logic.AM2R_can_spider(state)), + LocationData("Pre Industrial Complex", "Industrial Complex: Save Room", 8680046, lambda state: state.has("Speed Booster", player) or logic.AM2R_can_spider(state)), + LocationData("Pre Industrial Complex", "Industrial Complex: Spazer", 8680047, lambda state: state.has("Speed Booster", player) or logic.AM2R_can_spider(state)), + LocationData("Pre Industrial Complex", "Industrial Complex: Gama Spark", 8680048, lambda state: state.has("Speed Booster", player)), + LocationData("Pre Industrial Complex", "Industrial Complex: Speed Booster", 8680049, lambda state: state.has("Speed Booster", player) or logic.AM2R_can_bomb(state)), # bomb + + LocationData("Torizo Ascended", "Torizo Ascended: Boss", 8680050, logic.AM2R_can_schmove), + + LocationData("Industrial Complex", "Industrial Complex: Conveyor Belt Room", 8680051, lambda state: state.has("Speed Booster", player)), + LocationData("Industrial Complex", "Industrial Complex: Guarded by the Alpha", 8680052, lambda state: state.has("Speed Booster", player) or logic.AM2R_can_fly(state)), + LocationData("Industrial Complex", "Industrial Complex: Robot room in the Wall", 8680053, lambda state: state.has("Speed Booster", player)), + LocationData("Industrial Complex", "Industrial Complex: Robot room in the Floor", 8680054, lambda state: state.has("Super Missile", player) and (state.has("Speed Booster", player) or logic.AM2R_can_fly(state))), + LocationData("Industrial Complex", "Industrial Complex: First Supers", 8680055), + + LocationData("GFS Thoth", "GFS Thoth: Research Camp", 8680056), + LocationData("GFS Thoth", "GFS Thoth: Hornoad room", 8680057, lambda state: state.has("Power Bomb", player)), + LocationData("GFS Thoth", "GFS Thoth: Outside the Front of the Ship", 8680058, lambda state: state.has("Power Bomb", player)), + LocationData("GFS Thoth", "GFS Thoth: Genesis", 8680059, lambda state: state.has("Power Bomb", player)), + + LocationData("The Tower", "The Tower: Beside Hydro Pipe", 8680060, lambda state: state.has("Screw Attack", player)), + LocationData("The Tower", "The Tower: Right Side of Tower", 8680061), + LocationData("The Tower", "The Tower: In the Ceiling", 8680062, lambda state: logic.AM2R_can_bomb(state) and logic.AM2R_can_schmove(state)), # schmove + bomb + LocationData("The Tower", "The Tower: Dark Maze", 8680063, logic.AM2R_can_bomb), # bomb + LocationData("The Tower", "The Tower: Outside the Dark Maze", 8680064, logic.AM2R_can_bomb), + LocationData("The Tower", "The Tower: Plasma Beam", 8680065, lambda state: logic.AM2R_can_bomb(state) and state.can_reach("Tester", "Region", player)), + LocationData("The Tower", "The Tower: Beside Tester", 8680066, lambda state: state.has("Power Bomb", player)), # pb + LocationData("The Tower", "The Tower: Left side of tower", 8680067, lambda state: state.has("Power Bomb", player)), # pb + + LocationData("Geothermal", "The Tower: Geothermal Reactor", 8680068), + LocationData("Geothermal", "The Tower: Post Geothermal Chozo", 8680069, lambda state: state.has("Power Bomb", player)), # pb + LocationData("Geothermal", "The Tower: Post Geothermal Shinespark", 8680070, lambda state: state.has("Power Bomb", player) and state.has("Speed Booster", player) and state.has("", player)), # Pb + spped + super + + LocationData("Underwater Distribution Center", "Distribution Center: Main Room Shinespark", 8680071, lambda state: state.has("Gravity Suit", player) and state.has("Speed Booster")), # grav + screw + LocationData("Underwater Distribution Center", "Distribution Center: Speed Hallway", 8680072, lambda state: state.has("Speed Booster", player) and state.has("Gravity Suit", player)), # speed + grav + + LocationData("EMP", "Distribution Center: After EMP Activation", 8680073, lambda state: state.has("Screw Attack", player)), # screw + + LocationData("Underwater Distro Connection", "Distribution Center: Spiderball Spike \"Maze\"", 8680074, lambda state: state.has("Spider_Ball", player)), # spiderball + LocationData("Underwater Distro Connection", "Distribution Center: Before Spikey Tunnel", 8680075), + LocationData("Underwater Distro Connection", "Distribution Center: Spikey Tunnel Shinespark", 8680076, lambda state: state.has("Gravity Suit", player) and state.has("Speed Booster", player)), # grav + speed + LocationData("Underwater Distro Connection", "Distribution Center: After Spikey Tunnel", 8680078, lambda state: state.has("Power Bomb", player) and state.has("Speed Booster", player) and state.has("Gravity Suit", player) and state.has("Space_Jump", player)), # speed + grav + space + pb + + LocationData("Pipe Hell R", "Distribution Center: Screw Attack", 8680080), + LocationData("Pipe Hell Outside", "Distribution Center: Outside after Gravity", 8680081, lambda state: state.has("Power Bomb", player) and state.has("Space_Jump", player) and state.has("Gravity Suit", player)), # pb + space + grav + LocationData("Pipe Hell R", "Distribution Center: Before Underwater Pipe", 8680082, lambda state: state.has("Power Bomb", player) and state.has("Speed Booster", player)), # pb + speed + + LocationData("Gravity", "Distribution Center: Before Gravity", 8680083, lambda state: (state.has("Bombs", player) and (state.has("ChargeBeam", player) or state.has("Gravity Suit", player))) or state.has("Power Bomb", player)), # bomb + charge/gravity / PB + LocationData("Gravity", "Distribution Center: Gravity", 8680084, logic.AM2R_can_bomb), # can bomb + + LocationData("Ice Beam", "Serris: Ice Beam", 8680085, lambda state: state.has("Ice Beam", player) and (state.has("Super Missile", player) or state.has("Speed Booster", player))), # speed / Supers + + LocationData("Deep Caves", "Deep Caves: Ball Spark", 8680086, logic.AM2R_has_ballspark), + LocationData("Deep Caves", "Deep Caves: Behind the Bomb Block", 8680087, logic.AM2R_can_bomb), + + LocationData("Deep Caves", "Deep Caves: After Omega", 8680088), + #metroids + + LocationData("First Alpha", "The Forgotten Alpha", 8680100), + + LocationData("Golden Temple", "Golden Temple: Metroid above Spider Ball", 8680101, logic.AM2R_can_spider), + LocationData("Golden Temple Nest", "Golden Temple Nest: Metroid 1", 8680102, logic.AM2R_can_bomb), + LocationData("Golden Temple Nest", "Golden Temple Nest: Metroid 2", 8680103, logic.AM2R_can_bomb), + LocationData("Golden Temple Nest", "Golden Temple Nest: Metroid 3", 8680104, logic.AM2R_can_bomb), + + LocationData("Main Caves", "Main Caves: Metroid before Hydro Station", 8680105), + LocationData("Hydro Station", "Hydro Station: Metroid Turbine Terror", 8680106), + LocationData("Hydro Station", "Hydro Station: Metroid up above", 8680107, logic.AM2R_can_schmove), + LocationData("Hydro Station", "Hydro Station: Metroid guarding the way inside", 8680108), + + LocationData("Hydro Nest", "Hydro Station Nest: Metroid 1", 8680109), + LocationData("Hydro Nest", "Hydro Station Nest: Metroid 2", 8680110), + LocationData("Hydro Nest", "Hydro Station Nest: Metroid 3", 8680111), + LocationData("Hydro Nest", "Hydro Station Nest: Surprise!", 8680112), + + LocationData("Industrial Complex Nest", "Industrial Nest: Metroid 1", 8680113, lambda state: state.has("Speed Booster", player) or state.has("Super Missile", player)), + LocationData("Industrial Complex Nest", "Industrial Nest: Metroid 2", 8680114, lambda state: state.has("Speed Booster", player) or state.has("Super Missile", player)), + LocationData("Industrial Complex Nest", "Industrial Nest: Metroid 3", 8680115, lambda state: state.has("Speed Booster", player) or state.has("Super Missile", player)), + LocationData("Industrial Complex Nest", "Industrial Nest: Metroid 4", 8680116, lambda state: state.has("Speed Booster", player) or state.has("Super Missile", player)), + LocationData("Industrial Complex Nest", "Industrial Nest: Gama in your floors", 8680117, lambda state: logic.AM2R_can_bomb(state) and (state.has("Speed Booster", player) or state.has("Super Missile", player))), + LocationData("Industrial Complex Nest", "Industrial Nest: At the end of the road", 8680118, lambda state: logic.AM2R_can_bomb(state) and state.has("Spider_Ball", player) and (state.has("Speed Booster", player) or state.has("Super Missile", player))), + + LocationData("Pre Industrial Complex", "Industrial Complex: Shinespark denier Gama", 8680119), + LocationData("Pre Industrial Complex", "Industrial Complex: Gama Off to the Right", 8680120), + + LocationData("Industrial Complex", "Industrial Complex: Guardian of doom treadmill", 8680121), + LocationData("Industrial Complex", "Industrial Complex: Super Missile Test Subject", 8680121), + + LocationData("GFS Thoth", "Dual Alphas: Fred", 8680123), + LocationData("GFS Thoth", "Dual Alphas: George", 8680124), + + LocationData("Mines", "Mines: Alpha", 8680125, lambda state: state.has("Super Missile", player) and (logic.AM2R_can_fly(state) or state.has("Speed Booster", player))), + LocationData("Mines", "Mines: Gama", 8680126, lambda state: state.has("Super Missile", player) and (logic.AM2R_can_fly(state) or state.has("Speed Booster", player))), + + LocationData("The Tower", "The Tower: Metroid 1", 8680127, logic.AM2R_can_spider), + LocationData("The Tower", "The Tower: Metroid 2", 8680128, logic.AM2R_can_fly), + LocationData("The Tower", "The Tower: Metroid 3", 8680129, logic.AM2R_can_schmove), + LocationData("The Tower", "The Tower: Metroid 4", 8680130, logic.AM2R_can_bomb), + LocationData("The Tower", "The Tower: Metroid 5", 8680131, logic.AM2R_can_fly), + LocationData("The Tower", "The Tower: Metroid 6", 8680132, logic.AM2R_can_fly), + + LocationData("EMP", "EMP: Sir Zeta Commander of the Alpha Squadron", 8680133), + + LocationData("Pipe Hell R", "Alpha Squadron: Timmy", 8680134), + LocationData("Pipe Hell R", "Alpha Squadron: Tommy", 8680135), + LocationData("Pipe Hell R", "Alpha Squadron: Terry", 8680136), + LocationData("Pipe Hell R", "Alpha Squadron: Telly", 8680137), + LocationData("Pipe Hell R", "Alpha Squadron: Martin", 8680138), + + LocationData("Underwater Distro Connection", "Underwater: Gama Bros Mario", 8680139), + LocationData("Underwater Distro Connection", "Underwater: Gama Bros Luigi", 8680140), + + LocationData("Deep Caves", "Deep Caves: Little Bro", 8680141), + LocationData("Deep Caves", "Deep Caves: Big Sis", 8680142), + LocationData("Deep Caves", "Omega Nest: Metroid 3", 8680143), + LocationData("Deep Caves", "Omega Nest: Metroid 4", 8680144), + LocationData("Deep Caves", "Omega Nest: Metroid 5", 8680145), + LocationData("Research Station", "The Last Metroid is in Captivity", EventId), + ] -class AM2RLocation(Location): - game: str = "AM2R" - - def __int__(self, player: int, name: str, address: typing.Optional[int], parent): - super(). __init__(player, name, address, parent) - self.event = not address - - -# todo actual location numbers -location_table = { - "Main Caves: Vertical Spike Room Upper": LocationData(2, "Main Caves"), # spider + bomb - "Main Caves: Vertical Spike Room Lower": LocationData(2, "Main Caves"), # bomb - "Main Caves: Crumble Spike Room": LocationData(2, "Main Caves"), # jump - "Main Caves: Maze": LocationData(2, "Main Caves"), - "Main Caves: Shinespark Before the drop": LocationData(2, "Main Caves"), # speed - "Main Caves: Shinespark After the drop": LocationData(2, "Main Caves"), # speed - - "Golden Temple: Bombs": LocationData(2, "Golden Temple"), - "Golden Temple: Missile below Bombs": LocationData(2, "Golden Temple"), # bomb - "Golden Temple: Hidden Energy Tank": LocationData(2, "Golden Temple"), # bomb - "Golden Temple: Charge Beam": LocationData(2, "Golden Temple"), - "Golden Temple: Armory 1": LocationData(2, "Golden Temple"), - "Golden Temple: Armory 2": LocationData(2, "Golden Temple"), - "Golden Temple: Armory 3": LocationData(2, "Golden Temple"), - "Golden Temple: Armory Missile False Wall ": LocationData(2, "Golden Temple"), # bomb - "Golden Temple: Puzzle Room Missle 1": LocationData(2, "Golden Temple"), - "Golden Temple: Puzzle Room Missle 2": LocationData(2, "Golden Temple"), - "Golden Temple: Puzzle Room Energy Tank": LocationData(2, "Golden Temple"), - "Golden Temple: Spider Ball": LocationData(2, "Golden Temple"), - "Golden Temple: Celling Missile": LocationData(2, "Golden Temple"), # canspider - "Golden Temple: EMP room": LocationData(2, "Golden Temple"), # super + ballspark - - "Guardian: Up Above": LocationData(2, "Guardian"), # bomb + schmove - "Guardian: Behind The Door": LocationData(2, "Guardian"), # PB + schmove - - "Hydro Station: Clif": LocationData(2, "Hydro Station"), - "Hydro Station: Morph Tunnel": LocationData(2, "Hydro Station"), - "Hydro Station: Arachnus": LocationData(2, "Hydro Station"), # bomb - "Hydro Station: Turbine Room": LocationData(2, "Hydro Station"), # bomb - "Hydro Station: Not so Secret Tunel": LocationData(2, "Hydro Station"), # schmove - "Hydro Station: Water puzzle Beside Varia": LocationData(2, "Hydro Station"), # bomb - "Hydro Station: Varia Suit": LocationData(2, "Hydro Station"), # bomb - "Hydro Station: EMP room": LocationData(2, "Hydro Station"), # super + speed - "Hydro Station: Wave Beam": LocationData(2, "Inner Hydro Station"), - "Hydro Station: Below Tower Pipe Upper": LocationData(2, "Inner Hydro Station"), # schmove - "Hydro Station: Below Tower Pipe Lower": LocationData(2, "Inner Hydro Station"), - "Hydro Station: Dead End Missile ": LocationData(2, "Inner Hydro Station"), - "Hydro Station: Hi Jump": LocationData(2, "Inner Hydro Station"), - "Hydro Station: Behind Hi Jump Upper": LocationData(2, "Inner Hydro Station"), - "Hydro Station: Behind Hi Jump": LocationData(2, "Inner Hydro Station"), - - "Hydro Nest: Below the Walkway": LocationData(2, "Hydro Nest"), # Bomb - "Hydro Nest: Speed Celling": LocationData(2, "Hydro Nest"), # speed - "Hydro Nest: Behind the Wall": LocationData(2, "Hydro Nest"), # PB + screw/speed - - "Industrial Complex Nest: Above Save": LocationData(2, "Industrial Complex Nest"), - "Industrial Complex Nest: EMP room": LocationData(2, "Industrial Complex Nest"), # PB + super - "Industrial Complex Nest: Nest Super": LocationData(2, "Industrial Complex Nest"), # super + schmove - - "Pre Industrial Complex: In the Sand": LocationData(2, "Pre Industrial Complex"), - "Pre Industrial Complex: Space Jump": LocationData(2, "Pre Industrial Complex"), # schmove - "Pre Industrial Complex: Complex Side After Tunnel": LocationData(2, "Pre Industrial Complex"), - "Pre Industrial Complex: Complex Side Tunnel": LocationData(2, "Pre Industrial Complex"), - "Industrial Complex: Save Room": LocationData(2, "Pre Industrial Complex"), - "Industrial Complex: Spazer": LocationData(2, "Pre Industrial Complex"), - "Industrial Complex: Speed Booster": LocationData(2, "Pre Industrial Complex"), # bomb - - "Industrial Complex: Gama Spark": LocationData(2, "Industrial Complex"), - "Industrial Complex: Conveyor Belt Room": LocationData(2, "Industrial Complex"), - "Industrial Complex: Guarded by the Alpha": LocationData(2, "Industrial Complex"), - "Industrial Complex: Robot room in the Wall": LocationData(2, "Industrial Complex"), - "Industrial Complex: Robot room un the Floor": LocationData(2, "Industrial Complex"), - "Industrial Complex: First Supers": LocationData(2, "Industrial Complex"), - - "GFS Thoth: Research Camp": LocationData(2, "GFS Thoth"), - "GFS Thoth: Hornoad room": LocationData(2, "GFS Thoth"), - "GFS Thoth: Outside the Front of the Ship": LocationData(2, "GFS Thoth"), - "GFS Thoth: Genesis": LocationData(2, "GFS Thoth"), - - "The Tower: Beside Hydro Pipe": LocationData(2, "The Tower"), - "The Tower: Right Side of Tower": LocationData(2, "The Tower"), - "The Tower: In the Ceiling": LocationData(2, "The Tower"), # schmove + bomb - "The Tower: Dark Maze": LocationData(2, "The Tower"), # - "The Tower: Outside the Dark Maze": LocationData(2, "The Tower"), - "The Tower: Plasma Beam": LocationData(2, "The Tower"), - "The Tower: Beside Tester": LocationData(2, "The Tower"), # pb - "The Tower: Left side of tower": LocationData(2, "The Tower"), # pb - "The Tower: Geothermal Reactor": LocationData(2, "The Tower"), - "The Tower: Post Geothermal Chozo": LocationData(2, "The Tower"), # pb - "The Tower: Post Geothermal Shinespark": LocationData(2, "The Tower"), # Pb + spped + super - - "Distribution Center: Main Room Shinespark": LocationData(2, "Distribution Center"), # grav + screw - "Distribution Center: After EMP Activation": LocationData(2, "Distribution Center"), # screw - "Distribution Center: Screw Attack": LocationData(2, "Distribution Center"), - "Distribution Center: Before Gravity": LocationData(2, "Distribution Center"), # bomb + charge/gravity / PB - "Distribution Center: Gravity": LocationData(2, "Distribution Center"), # can bomb - "Distribution Center: Outside after Gravity": LocationData(2, "Distribution Center"), # pb + space + grav - "Distribution Center: Before Underwater Pipe": LocationData(2, "Distribution Center"), # pb + speed - "Distribution Center: Ice Beam": LocationData(2, "Distribution Center"), # grav + speed / Supers - "Distribution Center: Spiderball Spike Maze": LocationData(2, "Distribution Center"), # spiderball - "Distribution Center: Before Spike tunnel room": LocationData(2, "Distribution Center"), - "Distribution Center: Spike Tunnel room Shinespark": LocationData(2, "Distribution Center"), # grav + speed - "Distribution Center: After Spike Tunnel Room": LocationData(2, "Distribution Center"), # speed + grav + space + pb - "Distribution Center: Speed Hallway": LocationData(2, "Distribution Center"), # speed + grav - - "Deep Caves: Ball Spark": LocationData(2, "Deep Caves"), - "Deep Caves: Behind the Bomb Block": LocationData(2, "Deep Caves"), - "Deep Caves: After Omega": LocationData(2, "Deep Caves"), - - "The Last Metroid is in Captivity": LocationData(None, "Research Station") -} + return tuple(location_table) diff --git a/worlds/AM2R/options.py b/worlds/AM2R/options.py index 5f9129d1863a..7ca797f84e00 100644 --- a/worlds/AM2R/options.py +++ b/worlds/AM2R/options.py @@ -11,12 +11,17 @@ class MetroidsRequired(Range): default = 41 -class MetroidsAreChecks(Toggle): +class MetroidsAreChecks(Choice): """Have each of the 46 non lab Metroids be treated as locations""" display_name = "Metroids are Checks" + default = 0 + option_disabled = 0 + option_exclude_A6 = 1 + option_include_A6 = 2 + -class TrapFillPrecentage(Range): +class TrapFillPercentage(Range): """Adds in Slightly inconvenient Traps into the item pool Equipment Traps disable 1 random item for up to 3 minutes depending on the disabled item (more critical items will be disabled for less time). Ice Traps seem rather self-explanatory, but they will freeze you upon receiving them with a full fanfare and an actual player freeze""" @@ -33,41 +38,65 @@ class Traps(OptionList): default = {"Equipment Trap", "Ice Trap", "Short Beam", "EMP Trap"} -# class AreaRando(Choice): - # """Activates Area Randomization and or Boss Randomization, also activates rolling saves as softlock prevention - # Area Randomizer will shuffle various Areas arround in order to create a new expierence - # Boss Randomization randomizes Arachnus, Torizo Ascended, and Genesis with each other also then randomizes - # Temple Guardian, Tester and Serris - # Both activates Both independently on their own""" - # display_name = "Area Randomizer" +#class ItemSprites(OptionList): +# """Changes Item Sprites """ +# display_name = "Item Sprites" +# default = 0 +# option_normal = 0 +# option_themed = 1 +# option_chiny = 2 +# option_ungrouped = 3 +# option_lies = 4 + + + +#class StartingWeapons(Choice): +# """Removes your Arm Cannon and makes it a findable item""" +# display_name = "Starting Weapons" +# default = 0 +# option_normal = 0 +# option_missiles_only = 1 +# option_beam_only = 2 +# option_none = 3 + - # default = 0 - # option_disabled = 0 - # option_area = 1 - # option_boss = 2 - # option_both = 3 - # option_chaos = 4 +class RandomizeBaby(Toggle): + """Randomizes the baby metroid as a cosmetic find""" + display_name = "Randomize Baby" -# class BossRando(Toggle): - # """Activates Boss Randomization randomizes Arachnus, Torizo Ascended, and Genesis with each other. - # then also randomizes Temple Guardian, Tester abd Serris""" - # display_name = "Boss Randomizer" +#class AreaRando(Choice): +# """Activates Area Randomization and or Boss Randomization, also activates rolling saves as softlock prevention +# Area Randomizer will shuffle various Areas arround in order to create a new expierence +# Boss Randomization randomizes Arachnus, Torizo Ascended, and Genesis with each other also then randomizes +# Temple Guardian, Tester and Serris +# Both activates Both independently on their own""" +# display_name = "Area Randomizer" +# +# default = 0 +# option_disabled = 0 +# option_area = 1 +# option_boss = 2 +# option_both = 3 # class IceMissiles(Toggle): - # """Changes missiles to have Ice properties """ - # display_name = "Ice Missiles" +# """Changes missiles to have Ice properties +# Does not account for jumping off enimies +# only counts as being able to freeze meboids and metroid larva""" +# display_name = "Ice Missiles" AM2R_options: Dict[str, type(Option)] = { - "metroids_required": MetroidsRequired, - "metroids_are_checks": MetroidsAreChecks, - "trap_fill_precentage": TrapFillPrecentage, - "traps": Traps, - # "area_rando": AreaRando, - # "boss_rando": BossRando, - # "ice_missiles": IceMissiles, + "Metroids Required": MetroidsRequired, + "Metroids are Checks": MetroidsAreChecks, + "Trap Fill Percentage": TrapFillPercentage, + "Traps": Traps, + # "Item Sprites": ItemSprites, + # "Starting Weapons": StartingWeapons, + # "Randomize Baby", RandomizeBaby + # "Area Rando": AreaRando, + # "Ice Missiles": IceMissiles, # "DeathLink": DeathLink, } diff --git a/worlds/AM2R/regions.py b/worlds/AM2R/regions.py index eb310f465ed6..e61cc52ab39f 100644 --- a/worlds/AM2R/regions.py +++ b/worlds/AM2R/regions.py @@ -1,146 +1,258 @@ -from dataclasses import dataclass, field -from enum import IntFlag -from random import Random -from typing import Iterable, Dict, Protocol, Optional, List, Tuple +from typing import List, Set, Dict, Tuple, Optional, Callable, NamedTuple +from BaseClasses import CollectionState, MultiWorld, Region, Entrance, Location +from .options import is_option_enabled +from .locations import LocationData, get_location_datas +from .rules import AM2RLogic -from BaseClasses import Region, Entrance -from . import options -from .options import AM2ROptions +EventId: Optional[int] = None +class LocationData(NamedTuple): + region: str + name: str + code: Optional[int] + rule: Callable[[CollectionState], bool] = lambda state: True +def create_regions_and_locations(world: MultiWorld, player: int): + location_datas: Tuple[LocationData] = get_location_datas(world, player) -class RegionFactory(Protocol): - def __call__(self, name: str, regions: Iterable[str]) -> Region: - raise NotImplementedError + locations_per_region: Dict[str, List[LocationData]] = split_location_datas_per_region(location_datas) + regions = [ + create_region(world, player, locations_per_region, "Menu"), + create_region(world, player, locations_per_region, "Main Caves"), + create_region(world, player, locations_per_region, "First Alpha"), + create_region(world, player, locations_per_region, "Guardian"), + create_region(world, player, locations_per_region, "Golden Temple"), + create_region(world, player, locations_per_region, "Golden Temple Nest"), + create_region(world, player, locations_per_region, "Hydro Station"), + create_region(world, player, locations_per_region, "Inner Hydro Station"), + create_region(world, player, locations_per_region, "Hydro Nest"), + create_region(world, player, locations_per_region, "Arachnus"), + create_region(world, player, locations_per_region, "Mines"), + create_region(world, player, locations_per_region, "Industrial Complex Nest"), + create_region(world, player, locations_per_region, "Pre Industrial Complex"), + create_region(world, player, locations_per_region, "Industrial Complex"), + create_region(world, player, locations_per_region, "Torizo Ascended"), + create_region(world, player, locations_per_region, "The Tower"), + create_region(world, player, locations_per_region, "Geothermal"), + create_region(world, player, locations_per_region, "Tester"), + create_region(world, player, locations_per_region, "Tester Upper"), + create_region(world, player, locations_per_region, "Tester Lower"), + create_region(world, player, locations_per_region, "Underwater Distribution Center"), + create_region(world, player, locations_per_region, "Underwater Distro Connection"), + create_region(world, player, locations_per_region, "Serris"), + create_region(world, player, locations_per_region, "Ice Beam"), + create_region(world, player, locations_per_region, "Pipe Hell BL"), + create_region(world, player, locations_per_region, "Pipe Hell BR"), + create_region(world, player, locations_per_region, "Pipe Hell L"), + create_region(world, player, locations_per_region, "Pipe Hell R"), + create_region(world, player, locations_per_region, "Pipe Hell Outside"), + create_region(world, player, locations_per_region, "Fast Travel"), + create_region(world, player, locations_per_region, "Gravity"), + create_region(world, player, locations_per_region, "EMP"), + create_region(world, player, locations_per_region, "GFS Thoth"), + create_region(world, player, locations_per_region, "Genesis"), + create_region(world, player, locations_per_region, "Deep Caves"), + create_region(world, player, locations_per_region, "Omega Nest"), + create_region(world, player, locations_per_region, "The Lab"), + create_region(world, player, locations_per_region, "Research Station") + ] -class RandomizationFlag(IntFlag): - NOT_RANDOMIZED = 0b0 - AREA_RANDO = 0b11111 - BOSS_RANDO = 0b11110 - PIPE_RANDO = 0b11100 + if __debug__: + throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys()) + world.regions += regions -@dataclass(frozen=True) -class RegionData: - name: str - exits: List[str] = field(default_factory=list) + logic = AM2RLogic(world, player) + connect(world, player, "Menu", "Main Caves"), -@dataclass(frozen=True) -class ConnectionData: - name: str - destination: str - reverse: Optional[str] = None - flag: RandomizationFlag = RandomizationFlag.NOT_RANDOMIZED - - def __post_init__(self): - if self.reverse is None and " to " in self.name: - origin, destination = self.name.split(" to ") - super().__setattr__("reverse", f"{destination} to {origin}") - - -AM2R_regions = [ - RegionData('Menu', ['New Game']), - RegionData('Main Caves', ['To Alpha', 'Fight Guardian', 'Reach A2', 'Reach Mines', 'Reach A3 Nest', - 'Reach A4', 'Reach A5', 'Reach A6', 'Reach GFS']), - RegionData('Guardian', ['Reach A1', 'Reach A1 Nest']), - RegionData('Golden Temple', ['A1-Pipe Hell Pipe']), - RegionData('Golden Temple Nest'), - RegionData('Hydro Station', ['Reach A2 Nest', 'A2-A4 Pipe', 'A2-Lab Pipe', 'Fight Arachnus']), - RegionData('Hydro Nest'), - RegionData('Arachnus'), - RegionData('Mines'), - RegionData('Industrial Complex Nest', ['Reach A3']), - RegionData('Pre Industrial Complex', ['To A3 Pipe', 'I find it quite simple', 'Fight Torizo Ascended']), - RegionData('Torizo Ascended'), - RegionData('Industrial Complex'), - RegionData('The Tower', ['Reach Basement', 'Fight Tester']), - RegionData('Geothermal'), - RegionData('Tester', ['Leave Tester']), - RegionData('Distribution Center', ['Reach EMP Win', 'Reach Bottom Hell', 'Reach Top Hell', - 'Fight Serris', ]), - RegionData('Serris', ['Exit Stage Left']), - RegionData('Pipe Hell', ['Grav Win Pipe', 'BL-BR Pipe', 'TL-TR Pipe']), - RegionData('Gravity', ['Reach Outside Hell']), - RegionData('EMP', ['Reach Pipe Hell Lose']), - RegionData('GFS Thoth' ['Fight Genesis']), - RegionData('Genesis'), - RegionData('Deep Caves', ['reach Omega Nest']), - RegionData('Omega Nest') -] - -mandatory_connections = [ - ConnectionData('New Game', 'Main Caves'), - ConnectionData('To Alpha', 'First Alpha', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Fight Guardian', 'Guardian', flag=RandomizationFlag.BOSS_RANDO_2), - ConnectionData('Reach A1', 'Golden Temple', flag=RandomizationFlag.BOSS_RANDO_2), - ConnectionData('Reach A2', 'Hydro Station', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach Mines', 'Mines', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach A3 Nest', 'Industrial Complex Nest', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach GFS', 'GFS Thoth', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Fight Genesis', 'Genesis', flag=RandomizationFlag.BOSS_RANDO_1), - ConnectionData('Reach A4', 'The Tower', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach A5', 'Distribution Center', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach A6', 'Deep Caves', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach A1 Nest', 'Golden Temple Nest', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('A1-Pipe Hell Pipe', 'Pipe Hell', flag=RandomizationFlag.PIPE_RANDO), - ConnectionData('Reach A2 Nest', 'Hydro Station Nest', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('A2-A4 Pipe', 'The Tower', flag=RandomizationFlag.PIPE_RANDO), - ConnectionData('A2-Lab Pipe', 'Omega Nest', flag=RandomizationFlag.PIPE_RANDO), - ConnectionData('Fight Arachnus', 'Arachnus', flag=RandomizationFlag.BOSS_RANDO_1), - ConnectionData('Reach A3', 'Pre Industrial Complex', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('I find it quite simple', 'Industrial Complex', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Fight Torizo Ascended', 'Torizo Ascended', flag=RandomizationFlag.BOSS_RANDO_1), - ConnectionData('Reach Basement', 'Geothermal', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Fight Tester', 'Tester', flag=RandomizationFlag.BOSS_RANDO_2), - ConnectionData('Leave Tester', 'The Tower', flag=RandomizationFlag.BOSS_RANDO_2), - ConnectionData('Reach EMP Win', 'EMP', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach Pipe Hell Lose', 'Pipe Hell', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach Bottom Hell', 'Pipe Hell', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach Top Hell', 'Pipe Hell', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Fight Serris', 'Serris', flag=RandomizationFlag.BOSS_RANDO_2), - ConnectionData('Exit Stage Left', 'Distribution Center', flag=RandomizationFlag.BOSS_RANDO_2), - ConnectionData('Grav Win Pipe', 'Gravity', flag=RandomizationFlag.PIPE_RANDO), - ConnectionData('BL-BR Pipe', 'Pipe Hell', flag=RandomizationFlag.PIPE_RANDO), - ConnectionData('TL-TR Pipe', 'Pipe Hell', flag=RandomizationFlag.PIPE_RANDO), - ConnectionData('Reach Outside Hell', 'Pipe Hell', flag=RandomizationFlag.AREA_RANDO), - ConnectionData('Reach Omega Nest', 'Omega Nest', flag=RandomizationFlag.AREA_RANDO) -] - - -# def create_regions(region_factory: RegionFactory, random: Random, world_options: AM2ROptions) -> Tuple[Iterable[Region], Dict[str, str]]: - # regions: Dict[str: Region] = {region.name: region_factory(region.name, region.exits) for region in AM2R_regions} - # entrances: Dict[str: Entrance] = {entrance.name: entrance - # for region in regions.values() - # for entrance in region.exits} - - # connections, randomized_data = randomize_connections(random, world_options) - - # for connection in connections: - # if connection.name not in entrances: - # continue - # entrances[connection.name].connect(regions[connection.destination]) - - # return regions.values(), randomized_data - - -# def randomize_connections(random: Random, world_options: AM2ROptions) -> Tuple[List[ConnectionData], Dict[str, str]]: # todo this section - # connections_to_randomize = [] - # if world_options[options.EntranceRandomization] == options.AreaRando.option_area: - # connections_to_randomize = [connection for connection in mandatory_connections if RandomizationFlag.AREA_RANDO in connection.flag] - # elif world_options[options.EntranceRandomization] == options.AreaRando.option_boss: - # connections_to_randomize = [connection for connection in mandatory_connections if RandomizationFlag.BOSS_RANDO in connection.flag] - # random.shuffle(connections_to_randomize) - - # destination_pool = list(connections_to_randomize) - # random.shuffle(destination_pool) - - # randomized_connections = [] - # randomized_data = {} - # for connection in connections_to_randomize: - # destination = destination_pool.pop() - # randomized_connections.append(ConnectionData(connection.name, destination.destination, destination.reverse)) - # randomized_data[connection.name] = destination.name - # randomized_data[destination.reverse] = connection.reverse - - # return mandatory_connections, randomized_data + connect(world, player, "Main Caves", "Guardian"), + connect(world, player, "Guardian", "Main Caves"), + + connect(world, player, "Main Caves", "First Alpha"), + connect(world, player, "First Alpha", "Main Caves"), + + connect(world, player, "Main Caves", "Hydro Station"), + connect(world, player, "Hydro Station", "Main Caves"), + + connect(world, player, "Main Caves", "Mines"), + connect(world, player, "Mines", "Main Caves"), + + connect(world, player, "Main Caves", "Industrial Complex Nest"), + connect(world, player, "Industrial Complex Nest", "Main Caves"), + + connect(world, player, "Main Caves", "The Tower"), + connect(world, player, "The Tower", "Main Caves"), + + connect(world, player, "Main Caves", "Underwater Distribution Center"), + connect(world, player, "Underwater Distribution Center", "Main Caves"), + + connect(world, player, "Main Caves", "Deep Caves",), + connect(world, player, "Deep Caves", "Main Caves"), + + connect(world, player, "Main Caves", "GFS Thoth"), + connect(world, player, "GFS Thoth", "Main Caves"), + + connect(world, player, "Guardian", "Golden Temple"), + connect(world, player, "Golden Temple", "Guardian"), + + connect(world, player, "Guardian", "Golden Temple Nest"), + connect(world, player, "Golden Temple Nest", "Guardian"), + + connect(world, player, "Golden Temple", "Fast Travel", lambda state: state.has("Screw Attack", player)), + connect(world, player, "Fast Travel", "Golden Temple", lambda state: state.has("Screw Attack", player)), + + connect(world, player, "Hydro Station", "Hydro Nest", logic.AM2R_can_jump), + connect(world, player, "Hydro Nest", "Hydro Station"), + + connect(world, player, "Hydro Station", "The Tower", lambda state: state.has("Screw Attack", player)), + connect(world, player, "The Tower", "Hydro Station", lambda state: state.has("Screw Attack", player)), + + connect(world, player, "Hydro Station", "The Lab", lambda state: state.has("Screw Attack", player)), # todo make this right + connect(world, player, "The Lab", "Hydro Station", lambda state: state.has("Screw Attack", player)), + + connect(world, player, "Hydro Station", "Arachnus", logic.AM2R_can_bomb), + connect(world, player, "Arachnus", "Hydro Station"), + + connect(world, player, "Hydro Station", "Inner Hydro Station", lambda state: state.has("Screw Attack", player) or logic.AM2R_can_bomb(state)) + connect(world, player, "Inner Hydro Station", "Hydro Station", lambda state: state.has("Screw Attack", player) or logic.AM2R_can_bomb(state)) + + connect(world, player, "Industrial Complex Nest", "Pre Industrial Complex"), + connect(world, player, "Pre Industrial Complex", "Industrial Complex Nest"), + + connect(world, player, "Pre Industrial Complex", "Industrial Complex"), + connect(world, player, "Industrial Complex", "Pre Industrial Complex", lambda state: state.has("Speed Booster", player)), + + connect(world, player, "Pre Industrial Complex", "Fast Travel", lambda state: state.has("Screw Attack", player)), + connect(world, player, "Fast Travel", "Pre Industrial Complex", lambda state: state.has("Screw Attack", player)), + + connect(world, player, "Pre Industrial Complex", "Torizo Ascended"), + connect(world, player, "Torizo Ascended", "Pre Industrial Complex"), + # A4 to Geothermal + connect(world, player, "The Tower", "Geothermal"), + connect(world, player, "Geothermal", "The Tower", lambda state: state.has("Speed Booster", player) and state.has("Power Bomb", player)), + # tower to A5 + connect(world, player, "The Tower", "Fast Travel", lambda state: state.has("Screw Attack", player)), + connect(world, player, "Fast Travel", "The Tower", lambda state: state.has("Screw Attack", player)), + # tester + connect(world, player, "The Tower", "Tester Lower", logic.AM2R_can_bomb), + connect(world, player, "The Tower", "Tester Upper", logic.AM2R_can_bomb), + connect(world, player, "Tester Lower", "Tester Upper"), + connect(world, player, "Tester Upper", "Tester Lower"), + connect(world, player, "Tester Lower", "The Tower"), + connect(world, player, "Tester Upper", "The Tower"), + # A5 + connect(world, player, "Underwater Distribution Center", "EMP"), + connect(world, player, "EMP", "Underwater Distribution Center"), + + connect(world, player, "Underwater Distribution Center", "Serris"), + connect(world, player, "Serris", "Underwater Distribution Center"), + + connect(world, player, "Ice Beam", "Serris"), + connect(world, player, "Serris", "Ice Beam"), + + # Pipe Hell Fuckery + connect(world, player, "EMP", "Pipe Hell BL"), + connect(world, player, "Pipe Hell BL", "EMP"), + + connect(world, player, "Pipe Hell BL", "Pipe Hell BR"), + connect(world, player, "Pipe Hell BR", "Pipe Hell BL"), + + connect(world, player, "Pipe Hell L", "Pipe Hell BL", lambda state: state.has("Screw Attack", player)), + connect(world, player, "Pipe Hell BL", "Pipe Hell L", lambda state: state.has("Screw Attack", player)), + + connect(world, player, "Pipe Hell BR", "Pipe Hell L"), + connect(world, player, "Pipe Hell L", "Pipe Hell BR"), + + connect(world, player, "Pipe Hell BR", "Pipe Hell R", lambda state: state.has("Screw Attack", player)), + connect(world, player, "Pipe Hell R", "Pipe Hell BR", lambda state: state.has("Screw Attack", player)), + + connect(world, player, "Pipe Hell R", "Pipe Hell L", logic.AM2R_can_bomb), + connect(world, player, "Pipe Hell L", "Pipe Hell R", logic.AM2R_can_bomb), + + connect(world, player, "Pipe Hell L", "Fast Travel", lambda state: state.has("Screw Attack", player)), + connect(world, player, "Fast Travel", "Pipe Hell L", lambda state: state.has("Screw Attack", player)), + + connect(world, player, "Fast Travel", "Gravity"), # one way transition due to crumbles + + connect(world, player, "Fast Travel", "Underwater Distribution Center"), + connect(world, player, "Underwater Distribution Center", "Fast Travel", lambda state: state.can_reach("Fast Travel", "Region", player)), + + connect(world,player, "Gravity", "Pipe Hell Outside", lambda state: state.has("Gravity Suit", player) and state.has("Spcae Jump", player)), + connect(world,player, "Pipe Hell Outside", "Gravity"), + + connect(world, player, "Pipe Hell Outside", "Pipe Hell R"), + connect(world, player, "Pipe Hell R", "Pipe Hell Outside", lambda state: state.can_reach("Pipe Hell Outside", "Region", player)), + + connect(world, player, "Underwater Distribution Center", "Underwater Distro Connection", lambda state: state.has("Ice Beam", player) or (state.has("Gravity Suit", player) and state.has("Speed Booster", player))), + connect(world, player, "Underwater Distro Connection", "Underwater Distribution Center", lambda state: state.has("Ice Beam", player) or (state.has("Gravity Suit", player) and state.has("Speed Booster", player))), + + connect(world, player, "Underwater Distro Connection", "Pipe Hell R"), + connect(world, player, "Pipe Hell R", "Underwater Distro Connection", lambda state: state.has("Super Missile", player) or (state.has("Gravity Suit", player) and state.has("Speed Booster", player))) + + connect(world, player, "Deep Caves", "Omega Nest") + connect(world, player, "Omega Nest", "Deep Caves") + + connect(world, player, "Omega Nest", "The Lab", logic.AM2R_can_lab) + connect(world, player, "The Lab", "Omega Nest", logic.AM2R_can_lab) + + connect(world, player, "The Lab", "Research Station") + + + + +def throwIfAnyLocationIsNotAssignedToARegion(regions: List[Region], regionNames: Set[str]): + existingRegions: Set[str] = set() + + for region in regions: + existingRegions.add(region.name) + + if regionNames - existingRegions: + raise Exception( + "AM2R: the following regions are used in locations: {}, but no such region exists".format( + regionNames - existingRegions)) + + +def create_location(player: int, location_data: LocationData, region: Region) -> Location: + location = Location(player, location_data.name, location_data.code, region) + location.access_rule = location_data.rule + + if id is None: + location.event = True + location.locked = True + + return location + + +def create_region(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]],name: str) -> Region: + region = Region(name, player, world) + + if name in locations_per_region: + for location_data in locations_per_region[name]: + location = create_location(player, location_data, region) + region.locations.append(location) + + return region + + +def connect(world: MultiWorld, player: int, source: str, target: str, + rule: Optional[Callable[[CollectionState], bool]] = None): + sourceRegion = world.get_region(source, player) + targetRegion = world.get_region(target, player) + + connection = Entrance(player, "", sourceRegion) + + if rule: + connection.access_rule = rule + + sourceRegion.exits.append(connection) + connection.connect(targetRegion) + + +def split_location_datas_per_region(locations: Tuple[LocationData, ...]) -> Dict[str, List[LocationData]]: + per_region: Dict[str, List[LocationData]] = {} + + for location in locations: + per_region.setdefault(location.region, []).append(location) + + return per_region diff --git a/worlds/AM2R/rules.py b/worlds/AM2R/rules.py index ed6423997d3e..144c25bb1fbf 100644 --- a/worlds/AM2R/rules.py +++ b/worlds/AM2R/rules.py @@ -1,27 +1,42 @@ -from BaseClasses import MultiWorld -from ..AutoWorld import LogicMixin -# from .options import is_option_enabled +from typing import Union +from BaseClasses import MultiWorld, CollectionState +from .options import is_option_enabled -class AM2RLogic(LogicMixin): +class AM2RLogic: + player: int - def _AM2R_can_bomb(self, world: MultiWorld, player: int) -> bool: - return self.has_any({'Bombs', 'Power Bombs'}, player) + def __init__(self, world: MultiWorld, player: int): + self.player = player - def _AM2R_can_jump(self, world: MultiWorld, player: int) -> bool: - return self.has_any({'Hi Jump', 'Space Jump', 'Bombs'}, player) + def AM2R_can_bomb(self, state: CollectionState) -> bool: + return state.has_any({'Bombs', 'Power Bombs'}, self.player) - def _AM2R_can_fly(self, world: MultiWorld, player: int) -> bool: - return self.has_any({'Bombs', 'Space Jump'}, player) + def AM2R_can_jump(self, state: CollectionState) -> bool: + return state.has_any({'Hi Jump', 'Space Jump', 'Bombs'}, self.player) - def _AM2R_can_spider(self, world: MultiWorld, player: int) -> bool: - return self.has('Spiderball', player) or self._AM2R_can_fly(world, player) + def AM2R_can_fly(self, state: CollectionState) -> bool: + return state.has_any({'Bombs', 'Space Jump'}, self.player) - def _AM2R_can_schmove(self, world: MultiWorld, player: int) -> bool: - return self._AM2R_can_spider(world, player) or self.has('Hi Jump', player) + def AM2R_can_spider(self, state: CollectionState) -> bool: + return state.has('Spiderball', self.player) \ + or self.AM2R_can_fly(state) - def _AM2R_has_ballspark(self, world: MultiWorld, player: int) -> bool: - return self.has_all({'Speed Booster', 'Spring Ball'}, player) + def AM2R_can_schmove(self, state: CollectionState) -> bool: + return self.AM2R_can_spider(state) \ + or state.has('Hi Jump', self.player) - def _AM2R_can_down(self,world: MultiWorld, player: int) -> bool: - return self.has('Speed Booster', player) and self._AM2R_can_bomb(world, player) and self.has('Super Missiles') + def AM2R_has_ballspark(self, state: CollectionState) -> bool: + return state.has_all({'Speed Booster', 'Spring Ball'}, self.player) + + def AM2R_can_down(self, state: CollectionState) -> bool: + return state.has_all({'Speed Booster', 'Ice Beam'}, self.player) + + def AM2R_can_lab(self, state: CollectionState) -> bool: + return state.has_all({'Speed Booster', 'Ice Beam'}, self.player) + +# def AM2R_can_down(self, state: CollectionState) -> bool: +# return state.has('metroids', self.player) # todo make this right + +# def AM2R_can_lab(self, state: CollectionState) -> bool: +# return state.has('omegas', self.player) # todo make this right