From 552a6e7f1c6cac89f7d6766122eaf20717764351 Mon Sep 17 00:00:00 2001 From: Mysteryem Date: Fri, 18 Apr 2025 17:41:46 +0100 Subject: [PATCH 01/46] Stardew Valley: Precollect building items in deterministic order (#4883) #4239 refactored buildings, but introduced iteration of a set when precollecting the building items into start inventory. The iteration order of sets varies between separate Python processes due to set order being partially based on the hashes of the objects in the set and because Python processes each have a random hash seed by default. --- worlds/stardew_valley/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index bf900742b9..bad0ab9e68 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -206,7 +206,8 @@ class StardewValleyWorld(World): if not building_progression.is_progressive: return - for building in building_progression.starting_buildings: + # starting_buildings is a set, so sort for deterministic order. + for building in sorted(building_progression.starting_buildings): item, quantity = building_progression.to_progressive_item(building) for _ in range(quantity): self.multiworld.push_precollected(self.create_item(item)) From 1b3ee0e94fdcc604c246bacedab4d60b9f0eb6cb Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 18 Apr 2025 20:41:09 +0200 Subject: [PATCH 02/46] Core: require clients to support overlapping IDs (#4451) --- MultiServer.py | 3 ++- test/hosting/client.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index 05e93e678d..c9e0ad8bfa 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -46,7 +46,8 @@ from NetUtils import Endpoint, ClientStatus, NetworkItem, decode, encode, Networ SlotType, LocationStore, Hint, HintStatus from BaseClasses import ItemClassification -min_client_version = Version(0, 1, 6) + +min_client_version = Version(0, 5, 0) colorama.just_fix_windows_console() diff --git a/test/hosting/client.py b/test/hosting/client.py index b805bb6a26..01572c442c 100644 --- a/test/hosting/client.py +++ b/test/hosting/client.py @@ -80,8 +80,8 @@ class Client: "version": { "class": "Version", "major": 0, - "minor": 4, - "build": 6, + "minor": 6, + "build": 0, }, "items_handling": 0, "tags": [], From a0c83b48547d0e170de2324ce0a2a851adb2dee6 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 18 Apr 2025 20:49:08 +0200 Subject: [PATCH 03/46] Core: no longer log ID ranges on generate (#4013) Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- Main.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/Main.py b/Main.py index 528db10c64..6f6a09619d 100644 --- a/Main.py +++ b/Main.py @@ -56,29 +56,15 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No logger.info(f"Found {len(AutoWorld.AutoWorldRegister.world_types)} World Types:") longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.world_types) - max_item = 0 - max_location = 0 - for cls in AutoWorld.AutoWorldRegister.world_types.values(): - if cls.item_id_to_name: - max_item = max(max_item, max(cls.item_id_to_name)) - max_location = max(max_location, max(cls.location_id_to_name)) - - item_digits = len(str(max_item)) - location_digits = len(str(max_location)) item_count = len(str(max(len(cls.item_names) for cls in AutoWorld.AutoWorldRegister.world_types.values()))) location_count = len(str(max(len(cls.location_names) for cls in AutoWorld.AutoWorldRegister.world_types.values()))) - del max_item, max_location for name, cls in AutoWorld.AutoWorldRegister.world_types.items(): if not cls.hidden and len(cls.item_names) > 0: - logger.info(f" {name:{longest_name}}: {len(cls.item_names):{item_count}} " - f"Items (IDs: {min(cls.item_id_to_name):{item_digits}} - " - f"{max(cls.item_id_to_name):{item_digits}}) | " - f"{len(cls.location_names):{location_count}} " - f"Locations (IDs: {min(cls.location_id_to_name):{location_digits}} - " - f"{max(cls.location_id_to_name):{location_digits}})") + logger.info(f" {name:{longest_name}}: Items: {len(cls.item_names):{item_count}} | " + f"Locations: {len(cls.location_names):{location_count}}") - del item_digits, location_digits, item_count, location_count + del item_count, location_count # This assertion method should not be necessary to run if we are not outputting any multidata. if not args.skip_output and not args.spoiler_only: From cb3d35faf9c08b1900b310343c5c406eae501bcc Mon Sep 17 00:00:00 2001 From: ScootyPuffJr1 <77215594+ScootyPuffJr1@users.noreply.github.com> Date: Fri, 18 Apr 2025 14:50:51 -0400 Subject: [PATCH 04/46] LttP: Add keydrop locations to location groups (#4465) --- worlds/alttp/__init__.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index e4a04fe67e..4a026f109b 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -141,7 +141,7 @@ class ALTTPWorld(World): item_name_groups = item_name_groups location_name_groups = { "Blind's Hideout": {"Blind's Hideout - Top", "Blind's Hideout - Left", "Blind's Hideout - Right", - "Blind's Hideout - Far Left", "Blind's Hideout - Far Right"}, + "Blind's Hideout - Far Left", "Blind's Hideout - Far Right"}, "Kakariko Well": {"Kakariko Well - Top", "Kakariko Well - Left", "Kakariko Well - Middle", "Kakariko Well - Right", "Kakariko Well - Bottom"}, "Mini Moldorm Cave": {"Mini Moldorm Cave - Far Left", "Mini Moldorm Cave - Left", "Mini Moldorm Cave - Right", @@ -154,15 +154,23 @@ class ALTTPWorld(World): "Hookshot Cave": {"Hookshot Cave - Top Right", "Hookshot Cave - Top Left", "Hookshot Cave - Bottom Right", "Hookshot Cave - Bottom Left"}, "Hyrule Castle": {"Hyrule Castle - Boomerang Chest", "Hyrule Castle - Map Chest", - "Hyrule Castle - Zelda's Chest", "Sewers - Dark Cross", "Sewers - Secret Room - Left", - "Sewers - Secret Room - Middle", "Sewers - Secret Room - Right"}, + "Hyrule Castle - Zelda's Chest", "Hyrule Castle - Big Key Drop", + "Hyrule Castle - Boomerang Guard Key Drop", "Hyrule Castle - Map Guard Key Drop", + "Sewers - Dark Cross", "Sewers - Secret Room - Left", + "Sewers - Secret Room - Middle", "Sewers - Secret Room - Right", + "Sewers - Key Rat Key Drop"}, "Eastern Palace": {"Eastern Palace - Compass Chest", "Eastern Palace - Big Chest", "Eastern Palace - Cannonball Chest", "Eastern Palace - Big Key Chest", + "Eastern Palace - Dark Eyegore Key Drop", "Eastern Palace - Dark Square Pot Key", "Eastern Palace - Map Chest", "Eastern Palace - Boss"}, "Desert Palace": {"Desert Palace - Big Chest", "Desert Palace - Torch", "Desert Palace - Map Chest", - "Desert Palace - Compass Chest", "Desert Palace - Big Key Chest", "Desert Palace - Boss"}, + "Desert Palace - Beamos Hall Pot Key", "Desert Palace - Desert Tiles 1 Pot Key", + "Desert Palace - Desert Tiles 2 Pot Key", "Desert Palace - Compass Chest", + "Desert Palace - Big Key Chest", "Desert Palace - Boss"}, "Tower of Hera": {"Tower of Hera - Basement Cage", "Tower of Hera - Map Chest", "Tower of Hera - Big Key Chest", "Tower of Hera - Compass Chest", "Tower of Hera - Big Chest", "Tower of Hera - Boss"}, + "Castle Tower": {"Castle Tower - Room 03", "Castle Tower - Dark Maze", + "Castle Tower - Dark Archer Key Drop", "Castle Tower - Circle of Pots Key Drop"}, "Palace of Darkness": {"Palace of Darkness - Shooter Room", "Palace of Darkness - The Arena - Bridge", "Palace of Darkness - Stalfos Basement", "Palace of Darkness - Big Key Chest", "Palace of Darkness - The Arena - Ledge", "Palace of Darkness - Map Chest", @@ -173,25 +181,33 @@ class ALTTPWorld(World): "Swamp Palace": {"Swamp Palace - Entrance", "Swamp Palace - Map Chest", "Swamp Palace - Big Chest", "Swamp Palace - Compass Chest", "Swamp Palace - Big Key Chest", "Swamp Palace - West Chest", "Swamp Palace - Flooded Room - Left", "Swamp Palace - Flooded Room - Right", - "Swamp Palace - Waterfall Room", "Swamp Palace - Boss"}, + "Swamp Palace - Hookshot Pot Key", "Swamp Palace - Pot Row Pot Key", + "Swamp Palace - Trench 1 Pot Key", "Swamp Palace - Trench 2 Pot Key", + "Swamp Palace - Waterway Pot Key", "Swamp Palace - Waterfall Room", "Swamp Palace - Boss"}, "Thieves' Town": {"Thieves' Town - Big Key Chest", "Thieves' Town - Map Chest", "Thieves' Town - Compass Chest", "Thieves' Town - Ambush Chest", "Thieves' Town - Attic", "Thieves' Town - Big Chest", + "Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key", "Thieves' Town - Blind's Cell", "Thieves' Town - Boss"}, "Skull Woods": {"Skull Woods - Map Chest", "Skull Woods - Pinball Room", "Skull Woods - Compass Chest", "Skull Woods - Pot Prison", "Skull Woods - Big Chest", "Skull Woods - Big Key Chest", + "Skull Woods - Spike Corner Key Drop", "Skull Woods - West Lobby Pot Key", "Skull Woods - Bridge Room", "Skull Woods - Boss"}, "Ice Palace": {"Ice Palace - Compass Chest", "Ice Palace - Freezor Chest", "Ice Palace - Big Chest", "Ice Palace - Freezor Chest", "Ice Palace - Big Chest", "Ice Palace - Iced T Room", "Ice Palace - Spike Room", "Ice Palace - Big Key Chest", "Ice Palace - Map Chest", + "Ice Palace - Conveyor Key Drop", "Ice Palace - Hammer Block Key Drop", + "Ice Palace - Jelly Key Drop", "Ice Palace - Many Pots Pot Key", "Ice Palace - Boss"}, "Misery Mire": {"Misery Mire - Big Chest", "Misery Mire - Map Chest", "Misery Mire - Main Lobby", "Misery Mire - Bridge Chest", "Misery Mire - Spike Chest", "Misery Mire - Compass Chest", - "Misery Mire - Big Key Chest", "Misery Mire - Boss"}, + "Misery Mire - Conveyor Crystal Key Drop", "Misery Mire - Fishbone Pot Key", + "Misery Mire - Spikes Pot Key", "Misery Mire - Big Key Chest", "Misery Mire - Boss"}, "Turtle Rock": {"Turtle Rock - Compass Chest", "Turtle Rock - Roller Room - Left", "Turtle Rock - Roller Room - Right", "Turtle Rock - Chain Chomps", "Turtle Rock - Big Key Chest", "Turtle Rock - Big Chest", "Turtle Rock - Crystaroller Room", "Turtle Rock - Eye Bridge - Bottom Left", "Turtle Rock - Eye Bridge - Bottom Right", "Turtle Rock - Eye Bridge - Top Left", "Turtle Rock - Eye Bridge - Top Right", + "Turtle Rock - Pokey 1 Key Drop", "Turtle Rock - Pokey 2 Key Drop", "Turtle Rock - Boss"}, "Ganons Tower": {"Ganons Tower - Bob's Torch", "Ganons Tower - Hope Room - Left", "Ganons Tower - Hope Room - Right", "Ganons Tower - Tile Room", @@ -204,10 +220,13 @@ class ALTTPWorld(World): "Ganons Tower - Randomizer Room - Bottom Left", "Ganons Tower - Randomizer Room - Bottom Right", "Ganons Tower - Bob's Chest", "Ganons Tower - Big Chest", "Ganons Tower - Big Key Room - Left", "Ganons Tower - Big Key Room - Right", "Ganons Tower - Big Key Chest", - "Ganons Tower - Mini Helmasaur Room - Left", "Ganons Tower - Mini Helmasaur Room - Right", - "Ganons Tower - Pre-Moldorm Chest", "Ganons Tower - Validation Chest"}, + "Ganons Tower - Conveyor Cross Pot Key", "Ganons Tower - Conveyor Star Pits Pot Key", + "Ganons Tower - Double Switch Pot Key", "Ganons Tower - Mini Helmasaur Room - Left", + "Ganons Tower - Mini Helmasaur Room - Right", "Ganons Tower - Pre-Moldorm Chest", + "Ganons Tower - Mini Helmasaur Key Drop", "Ganons Tower - Validation Chest"}, "Ganons Tower Climb": {"Ganons Tower - Mini Helmasaur Room - Left", "Ganons Tower - Mini Helmasaur Room - Right", - "Ganons Tower - Pre-Moldorm Chest", "Ganons Tower - Validation Chest"}, + "Ganons Tower - Mini Helmasaur Key Drop", "Ganons Tower - Pre-Moldorm Chest", + "Ganons Tower - Validation Chest"}, } hint_blacklist = {"Triforce"} From 1b51714f3b734d0c43e6fdffa006c692b12f8c50 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Fri, 18 Apr 2025 16:34:34 -0500 Subject: [PATCH 05/46] LTTP: Rip Lttp specific entrance code out of core and use Region helpers (#1960) --- BaseClasses.py | 7 +------ worlds/alttp/OverworldGlitchRules.py | 10 ++-------- worlds/alttp/Regions.py | 8 ++++---- worlds/alttp/Rules.py | 8 ++++---- worlds/alttp/SubClasses.py | 17 +++++++++++++++-- worlds/alttp/UnderworldGlitchRules.py | 14 ++++++-------- worlds/pokemon_rb/regions.py | 6 +++++- 7 files changed, 37 insertions(+), 33 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 4074108b4b..ec3fa9cef1 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1022,9 +1022,6 @@ class Entrance: connected_region: Optional[Region] = None randomization_group: int randomization_type: EntranceType - # LttP specific, TODO: should make a LttPEntrance - addresses = None - target = None def __init__(self, player: int, name: str = "", parent: Optional[Region] = None, randomization_group: int = 0, randomization_type: EntranceType = EntranceType.ONE_WAY) -> None: @@ -1043,10 +1040,8 @@ class Entrance: return False - def connect(self, region: Region, addresses: Any = None, target: Any = None) -> None: + def connect(self, region: Region) -> None: self.connected_region = region - self.target = target - self.addresses = addresses region.entrances.append(self) def is_valid_source_transition(self, er_state: "ERPlacementState") -> bool: diff --git a/worlds/alttp/OverworldGlitchRules.py b/worlds/alttp/OverworldGlitchRules.py index 2da76234bd..1a1c01525d 100644 --- a/worlds/alttp/OverworldGlitchRules.py +++ b/worlds/alttp/OverworldGlitchRules.py @@ -2,8 +2,6 @@ Helper functions to deliver entrance/exit/region sets to OWG rules. """ -from BaseClasses import Entrance - from .StateHelpers import can_lift_heavy_rocks, can_boots_clip_lw, can_boots_clip_dw, can_get_glitched_speed_dw @@ -279,18 +277,14 @@ def create_no_logic_connections(player, world, connections): for entrance, parent_region, target_region, *rule_override in connections: parent = world.get_region(parent_region, player) target = world.get_region(target_region, player) - connection = Entrance(player, entrance, parent) - parent.exits.append(connection) - connection.connect(target) + parent.connect(target, entrance) def create_owg_connections(player, world, connections): for entrance, parent_region, target_region, *rule_override in connections: parent = world.get_region(parent_region, player) target = world.get_region(target_region, player) - connection = Entrance(player, entrance, parent) - parent.exits.append(connection) - connection.connect(target) + parent.connect(target, entrance) def set_owg_connection_rules(player, world, connections, default_rule): diff --git a/worlds/alttp/Regions.py b/worlds/alttp/Regions.py index f3dbbdc059..c2af795637 100644 --- a/worlds/alttp/Regions.py +++ b/worlds/alttp/Regions.py @@ -1,11 +1,11 @@ import collections import typing -from BaseClasses import Entrance, MultiWorld -from .SubClasses import LTTPRegion, LTTPRegionType +from BaseClasses import MultiWorld +from .SubClasses import LTTPEntrance, LTTPRegion, LTTPRegionType -def is_main_entrance(entrance: Entrance) -> bool: +def is_main_entrance(entrance: LTTPEntrance) -> bool: return entrance.parent_region.type in {LTTPRegionType.DarkWorld, LTTPRegionType.LightWorld} if entrance.parent_region.type else True @@ -410,7 +410,7 @@ def _create_region(world: MultiWorld, player: int, name: str, type: LTTPRegionTy ret = LTTPRegion(name, type, hint, player, world) if exits: for exit in exits: - ret.exits.append(Entrance(player, exit, ret)) + ret.create_exit(exit) if locations: for location in locations: if location in key_drop_data: diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 47992947ac..3f5081129a 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -3,7 +3,7 @@ import logging from typing import Iterator, Set from Options import ItemsAccessibility -from BaseClasses import Entrance, MultiWorld +from BaseClasses import MultiWorld from worlds.generic.Rules import (add_item_rule, add_rule, forbid_item, item_name_in_location_names, location_item_name, set_rule, allow_self_locking_items) @@ -1071,9 +1071,8 @@ def swordless_rules(world, player): def add_connection(parent_name, target_name, entrance_name, world, player): parent = world.get_region(parent_name, player) target = world.get_region(target_name, player) - connection = Entrance(player, entrance_name, parent) - parent.exits.append(connection) - connection.connect(target) + parent.connect(target, entrance_name) + def standard_rules(world, player): @@ -1108,6 +1107,7 @@ def standard_rules(world, player): set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state.has('Big Key (Hyrule Castle)', player)) + def toss_junk_item(world, player): items = ['Rupees (20)', 'Bombs (3)', 'Arrows (10)', 'Rupees (5)', 'Rupee (1)', 'Bombs (10)', 'Single Arrow', 'Rupees (50)', 'Rupees (100)', 'Single Bomb', 'Bee', 'Bee Trap', diff --git a/worlds/alttp/SubClasses.py b/worlds/alttp/SubClasses.py index 328e28da93..a3b1d778d6 100644 --- a/worlds/alttp/SubClasses.py +++ b/worlds/alttp/SubClasses.py @@ -2,11 +2,10 @@ from typing import Optional, TYPE_CHECKING from enum import IntEnum -from BaseClasses import Location, Item, ItemClassification, Region, MultiWorld +from BaseClasses import Entrance, Location, Item, ItemClassification, Region, MultiWorld if TYPE_CHECKING: from .Dungeons import Dungeon - from .Regions import LTTPRegion class ALttPLocation(Location): @@ -77,6 +76,19 @@ class ALttPItem(Item): return self.type +Addresses = int | list[int] | tuple[int, int, int, int, int, int, int, int, int, int, int, int, int] + + +class LTTPEntrance(Entrance): + addresses: Addresses | None = None + target: int | None = None + + def connect(self, region: Region, addresses: Addresses | None = None, target: int | None = None) -> None: + super().connect(region) + self.addresses = addresses + self.target = target + + class LTTPRegionType(IntEnum): LightWorld = 1 DarkWorld = 2 @@ -90,6 +102,7 @@ class LTTPRegionType(IntEnum): class LTTPRegion(Region): + entrance_type = LTTPEntrance type: LTTPRegionType # will be set after making connections. diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index 50397dea16..2b18f67ed9 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -1,6 +1,6 @@ -from BaseClasses import Entrance from worlds.generic.Rules import set_rule, add_rule from .StateHelpers import can_bomb_clip, has_sword, has_beam_sword, has_fire_source, can_melt_things, has_misery_mire_medallion +from .SubClasses import LTTPEntrance # We actually need the logic to properly "mark" these regions as Light or Dark world. @@ -9,17 +9,15 @@ def underworld_glitch_connections(world, player): specrock = world.get_region('Spectacle Rock Cave (Bottom)', player) mire = world.get_region('Misery Mire (West)', player) - kikiskip = Entrance(player, 'Kiki Skip', specrock) - mire_to_hera = Entrance(player, 'Mire to Hera Clip', mire) - mire_to_swamp = Entrance(player, 'Hera to Swamp Clip', mire) - specrock.exits.append(kikiskip) - mire.exits.extend([mire_to_hera, mire_to_swamp]) + kikiskip = specrock.create_exit('Kiki Skip') + mire_to_hera = mire.create_exit('Mire to Hera Clip') + mire_to_swamp = mire.create_exit('Hera to Swamp Clip') if world.worlds[player].fix_fake_world: kikiskip.connect(world.get_entrance('Palace of Darkness Exit', player).connected_region) mire_to_hera.connect(world.get_entrance('Tower of Hera Exit', player).connected_region) mire_to_swamp.connect(world.get_entrance('Swamp Palace Exit', player).connected_region) - else: + else: kikiskip.connect(world.get_region('Palace of Darkness (Entrance)', player)) mire_to_hera.connect(world.get_region('Tower of Hera (Bottom)', player)) mire_to_swamp.connect(world.get_region('Swamp Palace (Entrance)', player)) @@ -37,7 +35,7 @@ def fake_pearl_state(state, player): # Sets the rules on where we can actually go using this clip. # Behavior differs based on what type of ER shuffle we're playing. -def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, dungeon_exit: str): +def dungeon_reentry_rules(world, player, clip: LTTPEntrance, dungeon_region: str, dungeon_exit: str): fix_dungeon_exits = world.worlds[player].fix_palaceofdarkness_exit fix_fake_worlds = world.worlds[player].fix_fake_world diff --git a/worlds/pokemon_rb/regions.py b/worlds/pokemon_rb/regions.py index 84c9b25735..5aa6243514 100644 --- a/worlds/pokemon_rb/regions.py +++ b/worlds/pokemon_rb/regions.py @@ -2640,9 +2640,13 @@ class PokemonRBWarp(Entrance): self.warp_id = warp_id self.address = address self.flags = flags + self.addresses = None + self.target = None def connect(self, entrance): - super().connect(entrance.parent_region, None, target=entrance.warp_id) + super().connect(entrance.parent_region) + self.addresses = None + self.target = entrance.warp_id def access_rule(self, state): if self.connected_region is None: From 57a716b57a7df9a6ca3ad2c4d2d7e5169bde7021 Mon Sep 17 00:00:00 2001 From: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> Date: Fri, 18 Apr 2025 17:41:38 -0400 Subject: [PATCH 06/46] LTTP: Update to options API (#4134) Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/alttp/Bosses.py | 13 +- worlds/alttp/Dungeons.py | 4 +- worlds/alttp/EntranceShuffle.py | 100 +++--- worlds/alttp/ItemPool.py | 168 ++++----- worlds/alttp/Items.py | 4 +- worlds/alttp/Options.py | 6 +- worlds/alttp/OverworldGlitchRules.py | 20 +- worlds/alttp/Rom.py | 339 +++++++++--------- worlds/alttp/Rules.py | 155 ++++---- worlds/alttp/Shops.py | 56 +-- worlds/alttp/StateHelpers.py | 26 +- worlds/alttp/UnderworldGlitchRules.py | 6 +- worlds/alttp/__init__.py | 141 ++++---- worlds/alttp/test/dungeons/TestDungeon.py | 4 +- worlds/alttp/test/inverted/TestInverted.py | 6 +- .../test/inverted/TestInvertedBombRules.py | 2 +- .../TestInvertedMinor.py | 8 +- .../test/inverted_owg/TestInvertedOWG.py | 8 +- worlds/alttp/test/minor_glitches/TestMinor.py | 6 +- worlds/alttp/test/options/TestOpenPyramid.py | 2 +- worlds/alttp/test/owg/TestVanillaOWG.py | 6 +- worlds/alttp/test/vanilla/TestVanilla.py | 6 +- 22 files changed, 532 insertions(+), 554 deletions(-) diff --git a/worlds/alttp/Bosses.py b/worlds/alttp/Bosses.py index 02970edb9f..54686f45ad 100644 --- a/worlds/alttp/Bosses.py +++ b/worlds/alttp/Bosses.py @@ -102,7 +102,7 @@ def KholdstareDefeatRule(state, player: int) -> bool: state.has('Fire Rod', player) or ( state.has('Bombos', player) and - (has_sword(state, player) or state.multiworld.swordless[player]) + (has_sword(state, player) or state.multiworld.worlds[player].options.swordless) ) ) and ( @@ -111,7 +111,7 @@ def KholdstareDefeatRule(state, player: int) -> bool: ( state.has('Fire Rod', player) and state.has('Bombos', player) and - state.multiworld.swordless[player] and + state.multiworld.worlds[player].options.swordless and can_extend_magic(state, player, 16) ) ) @@ -137,7 +137,7 @@ def AgahnimDefeatRule(state, player: int) -> bool: def GanonDefeatRule(state, player: int) -> bool: - if state.multiworld.swordless[player]: + if state.multiworld.worlds[player].options.swordless: return state.has('Hammer', player) and \ has_fire_source(state, player) and \ state.has('Silver Bow', player) and \ @@ -146,7 +146,7 @@ def GanonDefeatRule(state, player: int) -> bool: can_hurt = has_beam_sword(state, player) common = can_hurt and has_fire_source(state, player) # silverless ganon may be needed in anything higher than no glitches - if state.multiworld.glitches_required[player] != 'no_glitches': + if state.multiworld.worlds[player].options.glitches_required != 'no_glitches': # need to light torch a sufficient amount of times return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or ( state.has('Silver Bow', player) and can_shoot_arrows(state, player)) or @@ -248,7 +248,7 @@ for location in boss_location_table: def place_boss(world: "ALTTPWorld", boss: str, location: str, level: Optional[str]) -> None: player = world.player - if location == 'Ganons Tower' and world.multiworld.mode[player] == 'inverted': + if location == 'Ganons Tower' and world.options.mode == 'inverted': location = 'Inverted Ganons Tower' logging.debug('Placing boss %s at %s', boss, location + (' (' + level + ')' if level else '')) world.dungeons[location].bosses[level] = BossFactory(boss, player) @@ -260,9 +260,8 @@ def format_boss_location(location_name: str, level: str) -> str: def place_bosses(world: "ALTTPWorld") -> None: multiworld = world.multiworld - player = world.player # will either be an int or a lower case string with ';' between options - boss_shuffle: Union[str, int] = multiworld.boss_shuffle[player].value + boss_shuffle: Union[str, int] = world.options.boss_shuffle.value already_placed_bosses: List[str] = [] remaining_locations: List[Tuple[str, str]] = [] # handle plando diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py index 8405fc4480..39e8d7072b 100644 --- a/worlds/alttp/Dungeons.py +++ b/worlds/alttp/Dungeons.py @@ -66,7 +66,7 @@ def create_dungeons(world: "ALTTPWorld"): def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items): dungeon = Dungeon(name, dungeon_regions, big_key, - [] if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal else small_keys, + [] if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal else small_keys, dungeon_items, player) for item in dungeon.all_items: item.dungeon = dungeon @@ -143,7 +143,7 @@ def create_dungeons(world: "ALTTPWorld"): item_factory(['Small Key (Turtle Rock)'] * 6, world), item_factory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], world)) - if multiworld.mode[player] != 'inverted': + if multiworld.worlds[player].options.mode != 'inverted': AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None, item_factory(['Small Key (Agahnims Tower)'] * 4, world), []) GT = make_dungeon('Ganons Tower', 'Agahnim2', diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index d0487494aa..c062a17ea6 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -23,17 +23,17 @@ def link_entrances(world, player): connect_simple(world, exitname, regionname, player) # if we do not shuffle, set default connections - if world.entrance_shuffle[player] == 'vanilla': + if world.worlds[player].options.entrance_shuffle == 'vanilla': for exitname, regionname in default_connections: connect_simple(world, exitname, regionname, player) for exitname, regionname in default_dungeon_connections: connect_simple(world, exitname, regionname, player) - elif world.entrance_shuffle[player] == 'dungeons_simple': + elif world.worlds[player].options.entrance_shuffle == 'dungeons_simple': for exitname, regionname in default_connections: connect_simple(world, exitname, regionname, player) simple_shuffle_dungeons(world, player) - elif world.entrance_shuffle[player] == 'dungeons_full': + elif world.worlds[player].options.entrance_shuffle == 'dungeons_full': for exitname, regionname in default_connections: connect_simple(world, exitname, regionname, player) @@ -43,7 +43,7 @@ def link_entrances(world, player): lw_entrances = list(LW_Dungeon_Entrances) dw_entrances = list(DW_Dungeon_Entrances) - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # must connect front of hyrule castle to do escape connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: @@ -56,7 +56,7 @@ def link_entrances(world, player): dw_entrances.append('Ganons Tower') dungeon_exits.append('Ganons Tower Exit') - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # rest of hyrule castle must be in light world, so it has to be the one connected to east exit of desert hyrule_castle_exits = [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')] connect_mandatory_exits(world, lw_entrances, hyrule_castle_exits, list(LW_Dungeon_Entrances_Must_Exit), player) @@ -65,9 +65,9 @@ def link_entrances(world, player): connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) - elif world.entrance_shuffle[player] == 'dungeons_crossed': + elif world.worlds[player].options.entrance_shuffle == 'dungeons_crossed': crossed_shuffle_dungeons(world, player) - elif world.entrance_shuffle[player] == 'simple': + elif world.worlds[player].options.entrance_shuffle == 'simple': simple_shuffle_dungeons(world, player) old_man_entrances = list(Old_Man_Entrances) @@ -138,7 +138,7 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, single_doors, door_targets, player) - elif world.entrance_shuffle[player] == 'restricted': + elif world.worlds[player].options.entrance_shuffle == 'restricted': simple_shuffle_dungeons(world, player) lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) @@ -210,7 +210,7 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.entrance_shuffle[player] == 'full': + elif world.worlds[player].options.entrance_shuffle == 'full': skull_woods_shuffle(world, player) lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances) @@ -227,7 +227,7 @@ def link_entrances(world, player): # tavern back door cannot be shuffled yet connect_doors(world, ['Tavern North'], ['Tavern'], player) - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # must connect front of hyrule castle to do escape connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: @@ -264,7 +264,7 @@ def link_entrances(world, player): pass else: #if the cave wasn't placed we get here connect_caves(world, lw_entrances, [], old_man_house, player) - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # rest of hyrule castle must be in light world connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) @@ -316,7 +316,7 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.entrance_shuffle[player] == 'crossed': + elif world.worlds[player].options.entrance_shuffle == 'crossed': skull_woods_shuffle(world, player) entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors) @@ -331,7 +331,7 @@ def link_entrances(world, player): # tavern back door cannot be shuffled yet connect_doors(world, ['Tavern North'], ['Tavern'], player) - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # must connect front of hyrule castle to do escape connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: @@ -348,7 +348,7 @@ def link_entrances(world, player): #place must-exit caves connect_mandatory_exits(world, entrances, caves, must_exits, player) - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # rest of hyrule castle must be dealt with connect_caves(world, entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) @@ -394,7 +394,7 @@ def link_entrances(world, player): # place remaining doors connect_doors(world, entrances, door_targets, player) - elif world.entrance_shuffle[player] == 'insanity': + elif world.worlds[player].options.entrance_shuffle == 'insanity': # beware ye who enter here entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave'] @@ -431,7 +431,7 @@ def link_entrances(world, player): # tavern back door cannot be shuffled yet connect_doors(world, ['Tavern North'], ['Tavern'], player) - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # cannot move uncle cave connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player) @@ -464,7 +464,7 @@ def link_entrances(world, player): connect_entrance(world, hole, hole_targets.pop(), player) # hyrule castle handling - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # must connect front of hyrule castle to do escape connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player) @@ -544,12 +544,12 @@ def link_entrances(world, player): else: raise NotImplementedError( - f'{world.entrance_shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}') + f'{world.worlds[player].options.entrance_shuffle} Shuffling not supported yet. Player {world.get_player_name(player)}') - if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: + if world.worlds[player].options.glitches_required in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: overworld_glitch_connections(world, player) # mandatory hybrid major glitches connections - if world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: + if world.worlds[player].options.glitches_required in ['hybrid_major_glitches', 'no_logic']: underworld_glitch_connections(world, player) # check for swamp palace fix @@ -584,17 +584,17 @@ def link_inverted_entrances(world, player): connect_simple(world, exitname, regionname, player) # if we do not shuffle, set default connections - if world.entrance_shuffle[player] == 'vanilla': + if world.worlds[player].options.entrance_shuffle == 'vanilla': for exitname, regionname in inverted_default_connections: connect_simple(world, exitname, regionname, player) for exitname, regionname in inverted_default_dungeon_connections: connect_simple(world, exitname, regionname, player) - elif world.entrance_shuffle[player] == 'dungeons_simple': + elif world.worlds[player].options.entrance_shuffle == 'dungeons_simple': for exitname, regionname in inverted_default_connections: connect_simple(world, exitname, regionname, player) simple_shuffle_dungeons(world, player) - elif world.entrance_shuffle[player] == 'dungeons_full': + elif world.worlds[player].options.entrance_shuffle == 'dungeons_full': for exitname, regionname in inverted_default_connections: connect_simple(world, exitname, regionname, player) @@ -649,9 +649,9 @@ def link_inverted_entrances(world, player): connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player) connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player) - elif world.entrance_shuffle[player] == 'dungeons_crossed': + elif world.worlds[player].options.entrance_shuffle == 'dungeons_crossed': inverted_crossed_shuffle_dungeons(world, player) - elif world.entrance_shuffle[player] == 'simple': + elif world.worlds[player].options.entrance_shuffle == 'simple': simple_shuffle_dungeons(world, player) old_man_entrances = list(Inverted_Old_Man_Entrances) @@ -748,7 +748,7 @@ def link_inverted_entrances(world, player): # place remaining doors connect_doors(world, single_doors, door_targets, player) - elif world.entrance_shuffle[player] == 'restricted': + elif world.worlds[player].options.entrance_shuffle == 'restricted': simple_shuffle_dungeons(world, player) lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors) @@ -833,7 +833,7 @@ def link_inverted_entrances(world, player): doors = lw_entrances + dw_entrances # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.entrance_shuffle[player] == 'full': + elif world.worlds[player].options.entrance_shuffle == 'full': skull_woods_shuffle(world, player) lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors) @@ -984,7 +984,7 @@ def link_inverted_entrances(world, player): # place remaining doors connect_doors(world, doors, door_targets, player) - elif world.entrance_shuffle[player] == 'crossed': + elif world.worlds[player].options.entrance_shuffle == 'crossed': skull_woods_shuffle(world, player) entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors) @@ -1095,7 +1095,7 @@ def link_inverted_entrances(world, player): # place remaining doors connect_doors(world, entrances, door_targets, player) - elif world.entrance_shuffle[player] == 'insanity': + elif world.worlds[player].options.entrance_shuffle == 'insanity': # beware ye who enter here entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)'] @@ -1254,10 +1254,10 @@ def link_inverted_entrances(world, player): else: raise NotImplementedError('Shuffling not supported yet') - if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: + if world.worlds[player].options.glitches_required in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: overworld_glitch_connections(world, player) # mandatory hybrid major glitches connections - if world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: + if world.worlds[player].options.glitches_required in ['hybrid_major_glitches', 'no_logic']: underworld_glitch_connections(world, player) # patch swamp drain @@ -1349,7 +1349,7 @@ def scramble_holes(world, player): else: hole_targets.append(('Pyramid Exit', 'Pyramid')) - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # cannot move uncle cave connect_two_way(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player) connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player) @@ -1358,14 +1358,14 @@ def scramble_holes(world, player): hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) # do not shuffle sanctuary into pyramid hole unless shuffle is crossed - if world.entrance_shuffle[player] == 'crossed': + if world.worlds[player].options.entrance_shuffle == 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) if world.shuffle_ganon: world.random.shuffle(hole_targets) exit, target = hole_targets.pop() connect_two_way(world, 'Pyramid Entrance', exit, player) connect_entrance(world, 'Pyramid Hole', target, player) - if world.entrance_shuffle[player] != 'crossed': + if world.worlds[player].options.entrance_shuffle != 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) world.random.shuffle(hole_targets) @@ -1400,14 +1400,14 @@ def scramble_inverted_holes(world, player): hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance')) # do not shuffle sanctuary into pyramid hole unless shuffle is crossed - if world.entrance_shuffle[player] == 'crossed': + if world.worlds[player].options.entrance_shuffle == 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) if world.shuffle_ganon: world.random.shuffle(hole_targets) exit, target = hole_targets.pop() connect_two_way(world, 'Inverted Pyramid Entrance', exit, player) connect_entrance(world, 'Inverted Pyramid Hole', target, player) - if world.entrance_shuffle[player] != 'crossed': + if world.worlds[player].options.entrance_shuffle != 'crossed': hole_targets.append(('Sanctuary Exit', 'Sewer Drop')) world.random.shuffle(hole_targets) @@ -1430,15 +1430,15 @@ def connect_random(world, exitlist, targetlist, player, two_way=False): def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): # Keeps track of entrances that cannot be used to access each exit / cave - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': invalid_connections = Inverted_Must_Exit_Invalid_Connections.copy() else: invalid_connections = Must_Exit_Invalid_Connections.copy() invalid_cave_connections = defaultdict(set) - if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: + if world.worlds[player].options.glitches_required in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: from . import OverworldGlitchRules - for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'): + for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.worlds[player].options.mode == 'inverted'): invalid_connections[entrance] = set() if entrance in must_be_exits: must_be_exits.remove(entrance) @@ -1449,7 +1449,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): world.random.shuffle(caves) # Handle inverted Aga Tower - if it depends on connections, then so does Hyrule Castle Ledge - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': for entrance in invalid_connections: if world.get_entrance(entrance, player).connected_region == world.get_region('Inverted Agahnims Tower', player): @@ -1490,7 +1490,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player): entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit]) cave_entrances.append(entrance) entrances.remove(entrance) - connect_two_way(world,entrance,cave_exit, player) + connect_two_way(world, entrance, cave_exit, player) if entrance not in invalid_connections: invalid_connections[exit] = set() if all(entrance in invalid_connections for entrance in cave_entrances): @@ -1564,7 +1564,7 @@ def simple_shuffle_dungeons(world, player): dungeon_entrances = ['Eastern Palace', 'Tower of Hera', 'Thieves Town', 'Skull Woods Final Section', 'Palace of Darkness', 'Ice Palace', 'Misery Mire', 'Swamp Palace'] dungeon_exits = ['Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Palace of Darkness Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Swamp Palace Exit'] - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': if not world.shuffle_ganon: connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player) else: @@ -1579,13 +1579,13 @@ def simple_shuffle_dungeons(world, player): # mix up 4 door dungeons multi_dungeons = ['Desert', 'Turtle Rock'] - if world.mode[player] == 'open' or (world.mode[player] == 'inverted' and world.shuffle_ganon): + if world.worlds[player].options.mode == 'open' or (world.worlds[player].options.mode == 'inverted' and world.shuffle_ganon): multi_dungeons.append('Hyrule Castle') world.random.shuffle(multi_dungeons) dp_target = multi_dungeons[0] tr_target = multi_dungeons[1] - if world.mode[player] not in ['open', 'inverted'] or (world.mode[player] == 'inverted' and world.shuffle_ganon is False): + if world.worlds[player].options.mode not in ['open', 'inverted'] or (world.worlds[player].options.mode == 'inverted' and world.shuffle_ganon is False): # place hyrule castle as intended hc_target = 'Hyrule Castle' else: @@ -1593,7 +1593,7 @@ def simple_shuffle_dungeons(world, player): # ToDo improve this? - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': if hc_target == 'Hyrule Castle': connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) connect_two_way(world, 'Hyrule Castle Entrance (East)', 'Hyrule Castle Exit (East)', player) @@ -1708,7 +1708,7 @@ def crossed_shuffle_dungeons(world, player: int): dungeon_entrances.append('Ganons Tower') dungeon_exits.append('Ganons Tower Exit') - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': # must connect front of hyrule castle to do escape connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player) else: @@ -1718,7 +1718,7 @@ def crossed_shuffle_dungeons(world, player: int): connect_mandatory_exits(world, dungeon_entrances, dungeon_exits, LW_Dungeon_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit, player) - if world.mode[player] == 'standard': + if world.worlds[player].options.mode == 'standard': connect_caves(world, dungeon_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player) connect_caves(world, dungeon_entrances, [], dungeon_exits, player) @@ -1823,14 +1823,14 @@ lookup = { def plando_connect(world, player: int): - if world.plando_connections[player]: - for connection in world.plando_connections[player]: + if world.worlds[player].options.plando_connections: + for connection in world.worlds[player].options.plando_connections: func = lookup[connection.direction] try: func(world, connection.entrance, connection.exit, player) except Exception as e: raise Exception(f"Could not connect using {connection}") from e - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': mark_light_world_regions(world, player) else: mark_dark_world_regions(world, player) diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 77d02f9770..2b99ef8a73 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -226,25 +226,25 @@ def generate_itempool(world): player = world.player multiworld = world.multiworld - if multiworld.item_pool[player].current_key not in difficulties: - raise NotImplementedError(f"Diffulty {multiworld.item_pool[player]}") - if multiworld.goal[player] not in ('ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt', - 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals', - 'ganon_pedestal'): - raise NotImplementedError(f"Goal {multiworld.goal[player]} for player {player}") - if multiworld.mode[player] not in ('open', 'standard', 'inverted'): - raise NotImplementedError(f"Mode {multiworld.mode[player]} for player {player}") - if multiworld.timer[player] not in (False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'): - raise NotImplementedError(f"Timer {multiworld.timer[player]} for player {player}") + if world.options.item_pool.current_key not in difficulties: + raise NotImplementedError(f"Diffulty {world.options.item_pool}") + if world.options.goal not in ('ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt', + 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals', + 'ganon_pedestal'): + raise NotImplementedError(f"Goal {world.options.goal} for player {player}") + if world.options.mode not in ('open', 'standard', 'inverted'): + raise NotImplementedError(f"Mode {world.options.mode} for player {player}") + if world.options.timer not in (False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'): + raise NotImplementedError(f"Timer {world.options.timer} for player {player}") - if multiworld.timer[player] in ['ohko', 'timed_ohko']: + if world.options.timer in ['ohko', 'timed_ohko']: world.can_take_damage = False - if multiworld.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: + if world.options.goal in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Nothing', world), False) else: multiworld.push_item(multiworld.get_location('Ganon', player), item_factory('Triforce', world), False) - if multiworld.goal[player] in ['triforce_hunt', 'local_triforce_hunt']: + if world.options.goal in ['triforce_hunt', 'local_triforce_hunt']: region = multiworld.get_region('Light World', player) loc = ALttPLocation(player, "Murahdahla", parent=region) @@ -288,7 +288,7 @@ def generate_itempool(world): for item in precollected_items: multiworld.push_precollected(item_factory(item, world)) - if multiworld.mode[player] == 'standard' and not has_melee_weapon(multiworld.state, player): + if world.options.mode == 'standard' and not has_melee_weapon(multiworld.state, player): if "Link's Uncle" not in placed_items: found_sword = False found_bow = False @@ -304,10 +304,10 @@ def generate_itempool(world): elif item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']: if item not in possible_weapons: possible_weapons.append(item) - elif (item == 'Bombs (10)' and (not multiworld.bombless_start[player]) and item not in + elif (item == 'Bombs (10)' and (not world.options.bombless_start) and item not in possible_weapons): possible_weapons.append(item) - elif (item in ['Bomb Upgrade (+10)', 'Bomb Upgrade (50)'] and multiworld.bombless_start[player] and item + elif (item in ['Bomb Upgrade (+10)', 'Bomb Upgrade (50)'] and world.options.bombless_start and item not in possible_weapons): possible_weapons.append(item) @@ -315,21 +315,21 @@ def generate_itempool(world): placed_items["Link's Uncle"] = starting_weapon pool.remove(starting_weapon) if (placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Bomb Upgrade (+10)', - 'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and multiworld.enemy_health[player] not in ['default', 'easy']): - if multiworld.bombless_start[player] and "Bomb Upgrade" not in placed_items["Link's Uncle"]: + 'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and world.options.enemy_health not in ['default', 'easy']): + if world.options.bombless_start and "Bomb Upgrade" not in placed_items["Link's Uncle"]: if 'Bow' in placed_items["Link's Uncle"]: - multiworld.worlds[player].escape_assist.append('arrows') + world.escape_assist.append('arrows') elif 'Cane' in placed_items["Link's Uncle"]: - multiworld.worlds[player].escape_assist.append('magic') + world.escape_assist.append('magic') else: - multiworld.worlds[player].escape_assist.append('bombs') + world.escape_assist.append('bombs') for (location, item) in placed_items.items(): multiworld.get_location(location, player).place_locked_item(item_factory(item, world)) items = item_factory(pool, world) # convert one Progressive Bow into Progressive Bow (Alt), in ID only, for ganon silvers hint text - if multiworld.worlds[player].has_progressive_bows: + if world.has_progressive_bows: for item in items: if item.code == 0x64: # Progressive Bow item.code = 0x65 # Progressive Bow (Alt) @@ -338,21 +338,21 @@ def generate_itempool(world): if clock_mode: world.clock_mode = clock_mode - multiworld.worlds[player].treasure_hunt_required = treasure_hunt_required % 999 - multiworld.worlds[player].treasure_hunt_total = treasure_hunt_total + world.treasure_hunt_required = treasure_hunt_required % 999 + world.treasure_hunt_total = treasure_hunt_total dungeon_items = [item for item in get_dungeon_item_pool_player(world) - if item.name not in multiworld.worlds[player].dungeon_local_item_names] + if item.name not in world.dungeon_local_item_names] for key_loc in key_drop_data: key_data = key_drop_data[key_loc] drop_item = item_factory(key_data[3], world) - if not multiworld.key_drop_shuffle[player]: + if not world.options.key_drop_shuffle: if drop_item in dungeon_items: dungeon_items.remove(drop_item) else: dungeon = drop_item.name.split("(")[1].split(")")[0] - if multiworld.mode[player] == 'inverted': + if world.options.mode == 'inverted': if dungeon == "Agahnims Tower": dungeon = "Inverted Agahnims Tower" if dungeon == "Ganons Tower": @@ -365,7 +365,7 @@ def generate_itempool(world): loc = multiworld.get_location(key_loc, player) loc.place_locked_item(drop_item) loc.address = None - elif "Small" in key_data[3] and multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: + elif "Small" in key_data[3] and world.options.small_key_shuffle == small_key_shuffle.option_universal: # key drop shuffle and universal keys are on. Add universal keys in place of key drop keys. multiworld.itempool.append(item_factory(GetBeemizerItem(multiworld, player, 'Small Key (Universal)'), world)) dungeon_item_replacements = sum(difficulties[world.options.item_pool.current_key].extras, []) * 2 @@ -373,10 +373,10 @@ def generate_itempool(world): for x in range(len(dungeon_items)-1, -1, -1): item = dungeon_items[x] - if ((multiworld.small_key_shuffle[player] == small_key_shuffle.option_start_with and item.type == 'SmallKey') - or (multiworld.big_key_shuffle[player] == big_key_shuffle.option_start_with and item.type == 'BigKey') - or (multiworld.compass_shuffle[player] == compass_shuffle.option_start_with and item.type == 'Compass') - or (multiworld.map_shuffle[player] == map_shuffle.option_start_with and item.type == 'Map')): + if ((world.options.small_key_shuffle == small_key_shuffle.option_start_with and item.type == 'SmallKey') + or (world.options.big_key_shuffle == big_key_shuffle.option_start_with and item.type == 'BigKey') + or (world.options.compass_shuffle == compass_shuffle.option_start_with and item.type == 'Compass') + or (world.options.map_shuffle == map_shuffle.option_start_with and item.type == 'Map')): dungeon_items.pop(x) multiworld.push_precollected(item) multiworld.itempool.append(item_factory(dungeon_item_replacements.pop(), world)) @@ -384,7 +384,7 @@ def generate_itempool(world): set_up_shops(multiworld, player) - if multiworld.retro_bow[player]: + if world.options.retro_bow: shop_items = 0 shop_locations = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if shop.type == ShopType.Shop and shop.region.player == player) for location in shop_locations if @@ -395,12 +395,12 @@ def generate_itempool(world): else: shop_items += 1 else: - shop_items = min(multiworld.shop_item_slots[player], 30 if multiworld.include_witch_hut[player] else 27) + shop_items = min(world.options.shop_item_slots, 30 if world.options.include_witch_hut else 27) - if multiworld.shuffle_capacity_upgrades[player]: + if world.options.shuffle_capacity_upgrades: shop_items += 2 - chance_100 = int(multiworld.retro_bow[player]) * 0.25 + int( - multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal) * 0.5 + chance_100 = int(world.options.retro_bow) * 0.25 + int( + world.options.small_key_shuffle == small_key_shuffle.option_universal) * 0.5 for _ in range(shop_items): if multiworld.random.random() < chance_100: items.append(item_factory(GetBeemizerItem(multiworld, player, "Rupees (100)"), world)) @@ -410,19 +410,19 @@ def generate_itempool(world): multiworld.random.shuffle(items) pool_count = len(items) new_items = ["Triforce Piece" for _ in range(additional_triforce_pieces)] - if multiworld.shuffle_capacity_upgrades[player] or multiworld.bombless_start[player]: - progressive = multiworld.progressive[player] + if world.options.shuffle_capacity_upgrades or world.options.bombless_start: + progressive = world.options.progressive progressive = multiworld.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on' - if multiworld.shuffle_capacity_upgrades[player] == "on_combined": + if world.options.shuffle_capacity_upgrades == "on_combined": new_items.append("Bomb Upgrade (50)") - elif multiworld.shuffle_capacity_upgrades[player] == "on": + elif world.options.shuffle_capacity_upgrades == "on": new_items += ["Bomb Upgrade (+5)"] * 6 new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") - if multiworld.shuffle_capacity_upgrades[player] != "on_combined" and multiworld.bombless_start[player]: + if world.options.shuffle_capacity_upgrades != "on_combined" and world.options.bombless_start: new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") - if multiworld.shuffle_capacity_upgrades[player] and not multiworld.retro_bow[player]: - if multiworld.shuffle_capacity_upgrades[player] == "on_combined": + if world.options.shuffle_capacity_upgrades and not world.options.retro_bow: + if world.options.shuffle_capacity_upgrades == "on_combined": new_items += ["Arrow Upgrade (70)"] else: new_items += ["Arrow Upgrade (+5)"] * 6 @@ -481,7 +481,7 @@ def generate_itempool(world): if len(items) < pool_count: items += removed_filler[len(items) - pool_count:] - if multiworld.randomize_cost_types[player]: + if world.options.randomize_cost_types: # Heart and Arrow costs require all Heart Container/Pieces and Arrow Upgrades to be advancement items for logic for item in items: if item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart"): @@ -490,21 +490,21 @@ def generate_itempool(world): # Otherwise, logic has some branches where having 4 hearts is one possible requirement (of several alternatives) # rather than making all hearts/heart pieces progression items (which slows down generation considerably) # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode) - if multiworld.item_pool[player] in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0): + if world.options.item_pool in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0): next(item for item in items if item.name == 'Boss Heart Container').classification = ItemClassification.progression - elif multiworld.item_pool[player] in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4): + elif world.options.item_pool in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4): adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart') for i in range(4): next(adv_heart_pieces).classification = ItemClassification.progression - world.required_medallions = (multiworld.misery_mire_medallion[player].current_key.title(), - multiworld.turtle_rock_medallion[player].current_key.title()) + world.required_medallions = (world.options.misery_mire_medallion.current_key.title(), + world.options.turtle_rock_medallion.current_key.title()) place_bosses(world) multiworld.itempool += items - if multiworld.retro_caves[player]: + if world.options.retro_caves: set_up_take_anys(multiworld, world, player) # depends on world.itempool to be set @@ -527,7 +527,7 @@ take_any_locations.sort() def set_up_take_anys(multiworld, world, player): # these are references, do not modify these lists in-place - if multiworld.mode[player] == 'inverted': + if world.options.mode == 'inverted': take_any_locs = take_any_locations_inverted else: take_any_locs = take_any_locations @@ -578,14 +578,14 @@ def set_up_take_anys(multiworld, world, player): def get_pool_core(world, player: int): - shuffle = world.entrance_shuffle[player].current_key - difficulty = world.item_pool[player].current_key - timer = world.timer[player].current_key - goal = world.goal[player].current_key - mode = world.mode[player].current_key - swordless = world.swordless[player] - retro_bow = world.retro_bow[player] - logic = world.glitches_required[player] + shuffle = world.worlds[player].options.entrance_shuffle.current_key + difficulty = world.worlds[player].options.item_pool.current_key + timer = world.worlds[player].options.timer.current_key + goal = world.worlds[player].options.goal.current_key + mode = world.worlds[player].options.mode.current_key + swordless = world.worlds[player].options.swordless + retro_bow = world.worlds[player].options.retro_bow + logic = world.worlds[player].options.glitches_required pool = [] placed_items = {} @@ -602,11 +602,11 @@ def get_pool_core(world, player: int): placed_items[loc] = item # provide boots to major glitch dependent seeds - if logic.current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.glitch_boots[player]: + if logic.current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.worlds[player].options.glitch_boots: precollected_items.append('Pegasus Boots') pool.remove('Pegasus Boots') pool.append('Rupees (20)') - want_progressives = world.progressive[player].want_progressives + want_progressives = world.worlds[player].options.progressive.want_progressives if want_progressives(world.random): pool.extend(diff.progressiveglove) @@ -680,22 +680,22 @@ def get_pool_core(world, player: int): additional_pieces_to_place = 0 if 'triforce_hunt' in goal: - if world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_extra: - treasure_hunt_total = (world.triforce_pieces_required[player].value - + world.triforce_pieces_extra[player].value) - elif world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_percentage: - percentage = float(world.triforce_pieces_percentage[player].value) / 100 - treasure_hunt_total = int(round(world.triforce_pieces_required[player].value * percentage, 0)) + if world.worlds[player].options.triforce_pieces_mode.value == TriforcePiecesMode.option_extra: + treasure_hunt_total = (world.worlds[player].options.triforce_pieces_required.value + + world.worlds[player].options.triforce_pieces_extra.value) + elif world.worlds[player].options.triforce_pieces_mode.value == TriforcePiecesMode.option_percentage: + percentage = float(world.worlds[player].options.triforce_pieces_percentage.value) / 100 + treasure_hunt_total = int(round(world.worlds[player].options.triforce_pieces_required.value * percentage, 0)) else: # available - treasure_hunt_total = world.triforce_pieces_available[player].value + treasure_hunt_total = world.worlds[player].options.triforce_pieces_available.value - triforce_pieces = min(90, max(treasure_hunt_total, world.triforce_pieces_required[player].value)) + triforce_pieces = min(90, max(treasure_hunt_total, world.worlds[player].options.triforce_pieces_required.value)) pieces_in_core = min(extraitems, triforce_pieces) additional_pieces_to_place = triforce_pieces - pieces_in_core pool.extend(["Triforce Piece"] * pieces_in_core) extraitems -= pieces_in_core - treasure_hunt_required = world.triforce_pieces_required[player].value + treasure_hunt_required = world.worlds[player].options.triforce_pieces_required.value for extra in diff.extras: if extraitems >= len(extra): @@ -714,10 +714,10 @@ def get_pool_core(world, player: int): if retro_bow: replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (70)'} pool = ['Rupees (5)' if item in replace else item for item in pool] - if world.small_key_shuffle[player] == small_key_shuffle.option_universal: + if world.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal: pool.extend(diff.universal_keys) if mode == 'standard': - if world.key_drop_shuffle[player]: + if world.worlds[player].options.key_drop_shuffle: key_locations = ['Secret Passage', 'Hyrule Castle - Map Guard Key Drop'] key_location = world.random.choice(key_locations) key_locations.remove(key_location) @@ -741,11 +741,11 @@ def get_pool_core(world, player: int): def make_custom_item_pool(world, player): - shuffle = world.entrance_shuffle[player] - difficulty = world.item_pool[player] - timer = world.timer[player] - goal = world.goal[player] - mode = world.mode[player] + shuffle = world.worlds[player].options.entrance_shuffle + difficulty = world.worlds[player].options.item_pool + timer = world.worlds[player].options.timer + goal = world.worlds[player].options.goal + mode = world.worlds[player].options.mode customitemarray = world.customitemarray pool = [] @@ -845,10 +845,10 @@ def make_custom_item_pool(world, player): thisbottle = world.random.choice(diff.bottles) pool.append(thisbottle) - if "triforce" in world.goal[player]: - pool.extend(["Triforce Piece"] * world.triforce_pieces_available[player]) - itemtotal += world.triforce_pieces_available[player] - treasure_hunt_required = world.triforce_pieces_required[player] + if "triforce" in world.worlds[player].options.goal: + pool.extend(["Triforce Piece"] * world.worlds[player].options.triforce_pieces_available) + itemtotal += world.worlds[player].options.triforce_pieces_available + treasure_hunt_required = world.worlds[player].options.triforce_pieces_required if timer in ['display', 'timed', 'timed_countdown']: clock_mode = 'countdown' if timer == 'timed_countdown' else 'stopwatch' @@ -862,7 +862,7 @@ def make_custom_item_pool(world, player): itemtotal = itemtotal + 1 if mode == 'standard': - if world.small_key_shuffle[player] == small_key_shuffle.option_universal: + if world.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal: key_location = world.random.choice( ['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']) @@ -885,9 +885,9 @@ def make_custom_item_pool(world, player): pool.extend(['Magic Mirror'] * customitemarray[22]) pool.extend(['Moon Pearl'] * customitemarray[28]) - if world.small_key_shuffle[player] == small_key_shuffle.option_universal: + if world.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal: itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in universal Mode - if world.key_drop_shuffle[player]: + if world.worlds[player].options.key_drop_shuffle: itemtotal = itemtotal - (len(key_drop_data) - 1) if itemtotal < total_items_to_place: pool.extend(['Nothing'] * (total_items_to_place - itemtotal)) diff --git a/worlds/alttp/Items.py b/worlds/alttp/Items.py index 5f081e65fc..cbe6e99642 100644 --- a/worlds/alttp/Items.py +++ b/worlds/alttp/Items.py @@ -11,11 +11,11 @@ def GetBeemizerItem(world, player: int, item): return item # first roll - replaceable item should be replaced, within beemizer_total_chance - if not world.beemizer_total_chance[player] or world.random.random() > (world.beemizer_total_chance[player] / 100): + if not world.worlds[player].options.beemizer_total_chance or world.random.random() > (world.worlds[player].options.beemizer_total_chance / 100): return item # second roll - bee replacement should be trap, within beemizer_trap_chance - if not world.beemizer_trap_chance[player] or world.random.random() > (world.beemizer_trap_chance[player] / 100): + if not world.worlds[player].options.beemizer_trap_chance or world.random.random() > (world.worlds[player].options.beemizer_trap_chance / 100): return "Bee" if isinstance(item, str) else world.create_item("Bee", player) else: return "Bee Trap" if isinstance(item, str) else world.create_item("Bee Trap", player) diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py index 0974586117..519241d7f4 100644 --- a/worlds/alttp/Options.py +++ b/worlds/alttp/Options.py @@ -156,10 +156,10 @@ class OpenPyramid(Choice): def to_bool(self, world: MultiWorld, player: int) -> bool: if self.value == self.option_goal: - return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} + return world.worlds[player].options.goal.current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} elif self.value == self.option_auto: - return world.goal[player].current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \ - and (world.entrance_shuffle[player].current_key in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not + return world.worlds[player].options.goal.current_key in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \ + and (world.worlds[player].options.entrance_shuffle.current_key in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not world.shuffle_ganon) elif self.value == self.option_open: return True diff --git a/worlds/alttp/OverworldGlitchRules.py b/worlds/alttp/OverworldGlitchRules.py index 1a1c01525d..aeff9cb88e 100644 --- a/worlds/alttp/OverworldGlitchRules.py +++ b/worlds/alttp/OverworldGlitchRules.py @@ -220,14 +220,14 @@ def get_invalid_bunny_revival_dungeons(): def overworld_glitch_connections(world, player): # Boots-accessible locations. - create_owg_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted')) - create_owg_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player)) + create_owg_connections(player, world, get_boots_clip_exits_lw(world.worlds[player].options.mode == 'inverted')) + create_owg_connections(player, world, get_boots_clip_exits_dw(world.worlds[player].options.mode == 'inverted', player)) # Glitched speed drops. - create_owg_connections(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted')) + create_owg_connections(player, world, get_glitched_speed_drops_dw(world.worlds[player].options.mode == 'inverted')) # Mirror clip spots. - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': create_owg_connections(player, world, get_mirror_clip_spots_dw()) create_owg_connections(player, world, get_mirror_offset_spots_dw()) else: @@ -237,24 +237,24 @@ def overworld_glitch_connections(world, player): def overworld_glitches_rules(world, player): # Boots-accessible locations. - set_owg_connection_rules(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'), lambda state: can_boots_clip_lw(state, player)) - set_owg_connection_rules(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player), lambda state: can_boots_clip_dw(state, player)) + set_owg_connection_rules(player, world, get_boots_clip_exits_lw(world.worlds[player].options.mode == 'inverted'), lambda state: can_boots_clip_lw(state, player)) + set_owg_connection_rules(player, world, get_boots_clip_exits_dw(world.worlds[player].options.mode == 'inverted', player), lambda state: can_boots_clip_dw(state, player)) # Glitched speed drops. - set_owg_connection_rules(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted'), lambda state: can_get_glitched_speed_dw(state, player)) + set_owg_connection_rules(player, world, get_glitched_speed_drops_dw(world.worlds[player].options.mode == 'inverted'), lambda state: can_get_glitched_speed_dw(state, player)) # Dark Death Mountain Ledge Clip Spot also accessible with mirror. - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': add_alternate_rule(world.get_entrance('Dark Death Mountain Ledge Clip Spot', player), lambda state: state.has('Magic Mirror', player)) # Mirror clip spots. - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': set_owg_connection_rules(player, world, get_mirror_clip_spots_dw(), lambda state: state.has('Magic Mirror', player)) set_owg_connection_rules(player, world, get_mirror_offset_spots_dw(), lambda state: state.has('Magic Mirror', player) and can_boots_clip_lw(state, player)) else: set_owg_connection_rules(player, world, get_mirror_offset_spots_lw(player), lambda state: state.has('Magic Mirror', player) and can_boots_clip_dw(state, player)) # Regions that require the boots and some other stuff. - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': world.get_entrance('Turtle Rock Teleporter', player).access_rule = lambda state: (can_boots_clip_lw(state, player) or can_lift_heavy_rocks(state, player)) and state.has('Hammer', player) add_alternate_rule(world.get_entrance('Waterfall of Wishing', player), lambda state: state.has('Moon Pearl', player) or state.has('Pegasus Boots', player)) else: diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 5ed048e881..f69e6bb955 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -92,7 +92,7 @@ class LocalRom: # cause crash to provide traceback import xxtea - local_random = world.per_slot_randoms[player] + local_random = world.worlds[player].random key = bytes(local_random.getrandbits(8 * 16).to_bytes(16, 'big')) self.write_bytes(0x1800B0, bytearray(key)) self.write_int16(0x180087, 1) @@ -281,7 +281,6 @@ def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_rand def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): player = world.player - multiworld = world.multiworld check_enemizer(enemizercli) randopatch_path = os.path.abspath(os.path.join(output_directory, f'enemizer_randopatch_{player}.sfc')) options_path = os.path.abspath(os.path.join(output_directory, f'enemizer_options_{player}.json')) @@ -289,18 +288,18 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): # write options file for enemizer options = { - 'RandomizeEnemies': multiworld.enemy_shuffle[player].value, + 'RandomizeEnemies': world.options.enemy_shuffle.value, 'RandomizeEnemiesType': 3, - 'RandomizeBushEnemyChance': multiworld.bush_shuffle[player].value, - 'RandomizeEnemyHealthRange': multiworld.enemy_health[player] != 'default', + 'RandomizeBushEnemyChance': world.options.bush_shuffle.value, + 'RandomizeEnemyHealthRange': world.options.enemy_health != 'default', 'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[ - multiworld.enemy_health[player].current_key], + world.options.enemy_health.current_key], 'OHKO': False, - 'RandomizeEnemyDamage': multiworld.enemy_damage[player] != 'default', + 'RandomizeEnemyDamage': world.options.enemy_damage != 'default', 'AllowEnemyZeroDamage': True, - 'ShuffleEnemyDamageGroups': multiworld.enemy_damage[player] != 'default', - 'EnemyDamageChaosMode': multiworld.enemy_damage[player] == 'chaos', - 'EasyModeEscape': multiworld.mode[player] == "standard", + 'ShuffleEnemyDamageGroups': world.options.enemy_damage != 'default', + 'EnemyDamageChaosMode': world.options.enemy_damage == 'chaos', + 'EasyModeEscape': world.options.mode == "standard", 'EnemiesAbsorbable': False, 'AbsorbableSpawnRate': 10, 'AbsorbableTypes': { @@ -329,7 +328,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): 'GrayscaleMode': False, 'GenerateSpoilers': False, 'RandomizeLinkSpritePalette': False, - 'RandomizePots': multiworld.pot_shuffle[player].value, + 'RandomizePots': world.options.pot_shuffle.value, 'ShuffleMusic': False, 'BootlegMagic': True, 'CustomBosses': False, @@ -342,7 +341,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): 'BeesLevel': 0, 'RandomizeTileTrapPattern': False, 'RandomizeTileTrapFloorTile': False, - 'AllowKillableThief': multiworld.killable_thieves[player].value, + 'AllowKillableThief': world.options.killable_thieves.value, 'RandomizeSpriteOnHit': False, 'DebugMode': False, 'DebugForceEnemy': False, @@ -366,13 +365,13 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): 'MiseryMire': world.dungeons["Misery Mire"].boss.enemizer_name, 'TurtleRock': world.dungeons["Turtle Rock"].boss.enemizer_name, 'GanonsTower1': - world.dungeons["Ganons Tower" if multiworld.mode[player] != 'inverted' else + world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else "Inverted Ganons Tower"].bosses['bottom'].enemizer_name, 'GanonsTower2': - world.dungeons["Ganons Tower" if multiworld.mode[player] != 'inverted' else + world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else "Inverted Ganons Tower"].bosses['middle'].enemizer_name, 'GanonsTower3': - world.dungeons["Ganons Tower" if multiworld.mode[player] != 'inverted' else + world.dungeons["Ganons Tower" if world.options.mode != 'inverted' else "Inverted Ganons Tower"].bosses['top'].enemizer_name, 'GanonsTower4': 'Agahnim2', 'Ganon': 'Ganon', @@ -386,7 +385,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): max_enemizer_tries = 5 for i in range(max_enemizer_tries): - enemizer_seed = str(multiworld.per_slot_randoms[player].randint(0, 999999999)) + enemizer_seed = str(world.random.randint(0, 999999999)) enemizer_command = [os.path.abspath(enemizercli), '--rom', randopatch_path, '--seed', enemizer_seed, @@ -416,7 +415,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): continue for j in range(i + 1, max_enemizer_tries): - multiworld.per_slot_randoms[player].randint(0, 999999999) + world.random.randint(0, 999999999) # Sacrifice all remaining random numbers that would have been used for unused enemizer tries. # This allows for future enemizer bug fixes to NOT affect the rest of the seed's randomness break @@ -430,7 +429,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory): # Moblins attached to "key drop" locations crash the game when dropping their item when Key Drop Shuffle is on. # Replace them with a Slime enemy if they are placed. - if multiworld.key_drop_shuffle[player]: + if world.options.key_drop_shuffle: key_drop_enemies = { 0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201, 0x4E20A, 0x4E326, 0x4E4F7, 0x4E687, 0x4E70C, 0x4E7C8, 0x4E7FA @@ -792,8 +791,8 @@ def get_nonnative_item_sprite(code: int) -> int: def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): + local_random = world.worlds[player].random local_world = world.worlds[player] - local_random = local_world.random # patch items @@ -840,14 +839,14 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # patch music music_addresses = dungeon_music_addresses[location.name] - if world.map_shuffle[player]: + if local_world.options.map_shuffle: music = local_random.choice([0x11, 0x16]) else: music = 0x11 if 'Pendant' in location.item.name else 0x16 for music_address in music_addresses: rom.write_byte(music_address, music) - if world.map_shuffle[player]: + if local_world.options.map_shuffle: rom.write_byte(0x155C9, local_random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle # patch entrance/exits/holes @@ -868,15 +867,15 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # Thanks to Zarby89 for originally finding these values # todo fix screen scrolling - if world.entrance_shuffle[player] != 'insanity' and \ + if local_world.options.entrance_shuffle != 'insanity' and \ exit.name in {'Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit', 'Skull Woods Final Section Exit', 'Ice Palace Exit', 'Misery Mire Exit', 'Palace of Darkness Exit', 'Swamp Palace Exit', 'Ganons Tower Exit', 'Desert Palace Exit (North)', 'Agahnims Tower Exit', 'Spiral Cave Exit (Top)', 'Superbunny Cave Exit (Bottom)', 'Turtle Rock Ledge Exit (East)'} and \ - (world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic'] or - exit.name not in {'Palace of Darkness Exit', 'Tower of Hera Exit', 'Swamp Palace Exit'}): - # For exits that connot be reached from another, no need to apply offset fixes. + (local_world.options.glitches_required not in ['hybrid_major_glitches', 'no_logic'] or + exit.name not in {'Palace of Darkness Exit', 'Tower of Hera Exit', 'Swamp Palace Exit'}): + # For exits that cannot be reached from another, no need to apply offset fixes. rom.write_int16(0x15DB5 + 2 * offset, link_y) # same as final else elif room_id == 0x0059 and local_world.fix_skullwoods_exit: rom.write_int16(0x15DB5 + 2 * offset, 0x00F8) @@ -903,7 +902,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): else: # patch door table rom.write_byte(0xDBB73 + exit.addresses, exit.target) - if world.mode[player] == 'inverted': + if local_world.options.mode == 'inverted': patch_shuffled_dark_sanc(world, rom, player) write_custom_shops(rom, world, player) @@ -914,16 +913,16 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): return 0x53 + int(num), 0x79 + int(num) credits_total = 216 - if world.retro_caves[player]: # Old man cave and Take any caves will count towards collection rate. + if local_world.options.retro_caves: # Old man cave and Take any caves will count towards collection rate. credits_total += 5 - if world.shop_item_slots[player]: # Potion shop only counts towards collection rate if included in the shuffle. - credits_total += 30 if world.include_witch_hut[player] else 27 - if world.shuffle_capacity_upgrades[player]: + if local_world.options.shop_item_slots: # Potion shop only counts towards collection rate if included in the shuffle. + credits_total += 30 if local_world.options.include_witch_hut else 27 + if local_world.options.shuffle_capacity_upgrades: credits_total += 2 rom.write_byte(0x187010, credits_total) # dynamic credits - if world.key_drop_shuffle[player]: + if local_world.options.key_drop_shuffle: rom.write_byte(0x140000, 1) # enable key drop shuffle credits_total += len(key_drop_data) # update dungeon counters @@ -977,11 +976,11 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_byte(0x51DE, 0x00) # set open mode: - if world.mode[player] in ['open', 'inverted']: + if local_world.options.mode in ['open', 'inverted']: rom.write_byte(0x180032, 0x01) # open mode - if world.mode[player] == 'inverted': + if local_world.options.mode == 'inverted': set_inverted_mode(world, player, rom) - elif world.mode[player] == 'standard': + elif local_world.options.mode == 'standard': rom.write_byte(0x180032, 0x00) # standard mode uncle_location = world.get_location('Link\'s Uncle', player) @@ -1001,7 +1000,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_bytes(0x6D323, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E]) # set light cones - rom.write_byte(0x180038, 0x01 if world.mode[player] == "standard" else 0x00) + rom.write_byte(0x180038, 0x01 if local_world.options.mode == "standard" else 0x00) rom.write_byte(0x180039, 0x01 if world.light_world_light_cone else 0x00) rom.write_byte(0x18003A, 0x01 if world.dark_world_light_cone else 0x00) @@ -1011,7 +1010,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_byte(0x18004F, 0x01) # Byrna Invulnerability: on # handle item_functionality - if world.item_functionality[player] == 'hard': + if local_world.options.item_functionality == 'hard': rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup # Powdered Fairies Prize @@ -1031,7 +1030,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_int16(0x180036, world.rupoor_cost) # Set stun items rom.write_byte(0x180180, 0x02) # Hookshot only - elif world.item_functionality[player] == 'expert': + elif local_world.options.item_functionality == 'expert': rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup # Powdered Fairies Prize @@ -1071,7 +1070,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # Set stun items rom.write_byte(0x180180, 0x03) # All standard items # Set overflow items for progressive equipment - if world.timer[player] in ['timed', 'timed_countdown', 'timed_ohko']: + if local_world.options.timer in ['timed', 'timed_countdown', 'timed_ohko']: overflow_replacement = GREEN_CLOCK else: overflow_replacement = GREEN_TWENTY_RUPEES @@ -1083,7 +1082,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # Set overflow items for progressive equipment rom.write_bytes(0x180090, - [difficulty.progressive_sword_limit if not world.swordless[player] else 0, + [difficulty.progressive_sword_limit if not local_world.options.swordless else 0, item_table[difficulty.basicsword[-1]].item_code, difficulty.progressive_shield_limit, item_table[difficulty.basicshield[-1]].item_code, difficulty.progressive_armor_limit, item_table[difficulty.basicarmor[-1]].item_code, @@ -1091,7 +1090,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): difficulty.progressive_bow_limit, item_table[difficulty.basicbow[-1]].item_code]) if difficulty.progressive_bow_limit < 2 and ( - world.swordless[player] or world.glitches_required[player] == 'no_glitches'): + local_world.options.swordless or local_world.options.glitches_required == 'no_glitches'): rom.write_bytes(0x180098, [2, item_table["Silver Bow"].item_code]) rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup @@ -1099,15 +1098,15 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # set up game internal RNG seed rom.write_bytes(0x178000, local_random.getrandbits(8 * 1024).to_bytes(1024, 'big')) prize_replacements = {} - if world.item_functionality[player] in ['hard', 'expert']: + if local_world.options.item_functionality in ['hard', 'expert']: prize_replacements[0xE0] = 0xDF # Fairy -> heart prize_replacements[0xE3] = 0xD8 # Big magic -> small magic - if world.retro_bow[player]: + if local_world.options.retro_bow: prize_replacements[0xE1] = 0xDA # 5 Arrows -> Blue Rupee prize_replacements[0xE2] = 0xDB # 10 Arrows -> Red Rupee - if world.shuffle_prizes[player] in ("general", "both"): + if local_world.options.shuffle_prizes in ("general", "both"): # shuffle prize packs prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF, @@ -1169,7 +1168,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): byte = int(rom.read_byte(address)) rom.write_byte(address, prize_replacements.get(byte, byte)) - if world.shuffle_prizes[player] in ("bonk", "both"): + if local_world.options.shuffle_prizes in ("bonk", "both"): # set bonk prizes bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC, 0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79, @@ -1196,7 +1195,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): 0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees 0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade 0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade - 0x58, 0x01, 0x36 if world.retro_bow[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) + 0x58, 0x01, 0x36 if local_world.options.retro_bow else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode) 0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20 0x17, difficulty.heart_piece_limit, 0x47, 0xff, # piece of heart -> green 20 0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel @@ -1238,13 +1237,13 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_byte(0x180029, 0x01) # Smithy quick item give # set swordless mode settings - rom.write_byte(0x18003F, 0x01 if world.swordless[player] else 0x00) # hammer can harm ganon - rom.write_byte(0x180040, 0x01 if world.swordless[player] else 0x00) # open curtains - rom.write_byte(0x180041, 0x01 if world.swordless[player] else 0x00) # swordless medallions - rom.write_byte(0x180043, 0xFF if world.swordless[player] else 0x00) # starting sword for link - rom.write_byte(0x180044, 0x01 if world.swordless[player] else 0x00) # hammer activates tablets + rom.write_byte(0x18003F, 0x01 if local_world.options.swordless else 0x00) # hammer can harm ganon + rom.write_byte(0x180040, 0x01 if local_world.options.swordless else 0x00) # open curtains + rom.write_byte(0x180041, 0x01 if local_world.options.swordless else 0x00) # swordless medallions + rom.write_byte(0x180043, 0xFF if local_world.options.swordless else 0x00) # starting sword for link + rom.write_byte(0x180044, 0x01 if local_world.options.swordless else 0x00) # hammer activates tablets - if world.item_functionality[player] == 'easy': + if local_world.options.item_functionality == 'easy': rom.write_byte(0x18003F, 0x01) # hammer can harm ganon rom.write_byte(0x180041, 0x02) # Allow swordless medallion use EVERYWHERE. rom.write_byte(0x180044, 0x01) # hammer activates tablets @@ -1262,11 +1261,11 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # Set up requested clock settings if local_world.clock_mode in ['countdown-ohko', 'stopwatch', 'countdown']: rom.write_int32(0x180200, - world.red_clock_time[player] * 60 * 60) # red clock adjustment time (in frames, sint32) + local_world.options.red_clock_time * 60 * 60) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180204, - world.blue_clock_time[player] * 60 * 60) # blue clock adjustment time (in frames, sint32) + local_world.options.blue_clock_time * 60 * 60) # blue clock adjustment time (in frames, sint32) rom.write_int32(0x180208, - world.green_clock_time[player] * 60 * 60) # green clock adjustment time (in frames, sint32) + local_world.options.green_clock_time * 60 * 60) # green clock adjustment time (in frames, sint32) else: rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32) rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32) @@ -1274,20 +1273,20 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # Set up requested start time for countdown modes if local_world.clock_mode in ['countdown-ohko', 'countdown']: - rom.write_int32(0x18020C, world.countdown_start_time[player] * 60 * 60) # starting time (in frames, sint32) + rom.write_int32(0x18020C, local_world.options.countdown_start_time * 60 * 60) # starting time (in frames, sint32) else: rom.write_int32(0x18020C, 0) # starting time (in frames, sint32) # set up goals for treasure hunt rom.write_int16(0x180163, max(0, local_world.treasure_hunt_required - - sum(1 for item in world.precollected_items[player] if item.name == "Triforce Piece"))) + sum(1 for item in world.precollected_items[player] if item.name == "Triforce Piece"))) rom.write_bytes(0x180165, [0x0E, 0x28]) # Triforce Piece Sprite rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed gametype = 0x04 # item - if world.entrance_shuffle[player] != 'vanilla': + if local_world.options.entrance_shuffle != 'vanilla': gametype |= 0x02 # entrance if enemized: gametype |= 0x01 # enemizer @@ -1298,7 +1297,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_byte(0x1800A2, 0x01 if local_world.fix_fake_world else 0x00) # Lock or unlock aga tower door during escape sequence. rom.write_byte(0x180169, 0x00) - if world.mode[player] == 'inverted': + if local_world.options.mode == 'inverted': rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted rom.write_byte(0x180171, 0x01 if local_world.ganon_at_pyramid else 0x00) # Enable respawning on pyramid after ganon death @@ -1309,9 +1308,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest rom.write_byte(0x50599, 0x00) # disable below ganon chest rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest - rom.write_byte(0x18008B, 0x01 if world.open_pyramid[player].to_bool(world, player) else 0x00) # pre-open Pyramid Hole - rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt[ - player] == 0 else 0x00) # GT pre-opened if crystal requirement is 0 + rom.write_byte(0x18008B, 0x01 if local_world.options.open_pyramid.to_bool(world, player) else 0x00) # pre-open Pyramid Hole + rom.write_byte(0x18008C, 0x01 if local_world.options.crystals_needed_for_gt == 0 else 0x00) # GT pre-opened if crystal requirement is 0 rom.write_byte(0xF5D73, 0xF0) # bees are catchable rom.write_byte(0xF5F10, 0xF0) # bees are catchable rom.write_byte(0x180086, 0x00 if world.aga_randomness else 0x01) # set blue ball and ganon warp randomness @@ -1325,7 +1323,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): equip[0x36C] = 0x18 equip[0x36D] = 0x18 equip[0x379] = 0x68 - starting_max_bombs = 0 if world.bombless_start[player] else 10 + starting_max_bombs = 0 if local_world.options.bombless_start else 10 starting_max_arrows = 30 startingstate = CollectionState(world) @@ -1333,12 +1331,12 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): if startingstate.has('Silver Bow', player): equip[0x340] = 1 equip[0x38E] |= 0x60 - if not world.retro_bow[player]: + if not local_world.options.retro_bow: equip[0x38E] |= 0x80 elif startingstate.has('Bow', player): equip[0x340] = 1 equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases - if not world.retro_bow[player]: + if not local_world.options.retro_bow: equip[0x38E] |= 0x80 if startingstate.has('Silver Arrows', player): equip[0x38E] |= 0x40 @@ -1476,7 +1474,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): elif item.name in bombs: equip[0x343] += bombs[item.name] elif item.name in arrows: - if world.retro_bow[player]: + if local_world.options.retro_bow: equip[0x38E] |= 0x80 equip[0x377] = 1 else: @@ -1502,16 +1500,13 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_bytes(0x183000, equip[0x340:]) rom.write_bytes(0x271A6, equip[0x340:0x340 + 60]) - rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode + rom.write_byte(0x18004A, 0x00 if local_world.options.mode != 'inverted' else 0x01) # Inverted mode rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier - rom.write_byte(0x2AF79, 0xD0 if world.mode[ - player] != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) - rom.write_byte(0x3A943, 0xD0 if world.mode[ - player] != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both) - rom.write_byte(0x3A96D, 0xF0 if world.mode[ - player] != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader)) + rom.write_byte(0x2AF79, 0xD0 if local_world.options.mode != 'inverted' else 0xF0) # vortexes: Normal (D0=light to dark, F0=dark to light, 42 = both) + rom.write_byte(0x3A943, 0xD0 if local_world.options.mode != 'inverted' else 0xF0) # Mirror: Normal (D0=Dark to Light, F0=light to dark, 42 = both) + rom.write_byte(0x3A96D, 0xF0 if local_world.options.mode != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader)) rom.write_byte(0x3A9A7, 0xD0) # Residual Portal: Normal (D0= Light Side, F0=Dark Side, 42 = both (Darth Vader)) - if world.shuffle_capacity_upgrades[player]: + if local_world.options.shuffle_capacity_upgrades: rom.write_bytes(0x180080, [5, 10, 5, 10]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10) else: @@ -1522,21 +1517,21 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): (0x02 if 'bombs' in local_world.escape_assist else 0x00) | (0x04 if 'magic' in local_world.escape_assist else 0x00))) # Escape assist - if world.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: + if local_world.options.goal in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']: rom.write_byte(0x18003E, 0x01) # make ganon invincible - elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: + elif local_world.options.goal in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: rom.write_byte(0x18003E, 0x05) # make ganon invincible until enough triforce pieces are collected - elif world.goal[player] in ['ganon_pedestal']: + elif local_world.options.goal in ['ganon_pedestal']: rom.write_byte(0x18003E, 0x06) - elif world.goal[player] in ['bosses']: + elif local_world.options.goal in ['bosses']: rom.write_byte(0x18003E, 0x02) # make ganon invincible until all bosses are beat - elif world.goal[player] in ['crystals']: + elif local_world.options.goal in ['crystals']: rom.write_byte(0x18003E, 0x04) # make ganon invincible until all crystals else: rom.write_byte(0x18003E, 0x03) # make ganon invincible until all crystals and aga 2 are collected - rom.write_byte(0x18005E, world.crystals_needed_for_gt[player]) - rom.write_byte(0x18005F, world.crystals_needed_for_ganon[player]) + rom.write_byte(0x18005E, local_world.options.crystals_needed_for_gt) + rom.write_byte(0x18005F, local_world.options.crystals_needed_for_ganon) # Bitfield - enable text box to show with free roaming items # @@ -1547,21 +1542,20 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # c - enabled for inside compasses # s - enabled for inside small keys # block HC upstairs doors in rain state in standard mode - rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" and world.entrance_shuffle[player] != 'vanilla' else 0x00) + rom.write_byte(0x18008A, 0x01 if local_world.options.mode == "standard" and local_world.options.entrance_shuffle != 'vanilla' else 0x00) - rom.write_byte(0x18016A, 0x10 | ((0x01 if world.small_key_shuffle[player] else 0x00) - | (0x02 if world.compass_shuffle[player] else 0x00) - | (0x04 if world.map_shuffle[player] else 0x00) - | (0x08 if world.big_key_shuffle[ - player] else 0x00))) # free roaming item text boxes - rom.write_byte(0x18003B, 0x01 if world.map_shuffle[player] else 0x00) # maps showing crystals on overworld + rom.write_byte(0x18016A, 0x10 | ((0x01 if local_world.options.small_key_shuffle else 0x00) + | (0x02 if local_world.options.compass_shuffle else 0x00) + | (0x04 if local_world.options.map_shuffle else 0x00) + | (0x08 if local_world.options.big_key_shuffle else 0x00))) # free roaming item text boxes + rom.write_byte(0x18003B, 0x01 if local_world.options.map_shuffle else 0x00) # maps showing crystals on overworld # compasses showing dungeon count - if local_world.clock_mode or world.dungeon_counters[player] == 'off': + if local_world.clock_mode or local_world.options.dungeon_counters == 'off': rom.write_byte(0x18003C, 0x00) # Currently must be off if timer is on, because they use same HUD location - elif world.dungeon_counters[player] == 'on': + elif local_world.options.dungeon_counters == 'on': rom.write_byte(0x18003C, 0x02) # always on - elif world.compass_shuffle[player] or world.dungeon_counters[player] == 'pickup': + elif local_world.options.compass_shuffle or local_world.options.dungeon_counters == 'pickup': rom.write_byte(0x18003C, 0x01) # show on pickup else: rom.write_byte(0x18003C, 0x00) @@ -1574,11 +1568,11 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): # b - Big Key # a - Small Key # - rom.write_byte(0x180045, ((0x00 if (world.small_key_shuffle[player] == small_key_shuffle.option_original_dungeon or - world.small_key_shuffle[player] == small_key_shuffle.option_universal) else 0x01) - | (0x02 if world.big_key_shuffle[player] else 0x00) - | (0x04 if world.map_shuffle[player] else 0x00) - | (0x08 if world.compass_shuffle[player] else 0x00))) # free roaming items in menu + rom.write_byte(0x180045, ((0x00 if (local_world.options.small_key_shuffle == small_key_shuffle.option_original_dungeon or + local_world.options.small_key_shuffle == small_key_shuffle.option_universal) else 0x01) + | (0x02 if local_world.options.big_key_shuffle else 0x00) + | (0x04 if local_world.options.map_shuffle else 0x00) + | (0x08 if local_world.options.compass_shuffle else 0x00))) # free roaming items in menu # Map reveals reveal_bytes = { @@ -1604,31 +1598,25 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): return 0x0000 rom.write_int16(0x18017A, - get_reveal_bytes('Green Pendant') if world.map_shuffle[player] else 0x0000) # Sahasrahla reveal - rom.write_int16(0x18017C, get_reveal_bytes('Crystal 5') | get_reveal_bytes('Crystal 6') if world.map_shuffle[ - player] else 0x0000) # Bomb Shop Reveal + get_reveal_bytes('Green Pendant') if local_world.options.map_shuffle else 0x0000) # Sahasrahla reveal + rom.write_int16(0x18017C, get_reveal_bytes('Crystal 5') | get_reveal_bytes('Crystal 6') if local_world.options.map_shuffle else 0x0000) # Bomb Shop Reveal - rom.write_byte(0x180172, 0x01 if world.small_key_shuffle[ - player] == small_key_shuffle.option_universal else 0x00) # universal keys - rom.write_byte(0x18637E, 0x01 if world.retro_bow[player] else 0x00) # Skip quiver in item shops once bought - rom.write_byte(0x180175, 0x01 if world.retro_bow[player] else 0x00) # rupee bow - rom.write_byte(0x180176, 0x0A if world.retro_bow[player] else 0x00) # wood arrow cost - rom.write_byte(0x180178, 0x32 if world.retro_bow[player] else 0x00) # silver arrow cost - rom.write_byte(0x301FC, 0xDA if world.retro_bow[player] else 0xE1) # rupees replace arrows under pots - rom.write_byte(0x30052, 0xDB if world.retro_bow[player] else 0xE2) # replace arrows in fish prize from bottle merchant - rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if world.retro_bow[player] else [0xAF, 0x77, 0xF3, - 0x7E]) # Thief steals rupees instead of arrows - rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if world.retro_bow[player] else [0xAF, 0x77, 0xF3, - 0x7E]) # Pikit steals rupees instead of arrows - rom.write_bytes(0xEDA5, - [0x35, 0x41] if world.retro_bow[player] else [0x43, 0x44]) # Chest game gives rupees instead of arrows + rom.write_byte(0x180172, 0x01 if local_world.options.small_key_shuffle == small_key_shuffle.option_universal else 0x00) # universal keys + rom.write_byte(0x18637E, 0x01 if local_world.options.retro_bow else 0x00) # Skip quiver in item shops once bought + rom.write_byte(0x180175, 0x01 if local_world.options.retro_bow else 0x00) # rupee bow + rom.write_byte(0x180176, 0x0A if local_world.options.retro_bow else 0x00) # wood arrow cost + rom.write_byte(0x180178, 0x32 if local_world.options.retro_bow else 0x00) # silver arrow cost + rom.write_byte(0x301FC, 0xDA if local_world.options.retro_bow else 0xE1) # rupees replace arrows under pots + rom.write_byte(0x30052, 0xDB if local_world.options.retro_bow else 0xE2) # replace arrows in fish prize from bottle merchant + rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if local_world.options.retro_bow else [0xAF, 0x77, 0xF3, 0x7E]) # Thief steals rupees instead of arrows + rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if local_world.options.retro_bow else [0xAF, 0x77, 0xF3, 0x7E]) # Pikit steals rupees instead of arrows + rom.write_bytes(0xEDA5, [0x35, 0x41] if local_world.options.retro_bow else [0x43, 0x44]) # Chest game gives rupees instead of arrows digging_game_rng = local_random.randint(1, 30) # set rng for digging game rom.write_byte(0x180020, digging_game_rng) rom.write_byte(0xEFD95, digging_game_rng) rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills - rom.write_byte(0x1800A4, 0x01 if world.glitches_required[player] != 'no_logic' else 0x00) # enable POD EG fix - rom.write_byte(0x186383, 0x01 if world.glitches_required[ - player] == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room + rom.write_byte(0x1800A4, 0x01 if local_world.options.glitches_required != 'no_logic' else 0x00) # enable POD EG fix + rom.write_byte(0x186383, 0x01 if local_world.options.glitches_required == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill # remove shield from uncle @@ -1645,7 +1633,7 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_bytes(0x180185, [0, 0, 0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0, 0, 0]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0, 0, 0]) # Mantle respawn refills (magic, bombs, arrows) - if world.mode[player] == 'standard' and uncle_location.item and uncle_location.item.player == player: + if local_world.options.mode == 'standard' and uncle_location.item and uncle_location.item.player == player: if uncle_location.item.name in {'Bow', 'Progressive Bow'}: rom.write_byte(0x18004E, 1) # Escape Fill (arrows) rom.write_int16(0x180183, 300) # Escape fill rupee bow @@ -1673,8 +1661,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): 0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) # allow smith into multi-entrance caves in appropriate shuffles - if world.entrance_shuffle[player] in ['restricted', 'full', 'crossed', 'insanity'] or ( - world.entrance_shuffle[player] == 'simple' and world.mode[player] == 'inverted'): + if local_world.options.entrance_shuffle in ['restricted', 'full', 'crossed', 'insanity'] or ( + local_world.options.entrance_shuffle == 'simple' and local_world.options.mode == 'inverted'): rom.write_byte(0x18004C, 0x01) # set correct flag for hera basement item @@ -1694,8 +1682,8 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_byte(0xFED31, 0x2A) # bombable exit rom.write_byte(0xFEE41, 0x2A) # bombable exit - if world.tile_shuffle[player]: - tile_set = TileSet.get_random_tile_set(world.per_slot_randoms[player]) + if local_world.options.tile_shuffle: + tile_set = TileSet.get_random_tile_set(world.worlds[player].random) rom.write_byte(0x4BA21, tile_set.get_speed()) rom.write_byte(0x4BA1D, tile_set.get_len()) rom.write_bytes(0x4BA2A, tile_set.get_bytes()) @@ -1770,9 +1758,9 @@ def write_custom_shops(rom, world, player): slot = 0 if shop.type == ShopType.TakeAny else index if item is None: break - if world.shop_item_slots[player] or shop.type == ShopType.TakeAny: - count_shop = (shop.region.name != 'Potion Shop' or world.include_witch_hut[player]) and \ - (shop.region.name != 'Capacity Upgrade' or world.shuffle_capacity_upgrades[player]) + if world.worlds[player].options.shop_item_slots or shop.type == ShopType.TakeAny: + count_shop = (shop.region.name != 'Potion Shop' or world.worlds[player].options.include_witch_hut) and \ + (shop.region.name != 'Capacity Upgrade' or world.worlds[player].options.shuffle_capacity_upgrades) rom.write_byte(0x186560 + shop.sram_offset + slot, 1 if count_shop else 0) if item['item'] == 'Single Arrow' and item['player'] == 0: arrow_mask |= 1 << index @@ -1789,7 +1777,7 @@ def write_custom_shops(rom, world, player): item_code = get_nonnative_item_sprite(world.worlds[item['player']].item_name_to_id[item['item']]) else: item_code = item_table[item["item"]].item_code - if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro_bow[player]: + if item['item'] == 'Single Arrow' and item['player'] == 0 and world.worlds[player].options.retro_bow: rom.write_byte(0x186500 + shop.sram_offset + slot, arrow_mask) item_data = [shop_id, item_code] + price_data + \ @@ -1802,7 +1790,7 @@ def write_custom_shops(rom, world, player): items_data.extend([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) rom.write_bytes(0x184900, items_data) - if world.retro_bow[player]: + if world.worlds[player].options.retro_bow: retro_shop_slots.append(0xFF) rom.write_bytes(0x186540, retro_shop_slots) @@ -2207,19 +2195,18 @@ def write_string_to_rom(rom, target, string): def write_strings(rom, world, player): from . import ALTTPWorld - + local_random = world.worlds[player].random w: ALTTPWorld = world.worlds[player] - local_random = w.random tt = TextTable() tt.removeUnwantedText() # Let's keep this guy's text accurate to the shuffle setting. - if world.entrance_shuffle[player] in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']: + if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']: tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.' - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': tt['sign_village_of_outcasts'] = 'attention\nferal ducks sighted\nhiding in statues\n\nflute players beware\n' def hint_text(dest, ped_hint=False): @@ -2238,21 +2225,21 @@ def write_strings(rom, world, player): hint += f" for {world.player_name[dest.player]}" return hint - if world.scams[player].gives_king_zora_hint: + if world.worlds[player].options.scams.gives_king_zora_hint: # Zora hint zora_location = world.get_location("King Zora", player) tt['zora_tells_cost'] = f"You got 500 rupees to buy {hint_text(zora_location.item)}" \ f"\n ≥ Duh\n Oh carp\n{{CHOICE}}" - if world.scams[player].gives_bottle_merchant_hint: + if world.worlds[player].options.scams.gives_bottle_merchant_hint: # Bottle Vendor hint vendor_location = world.get_location("Bottle Merchant", player) tt['bottle_vendor_choice'] = f"I gots {hint_text(vendor_location.item)}\nYous gots 100 rupees?" \ f"\n ≥ I want\n no way!\n{{CHOICE}}" # First we write hints about entrances, some from the inconvenient list others from all reasonable entrances. - if world.hints[player]: - if world.hints[player].value >= 2: - if world.hints[player] == "full": + if world.worlds[player].options.hints: + if world.worlds[player].options.hints.value >= 2: + if world.worlds[player].options.hints == "full": tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles have hints!' else: tt['sign_north_of_links_house'] = '> Randomizer The telepathic tiles can have hints!' @@ -2265,11 +2252,11 @@ def write_strings(rom, world, player): entrances_to_hint = {} entrances_to_hint.update(InconvenientDungeonEntrances) if world.shuffle_ganon: - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': entrances_to_hint.update({'Inverted Ganons Tower': 'The sealed castle door'}) else: entrances_to_hint.update({'Ganons Tower': 'Ganon\'s Tower'}) - if world.entrance_shuffle[player] in ['simple', 'restricted']: + if world.worlds[player].options.entrance_shuffle in ['simple', 'restricted']: for entrance in all_entrances: if entrance.name in entrances_to_hint: this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text( @@ -2279,9 +2266,9 @@ def write_strings(rom, world, player): break # Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones. entrances_to_hint.update(InconvenientOtherEntrances) - if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: + if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: hint_count = 0 - elif world.entrance_shuffle[player] in ['simple', 'restricted']: + elif world.worlds[player].options.entrance_shuffle in ['simple', 'restricted']: hint_count = 2 else: hint_count = 4 @@ -2298,31 +2285,31 @@ def write_strings(rom, world, player): # Next we handle hints for randomly selected other entrances, # curating the selection intelligently based on shuffle. - if world.entrance_shuffle[player] not in ['simple', 'restricted']: + if world.worlds[player].options.entrance_shuffle not in ['simple', 'restricted']: entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(DungeonEntrances) - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': entrances_to_hint.update({'Inverted Agahnims Tower': 'The dark mountain tower'}) else: entrances_to_hint.update({'Agahnims Tower': 'The sealed castle door'}) - elif world.entrance_shuffle[player] == 'restricted': + elif world.worlds[player].options.entrance_shuffle == 'restricted': entrances_to_hint.update(ConnectorEntrances) entrances_to_hint.update(OtherEntrances) - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': entrances_to_hint.update({'Inverted Dark Sanctuary': 'The dark sanctuary cave'}) entrances_to_hint.update({'Inverted Big Bomb Shop': 'The old hero\'s dark home'}) entrances_to_hint.update({'Inverted Links House': 'The old hero\'s light home'}) else: entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'}) entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'}) - if world.entrance_shuffle[player] != 'insanity': + if world.worlds[player].options.entrance_shuffle != 'insanity': entrances_to_hint.update(InsanityEntrances) if world.shuffle_ganon: - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) else: entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'}) - hint_count = 4 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full', + hint_count = 4 if world.worlds[player].options.entrance_shuffle not in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'] else 0 for entrance in all_entrances: if entrance.name in entrances_to_hint: @@ -2337,10 +2324,10 @@ def write_strings(rom, world, player): # Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable. locations_to_hint = InconvenientLocations.copy() - if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: + if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: locations_to_hint.extend(InconvenientVanillaLocations) local_random.shuffle(locations_to_hint) - hint_count = 3 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full', + hint_count = 3 if world.worlds[player].options.entrance_shuffle not in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'] else 5 for location in locations_to_hint[:hint_count]: if location == 'Swamp Left': @@ -2395,15 +2382,15 @@ def write_strings(rom, world, player): # Lastly we write hints to show where certain interesting items are. items_to_hint = RelevantItems.copy() - if world.small_key_shuffle[player].hints_useful: + if world.worlds[player].options.small_key_shuffle.hints_useful: items_to_hint |= item_name_groups["Small Keys"] - if world.big_key_shuffle[player].hints_useful: + if world.worlds[player].options.big_key_shuffle.hints_useful: items_to_hint |= item_name_groups["Big Keys"] - if world.hints[player] == "full": + if world.worlds[player].options.hints == "full": hint_count = len(hint_locations) # fill all remaining hint locations with Item hints. else: - hint_count = 5 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full', + hint_count = 5 if world.worlds[player].options.entrance_shuffle not in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'] else 8 hint_count = min(hint_count, len(items_to_hint), len(hint_locations)) if hint_count: @@ -2434,7 +2421,7 @@ def write_strings(rom, world, player): tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint if world.worlds[player].has_progressive_bows and (w.difficulty_requirements.progressive_bow_limit >= 2 or ( - world.swordless[player] or world.glitches_required[player] == 'no_glitches')): + world.worlds[player].options.swordless or world.worlds[player].options.glitches_required == 'no_glitches')): prog_bow_locs = world.find_item_locations('Progressive Bow', player, True) local_random.shuffle(prog_bow_locs) found_bow = False @@ -2458,26 +2445,26 @@ def write_strings(rom, world, player): greenpendant = world.find_item('Green Pendant', player) tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text - if world.crystals_needed_for_gt[player] == 1: + if world.worlds[player].options.crystals_needed_for_gt == 1: tt['sign_ganons_tower'] = 'You need a crystal to enter.' else: - tt['sign_ganons_tower'] = f'You need {world.crystals_needed_for_gt[player]} crystals to enter.' + tt['sign_ganons_tower'] = f'You need {world.worlds[player].options.crystals_needed_for_gt} crystals to enter.' - if world.goal[player] == 'bosses': + if world.worlds[player].options.goal == 'bosses': tt['sign_ganon'] = 'You need to kill all bosses, Ganon last.' - elif world.goal[player] == 'ganon_pedestal': + elif world.worlds[player].options.goal == 'ganon_pedestal': tt['sign_ganon'] = 'You need to pull the pedestal to defeat Ganon.' - elif world.goal[player] == "ganon": - if world.crystals_needed_for_ganon[player] == 1: + elif world.worlds[player].options.goal == "ganon": + if world.worlds[player].options.crystals_needed_for_ganon == 1: tt['sign_ganon'] = 'You need a crystal to beat Ganon and have beaten Agahnim atop Ganons Tower.' else: - tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon and ' \ + tt['sign_ganon'] = f'You need {world.worlds[player].options.crystals_needed_for_ganon} crystals to beat Ganon and ' \ f'have beaten Agahnim atop Ganons Tower' else: - if world.crystals_needed_for_ganon[player] == 1: + if world.worlds[player].options.crystals_needed_for_ganon == 1: tt['sign_ganon'] = 'You need a crystal to beat Ganon.' else: - tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon.' + tt['sign_ganon'] = f'You need {world.worlds[player].options.crystals_needed_for_ganon} crystals to beat Ganon.' tt['uncle_leaving_text'] = Uncle_texts[local_random.randint(0, len(Uncle_texts) - 1)] tt['end_triforce'] = "{NOBORDER}\n" + Triforce_texts[local_random.randint(0, len(Triforce_texts) - 1)] @@ -2490,10 +2477,10 @@ def write_strings(rom, world, player): triforce_pieces_required = max(0, w.treasure_hunt_required - sum(1 for item in world.precollected_items[player] if item.name == "Triforce Piece")) - if world.goal[player] in ['triforce_hunt', 'local_triforce_hunt']: + if world.worlds[player].options.goal in ['triforce_hunt', 'local_triforce_hunt']: tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' - if world.goal[player] == 'triforce_hunt' and world.players > 1: + if world.worlds[player].options.goal == 'triforce_hunt' and world.players > 1: tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!' else: tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!' @@ -2507,7 +2494,7 @@ def write_strings(rom, world, player): "invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \ "hidden in a hollow tree. If you bring\n%d Triforce piece out of %d, I can reassemble it." % \ (triforce_pieces_required, w.treasure_hunt_total) - elif world.goal[player] in ['pedestal']: + elif world.worlds[player].options.goal in ['pedestal']: tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Your goal is at the pedestal.' tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.' tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!' @@ -2516,17 +2503,17 @@ def write_strings(rom, world, player): tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!' tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!' if triforce_pieces_required > 1: - if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1: + if world.worlds[player].options.goal == 'ganon_triforce_hunt' and world.players > 1: tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d with your friends to defeat Ganon.' % \ (triforce_pieces_required, w.treasure_hunt_total) - elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: + elif world.worlds[player].options.goal in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d to defeat Ganon.' % \ (triforce_pieces_required, w.treasure_hunt_total) else: - if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1: + if world.worlds[player].options.goal == 'ganon_triforce_hunt' and world.players > 1: tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d with your friends to defeat Ganon.' % \ (triforce_pieces_required, w.treasure_hunt_total) - elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: + elif world.worlds[player].options.goal in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d to defeat Ganon.' % \ (triforce_pieces_required, w.treasure_hunt_total) @@ -2549,11 +2536,11 @@ def write_strings(rom, world, player): tt['tablet_bombos_book'] = bombos_text # inverted spawn menu changes - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': tt['menu_start_2'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n{CHOICE3}" tt['menu_start_3'] = "{MENU}\n{SPEED0}\n≥@'s house\n Dark Chapel\n Mountain Cave\n{CHOICE2}" - for at, text, _ in world.plando_texts[player]: + for at, text, _ in world.worlds[player].options.plando_texts: if at not in tt: raise Exception(f"No text target \"{at}\" found.") @@ -2626,12 +2613,12 @@ def set_inverted_mode(world, player, rom): rom.write_byte(snes_to_pc(0x08D40C), 0xD0) # morph proof # the following bytes should only be written in vanilla # or they'll overwrite the randomizer's shuffles - if world.entrance_shuffle[player] == 'vanilla': + if world.worlds[player].options.entrance_shuffle == 'vanilla': rom.write_byte(0xDBB73 + 0x23, 0x37) # switch AT and GT rom.write_byte(0xDBB73 + 0x36, 0x24) rom.write_int16(0x15AEE + 2 * 0x38, 0x00E0) rom.write_int16(0x15AEE + 2 * 0x25, 0x000C) - if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: + if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rom.write_byte(0x15B8C, 0x6C) rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house rom.write_byte(0xDBB73 + 0x52, 0x01) @@ -2689,7 +2676,7 @@ def set_inverted_mode(world, player, rom): rom.write_int16(snes_to_pc(0x02D9A6), 0x005A) rom.write_byte(snes_to_pc(0x02D9B3), 0x12) # keep the old man spawn point at old man house unless shuffle is vanilla - if world.entrance_shuffle[player] in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']: + if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']: rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01]) rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1) rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03]) @@ -2752,7 +2739,7 @@ def set_inverted_mode(world, player, rom): rom.write_int16s(snes_to_pc(0x1bb836), [0x001B, 0x001B, 0x001B]) rom.write_int16(snes_to_pc(0x308300), 0x0140) # new pyramid hole entrance rom.write_int16(snes_to_pc(0x308320), 0x001B) - if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: + if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rom.write_byte(snes_to_pc(0x308340), 0x7B) rom.write_int16(snes_to_pc(0x1af504), 0x148B) rom.write_int16(snes_to_pc(0x1af50c), 0x149B) @@ -2789,10 +2776,10 @@ def set_inverted_mode(world, player, rom): rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82]) rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door rom.write_int16(0xDBA71 + 2 * 0x35, 0x06A4) - if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: + if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rom.write_byte(0xDBB73 + 0x35, 0x36) rom.write_byte(snes_to_pc(0x09D436), 0xF3) # remove castle gate warp - if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: + if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rom.write_int16(0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area rom.write_byte(0x15B8C + 0x37, 0x1B) rom.write_int16(0x15BDB + 2 * 0x37, 0x0418) diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index 3f5081129a..452c15223c 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -27,9 +27,9 @@ from .UnderworldGlitchRules import underworld_glitches_rules def set_rules(world): player = world.player world = world.multiworld - if world.glitches_required[player] == 'no_logic': + if world.worlds[player].options.glitches_required == 'no_logic': if player == next(player_id for player_id in world.get_game_players("A Link to the Past") - if world.glitches_required[player_id] == 'no_logic'): # only warn one time + if world.worlds[player_id].options.glitches_required == 'no_logic'): # only warn one time logging.info( 'WARNING! Seeds generated under this logic often require major glitches and may be impossible!') @@ -40,8 +40,8 @@ def set_rules(world): else: # Set access rules according to max glitches for multiworld progression. # Set accessibility to none, and shuffle assuming the no logic players can always win - world.accessibility[player].value = ItemsAccessibility.option_minimal - world.progression_balancing[player].value = 0 + world.worlds[player].options.accessibility.value = ItemsAccessibility.option_minimal + world.worlds[player].options.progression_balancing.value = 0 else: world.completion_condition[player] = lambda state: state.has('Triforce', player) @@ -49,52 +49,52 @@ def set_rules(world): dungeon_boss_rules(world, player) global_rules(world, player) - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': default_rules(world, player) - if world.mode[player] == 'open': + if world.worlds[player].options.mode == 'open': open_rules(world, player) - elif world.mode[player] == 'standard': + elif world.worlds[player].options.mode == 'standard': standard_rules(world, player) - elif world.mode[player] == 'inverted': + elif world.worlds[player].options.mode == 'inverted': open_rules(world, player) inverted_rules(world, player) else: - raise NotImplementedError(f'World state {world.mode[player]} is not implemented yet') + raise NotImplementedError(f'World state {world.worlds[player].options.mode} is not implemented yet') - if world.glitches_required[player] == 'no_glitches': + if world.worlds[player].options.glitches_required == 'no_glitches': no_glitches_rules(world, player) forbid_bomb_jump_requirements(world, player) - elif world.glitches_required[player] == 'overworld_glitches': + elif world.worlds[player].options.glitches_required == 'overworld_glitches': # Initially setting no_glitches_rules to set the baseline rules for some # entrances. The overworld_glitches_rules set is primarily additive. no_glitches_rules(world, player) fake_flipper_rules(world, player) overworld_glitches_rules(world, player) forbid_bomb_jump_requirements(world, player) - elif world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']: + elif world.worlds[player].options.glitches_required in ['hybrid_major_glitches', 'no_logic']: no_glitches_rules(world, player) fake_flipper_rules(world, player) overworld_glitches_rules(world, player) underworld_glitches_rules(world, player) bomb_jump_requirements(world, player) - elif world.glitches_required[player] == 'minor_glitches': + elif world.worlds[player].options.glitches_required == 'minor_glitches': no_glitches_rules(world, player) fake_flipper_rules(world, player) forbid_bomb_jump_requirements(world, player) else: - raise NotImplementedError(f'Not implemented yet: Logic - {world.glitches_required[player]}') + raise NotImplementedError(f'Not implemented yet: Logic - {world.worlds[player].options.glitches_required}') - if world.goal[player] == 'bosses': + if world.worlds[player].options.goal == 'bosses': # require all bosses to beat ganon add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player) and state.has('Beat Agahnim 1', player) and state.has('Beat Agahnim 2', player) and has_crystals(state, 7, player)) - elif world.goal[player] == 'ganon': + elif world.worlds[player].options.goal == 'ganon': # require aga2 to beat ganon add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player)) - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': set_big_bomb_rules(world, player) - if world.glitches_required[player].current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.entrance_shuffle[player].current_key not in {'insanity', 'insanity_legacy', 'madness'}: + if world.worlds[player].options.glitches_required.current_key in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.worlds[player].options.entrance_shuffle.current_key not in {'insanity', 'insanity_legacy', 'madness'}: path_to_courtyard = mirrorless_path_to_castle_courtyard(world, player) add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.multiworld.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or') else: @@ -102,21 +102,24 @@ def set_rules(world): # if swamp and dam have not been moved we require mirror for swamp palace # however there is mirrorless swamp in hybrid MG, so we don't necessarily want this. HMG handles this requirement itself. - if not world.worlds[player].swamp_patch_required and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: + if not world.worlds[player].swamp_patch_required and world.worlds[player].options.glitches_required not in ['hybrid_major_glitches', 'no_logic']: add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player)) # GT Entrance may be required for Turtle Rock for OWG and < 7 required - ganons_tower = world.get_entrance('Inverted Ganons Tower' if world.mode[player] == 'inverted' else 'Ganons Tower', player) - if world.crystals_needed_for_gt[player] == 7 and not (world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and world.mode[player] != 'inverted'): + ganons_tower = world.get_entrance('Inverted Ganons Tower' if world.worlds[player].options.mode == 'inverted' else 'Ganons Tower', player) + if (world.worlds[player].options.crystals_needed_for_gt == 7 + and not (world.worlds[player].options.glitches_required + in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic'] + and world.worlds[player].options.mode != 'inverted')): set_rule(ganons_tower, lambda state: False) set_trock_key_rules(world, player) - set_rule(ganons_tower, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_gt[player], player)) - if world.mode[player] != 'inverted' and world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: + set_rule(ganons_tower, lambda state: has_crystals(state, state.multiworld.worlds[player].options.crystals_needed_for_gt, player)) + if world.worlds[player].options.mode != 'inverted' and world.worlds[player].options.glitches_required in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']: add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.multiworld.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or') - set_bunny_rules(world, player, world.mode[player] == 'inverted') + set_bunny_rules(world, player, world.worlds[player].options.mode == 'inverted') def mirrorless_path_to_castle_courtyard(world, player): @@ -150,17 +153,17 @@ def set_always_allow(spot, rule): def add_lamp_requirement(world: MultiWorld, spot, player: int, has_accessible_torch: bool = False): - if world.dark_room_logic[player] == "lamp": + if world.worlds[player].options.dark_room_logic == "lamp": add_rule(spot, lambda state: state.has('Lamp', player)) - elif world.dark_room_logic[player] == "torches": # implicitly lamp as well + elif world.worlds[player].options.dark_room_logic == "torches": # implicitly lamp as well if has_accessible_torch: add_rule(spot, lambda state: state.has('Lamp', player) or state.has('Fire Rod', player)) else: add_rule(spot, lambda state: state.has('Lamp', player)) - elif world.dark_room_logic[player] == "none": + elif world.worlds[player].options.dark_room_logic == "none": pass else: - raise ValueError(f"Unknown Dark Room Logic: {world.dark_room_logic[player]}") + raise ValueError(f"Unknown Dark Room Logic: {world.worlds[player].options.dark_room_logic}") non_crossover_items = (item_name_groups["Small Keys"] | item_name_groups["Big Keys"] | progression_items) - { @@ -227,12 +230,13 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_location('Sick Kid', player), lambda state: state.has_group("Bottles", player)) set_rule(multiworld.get_location('Library', player), lambda state: state.has('Pegasus Boots', player)) - if multiworld.enemy_shuffle[player]: + if world.options.enemy_shuffle: set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) and can_kill_most_things(state, player, 4)) else: set_rule(multiworld.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) - and ((state.multiworld.enemy_health[player] in ("easy", "default") and can_use_bombs(state, player, 4)) + and ((state.multiworld.worlds[player].options.enemy_health in ("easy", "default") + and can_use_bombs(state, player, 4)) or can_shoot_arrows(state, player) or state.has("Cane of Somaria", player) or has_beam_sword(state, player))) @@ -299,8 +303,7 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_entrance('Sewers Door', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) or ( - multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal and multiworld.mode[ - player] == 'standard')) # standard universal small keys cannot access the shop + world.options.small_key_shuffle == small_key_shuffle.option_universal and world.options.mode == 'standard')) # standard universal small keys cannot access the shop set_rule(multiworld.get_entrance('Sewers Back Door', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4)) set_rule(multiworld.get_entrance('Sewers Secret Room', player), lambda state: can_bomb_or_bonk(state, player)) @@ -339,12 +342,12 @@ def global_rules(multiworld: MultiWorld, player: int): add_rule(ep_prize, lambda state: state.has('Big Key (Eastern Palace)', player) and state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and ep_prize.parent_region.dungeon.boss.can_defeat(state)) - if not multiworld.enemy_shuffle[player]: + if not world.options.enemy_shuffle: add_rule(ep_boss, lambda state: can_shoot_arrows(state, player)) add_rule(ep_prize, lambda state: can_shoot_arrows(state, player)) # You can always kill the Stalfos' with the pots on easy/normal - if multiworld.enemy_health[player] in ("hard", "expert") or multiworld.enemy_shuffle[player]: + if world.options.enemy_health in ("hard", "expert") or world.options.enemy_shuffle: stalfos_rule = lambda state: can_kill_most_things(state, player, 4) for location in ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', 'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop', @@ -362,14 +365,14 @@ def global_rules(multiworld: MultiWorld, player: int): add_rule(multiworld.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state)) # logic patch to prevent placing a crystal in Desert that's required to reach the required keys - if not (multiworld.small_key_shuffle[player] and multiworld.big_key_shuffle[player]): + if not (world.options.small_key_shuffle and world.options.big_key_shuffle): add_rule(multiworld.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state)) set_rule(multiworld.get_location('Tower of Hera - Basement Cage', player), lambda state: can_activate_crystal_switch(state, player)) set_rule(multiworld.get_location('Tower of Hera - Map Chest', player), lambda state: can_activate_crystal_switch(state, player)) set_rule(multiworld.get_entrance('Tower of Hera Small Key Door', player), lambda state: can_activate_crystal_switch(state, player) and (state._lttp_has_key('Small Key (Tower of Hera)', player) or location_item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player))) set_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_activate_crystal_switch(state, player) and state.has('Big Key (Tower of Hera)', player)) - if multiworld.enemy_shuffle[player]: + if world.options.enemy_shuffle: add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_kill_most_things(state, player, 3)) else: add_rule(multiworld.get_entrance('Tower of Hera Big Key Door', player), @@ -378,7 +381,7 @@ def global_rules(multiworld: MultiWorld, player: int): or state.has("Cane of Somaria", player))) set_rule(multiworld.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player)) set_rule(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player)) - if multiworld.accessibility[player] != 'full': + if world.options.accessibility != 'full': set_always_allow(multiworld.get_location('Tower of Hera - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Tower of Hera)' and item.player == player) set_rule(multiworld.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player)) @@ -387,32 +390,32 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_location('Swamp Palace - Trench 1 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 2)) set_rule(multiworld.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 3)) set_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player)) - if multiworld.pot_shuffle[player]: + if world.options.pot_shuffle: # it could move the key to the top right platform which can only be reached with bombs add_rule(multiworld.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Swamp Palace (West)', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6) if state.has('Hookshot', player) else state._lttp_has_key('Small Key (Swamp Palace)', player, 4)) set_rule(multiworld.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player)) - if multiworld.accessibility[player] != 'full': + if world.options.accessibility != 'full': allow_self_locking_items(multiworld.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)') set_rule(multiworld.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5)) - if not multiworld.small_key_shuffle[player] and multiworld.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']: + if not world.options.small_key_shuffle and world.options.glitches_required not in ['hybrid_major_glitches', 'no_logic']: forbid_item(multiworld.get_location('Swamp Palace - Entrance', player), 'Big Key (Swamp Palace)', player) add_rule(multiworld.get_location('Swamp Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) add_rule(multiworld.get_location('Swamp Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)) - if multiworld.pot_shuffle[player]: + if world.options.pot_shuffle: # key can (and probably will) be moved behind bombable wall set_rule(multiworld.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player)) - if multiworld.worlds[player].dungeons["Thieves Town"].boss.enemizer_name == "Blind": + if world.dungeons["Thieves Town"].boss.enemizer_name == "Blind": set_rule(multiworld.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player)) set_rule(multiworld.get_location('Thieves\' Town - Big Chest', player), lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) and state.has('Hammer', player)) set_rule(multiworld.get_location('Thieves\' Town - Blind\'s Cell', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player)) - if multiworld.accessibility[player] != 'full' and not multiworld.key_drop_shuffle[player]: + if world.options.accessibility != 'full' and not world.options.key_drop_shuffle: set_always_allow(multiworld.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player) set_rule(multiworld.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3)) set_rule(multiworld.get_location('Thieves\' Town - Spike Switch Pot Key', player), @@ -424,7 +427,7 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(multiworld.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) set_rule(multiworld.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, player)) - if multiworld.accessibility[player] != 'full': + if world.options.accessibility != 'full': allow_self_locking_items(multiworld.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)') set_rule(multiworld.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain add_rule(multiworld.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5)) @@ -501,13 +504,13 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player)) set_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_kill_most_things(state, player, 10)) - if not multiworld.worlds[player].fix_trock_doors: + if not world.fix_trock_doors: add_rule(multiworld.get_entrance('Turtle Rock Second Section Bomb Wall', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Turtle Rock Second Section from Bomb Wall', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge from Bomb Wall', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Turtle Rock Eye Bridge Bomb Wall', player), lambda state: can_use_bombs(state, player)) - if multiworld.enemy_shuffle[player]: + if world.options.enemy_shuffle: set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_kill_most_things(state, player, 3)) else: set_rule(multiworld.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_shoot_arrows(state, player)) @@ -517,18 +520,18 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_entrance('Palace of Darkness (North)', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 4)) set_rule(multiworld.get_location('Palace of Darkness - Big Chest', player), lambda state: can_use_bombs(state, player) and state.has('Big Key (Palace of Darkness)', player)) set_rule(multiworld.get_location('Palace of Darkness - The Arena - Ledge', player), lambda state: can_use_bombs(state, player)) - if multiworld.pot_shuffle[player]: + if world.options.pot_shuffle: # chest switch may be up on ledge where bombs are required set_rule(multiworld.get_location('Palace of Darkness - Stalfos Basement', player), lambda state: can_use_bombs(state, player)) set_rule(multiworld.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3)))) - if multiworld.accessibility[player] != 'full': + if world.options.accessibility != 'full': set_always_allow(multiworld.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) set_rule(multiworld.get_entrance('Palace of Darkness Spike Statue Room Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or ( location_item_name(state, 'Palace of Darkness - Harmless Hellway', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 4))) - if multiworld.accessibility[player] != 'full': + if world.options.accessibility != 'full': set_always_allow(multiworld.get_location('Palace of Darkness - Harmless Hellway', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5)) set_rule(multiworld.get_entrance('Palace of Darkness Maze Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6)) @@ -541,13 +544,13 @@ def global_rules(multiworld: MultiWorld, player: int): set_rule(multiworld.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player)) set_rule(multiworld.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player)) set_rule(multiworld.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player))) - if multiworld.pot_shuffle[player]: + if world.options.pot_shuffle: set_rule(multiworld.get_location('Ganons Tower - Conveyor Cross Pot Key', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player))) set_rule(multiworld.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or ( location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 6))) # this seemed to be causing generation failure, disable for now - # if world.accessibility[player] != 'full': + # if world.worlds[player].options.accessibility != 'full': # set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state._lttp_has_key('Small Key (Ganons Tower)', player, 7) and state.can_reach('Ganons Tower (Hookshot Room)', 'region', player)) # It is possible to need more than 6 keys to get through this entrance if you spend keys elsewhere. We reflect this in the chest requirements. @@ -582,7 +585,7 @@ def global_rules(multiworld: MultiWorld, player: int): lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) set_rule(multiworld.get_location('Ganons Tower - Big Key Room - Right', player), lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state)) - if multiworld.enemy_shuffle[player]: + if world.options.enemy_shuffle: set_rule(multiworld.get_entrance('Ganons Tower Big Key Door', player), lambda state: state.has('Big Key (Ganons Tower)', player)) else: @@ -600,12 +603,12 @@ def global_rules(multiworld: MultiWorld, player: int): set_defeat_dungeon_boss_rule(multiworld.get_location('Agahnim 2', player)) ganon = multiworld.get_location('Ganon', player) set_rule(ganon, lambda state: GanonDefeatRule(state, player)) - if multiworld.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: + if world.options.goal in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']: add_rule(ganon, lambda state: has_triforce_pieces(state, player)) - elif multiworld.goal[player] == 'ganon_pedestal': + elif world.options.goal == 'ganon_pedestal': add_rule(multiworld.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player)) else: - add_rule(ganon, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_ganon[player], player)) + add_rule(ganon, lambda state: has_crystals(state, state.multiworld.worlds[player].options.crystals_needed_for_ganon, player)) set_rule(multiworld.get_entrance('Ganon Drop', player), lambda state: has_beam_sword(state, player)) # need to damage ganon to get tiles to drop set_rule(multiworld.get_location('Flute Activation Spot', player), lambda state: state.has('Flute', player)) @@ -722,9 +725,9 @@ def default_rules(world, player): set_rule(world.get_entrance('Floating Island Mirror Spot', player), lambda state: state.has('Magic Mirror', player)) set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Moon Pearl', player) and has_sword(state, player) and has_turtle_rock_medallion(state, player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword required to cast magic (!) - set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player].to_bool(world, player)) + set_rule(world.get_entrance('Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.worlds[player].options.open_pyramid.to_bool(world, player)) - if world.swordless[player]: + if world.worlds[player].options.swordless: swordless_rules(world, player) @@ -879,14 +882,14 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Dark Grassy Lawn Flute', player), lambda state: state.has('Activated Flute', player)) set_rule(world.get_entrance('Hammer Peg Area Flute', player), lambda state: state.has('Activated Flute', player)) - set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.open_pyramid[player]) + set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: state.has('Beat Agahnim 2', player) or world.worlds[player].options.open_pyramid) - if world.swordless[player]: + if world.worlds[player].options.swordless: swordless_rules(world, player) def no_glitches_rules(world, player): """""" - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Moon Pearl', player) and (state.has('Flippers', player) or can_lift_rocks(state, player))) set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Moon Pearl', player) and state.has('Flippers', player)) # can be fake flippered to set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: state.has('Moon Pearl', player) and state.has('Flippers', player)) # can be fake flippered to @@ -910,7 +913,7 @@ def no_glitches_rules(world, player): add_conditional_lamps(world, player) def fake_flipper_rules(world, player): - if world.mode[player] == 'inverted': + if world.worlds[player].options.mode == 'inverted': set_rule(world.get_entrance('Zoras River', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_entrance('Lake Hylia Central Island Pier', player), lambda state: state.has('Moon Pearl', player)) set_rule(world.get_entrance('Lake Hylia Island Pier', player), lambda state: state.has('Moon Pearl', player)) @@ -996,7 +999,7 @@ def add_conditional_lamps(world, player): 'Location', True) add_conditional_lamp('Palace of Darkness - Dark Basement - Right', 'Palace of Darkness (Entrance)', 'Location', True) - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance') add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower') add_conditional_lamp('Castle Tower - Dark Archer Key Drop', 'Agahnims Tower') @@ -1018,7 +1021,7 @@ def add_conditional_lamps(world, player): add_conditional_lamp('Eastern Palace - Boss', 'Eastern Palace', 'Location', True) add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location', True) - if not world.mode[player] == "standard": + if not world.worlds[player].options.mode == "standard": add_lamp_requirement(world, world.get_location('Sewers - Dark Cross', player), player) add_lamp_requirement(world, world.get_entrance('Sewers Back Door', player), player) add_lamp_requirement(world, world.get_entrance('Throne Room', player), player) @@ -1044,7 +1047,7 @@ def open_rules(world, player): set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and state.has('Big Key (Hyrule Castle)', player) - and (world.enemy_health[player] in ("easy", "default") + and (world.worlds[player].options.enemy_health in ("easy", "default") or can_kill_most_things(state, player, 1))) @@ -1058,7 +1061,7 @@ def swordless_rules(world, player): set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop - if world.mode[player] != 'inverted': + if world.worlds[player].options.mode != 'inverted': set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has('Hammer', player) or state.has('Beat Agahnim 1', player)) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Turtle Rock', player), lambda state: state.has('Moon Pearl', player) and has_turtle_rock_medallion(state, player) and state.can_reach('Turtle Rock (Top)', 'Region', player)) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Misery Mire', player), lambda state: state.has('Moon Pearl', player) and has_misery_mire_medallion(state, player)) # sword not required to use medallion for opening in swordless (!) @@ -1084,7 +1087,7 @@ def standard_rules(world, player): set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player)) - if world.small_key_shuffle[player] != small_key_shuffle.option_universal: + if world.worlds[player].options.small_key_shuffle != small_key_shuffle.option_universal: set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1) and can_kill_most_things(state, player, 2)) @@ -1097,7 +1100,7 @@ def standard_rules(world, player): set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player), lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2) and state.has('Big Key (Hyrule Castle)', player) - and (world.enemy_health[player] in ("easy", "default") + and (world.worlds[player].options.enemy_health in ("easy", "default") or can_kill_most_things(state, player, 1))) set_rule(world.get_location('Sewers - Key Rat Key Drop', player), @@ -1195,15 +1198,15 @@ def set_trock_key_rules(multiworld, player): return 6 # If TR is only accessible from the middle, the big key must be further restricted to prevent softlock potential - if not can_reach_front and not multiworld.small_key_shuffle[player]: + if not can_reach_front and not multiworld.worlds[player].options.small_key_shuffle: # Must not go in the Big Key Chest - only 1 other chest available and 2+ keys required for all other chests forbid_item(multiworld.get_location('Turtle Rock - Big Key Chest', player), 'Big Key (Turtle Rock)', player) if not can_reach_big_chest: # Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests forbid_item(multiworld.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player) forbid_item(multiworld.get_location('Turtle Rock - Pokey 2 Key Drop', player), 'Big Key (Turtle Rock)', player) - if multiworld.accessibility[player] == 'full': - if multiworld.big_key_shuffle[player] and can_reach_big_chest: + if multiworld.worlds[player].options.accessibility == 'full': + if multiworld.worlds[player].options.big_key_shuffle and can_reach_big_chest: # Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest', 'Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop', @@ -1216,9 +1219,9 @@ def set_trock_key_rules(multiworld, player): location.place_locked_item(item) toss_junk_item(multiworld, player) - if multiworld.accessibility[player] != 'full': + if multiworld.worlds[player].options.accessibility != 'full': set_always_allow(multiworld.get_location('Turtle Rock - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Turtle Rock)' and item.player == player - and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player))) + and state.can_reach(state.multiworld.get_region('Turtle Rock (Second Section)', player))) def set_big_bomb_rules(world, player): @@ -1683,7 +1686,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): def get_rule_to_add(region, location = None, connecting_entrance = None): # In OWG, a location can potentially be superbunny-mirror accessible or # bunny revival accessible. - if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic']: + if world.worlds[player].options.glitches_required in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic']: if region.name == 'Swamp Palace (Entrance)': # Need to 0hp revive - not in logic return lambda state: state.has('Moon Pearl', player) if region.name == 'Tower of Hera (Bottom)': # Need to hit the crystal switch @@ -1723,7 +1726,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): seen.add(new_region) if not is_link(new_region): # For glitch rulesets, establish superbunny and revival rules. - if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): + if world.worlds[player].options.glitches_required in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions(): possible_options.append(lambda state: path_to_access_rule(new_path, entrance) and state.has('Magic Mirror', player) and has_sword(state, player)) elif (region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions() @@ -1760,7 +1763,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): # Add requirements for all locations that are actually in the dark world, except those available to the bunny, including dungeon revival for entrance in world.get_entrances(player): if is_bunny(entrance.connected_region): - if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] : + if world.worlds[player].options.glitches_required in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] : if entrance.connected_region.type == LTTPRegionType.Dungeon: if entrance.parent_region.type != LTTPRegionType.Dungeon and entrance.connected_region.name in OverworldGlitchRules.get_invalid_bunny_revival_dungeons(): add_rule(entrance, get_rule_to_add(entrance.connected_region, None, entrance)) @@ -1768,7 +1771,7 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool): if entrance.connected_region.name == 'Turtle Rock (Entrance)': add_rule(world.get_entrance('Turtle Rock Entrance Gap', player), get_rule_to_add(entrance.connected_region, None, entrance)) for location in entrance.connected_region.locations: - if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): + if world.worlds[player].options.glitches_required in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances(): continue if location.name in bunny_accessible_locations: continue diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index 055eb2da93..bb3945f5b0 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -168,7 +168,7 @@ def push_shop_inventories(multiworld): for location in shop_slots: item_name = location.item.name # Retro Bow arrows will already have been pushed - if (not multiworld.retro_bow[location.player]) or ((item_name, location.item.player) + if (not multiworld.worlds[location.player].options.retro_bow) or ((item_name, location.item.player) != ("Single Arrow", location.player)): location.shop.push_inventory(location.shop_slot, item_name, round(location.shop_price * get_price_modifier(location.item)), @@ -185,36 +185,36 @@ def push_shop_inventories(multiworld): def create_shops(multiworld, player: int): from .Options import RandomizeShopInventories player_shop_table = shop_table.copy() - if multiworld.include_witch_hut[player]: + if multiworld.worlds[player].options.include_witch_hut: player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False) dynamic_shop_slots = total_dynamic_shop_slots + 3 else: dynamic_shop_slots = total_dynamic_shop_slots - if multiworld.shuffle_capacity_upgrades[player]: + if multiworld.worlds[player].options.shuffle_capacity_upgrades: player_shop_table["Capacity Upgrade"] = player_shop_table["Capacity Upgrade"]._replace(locked=False) - num_slots = min(dynamic_shop_slots, multiworld.shop_item_slots[player]) + num_slots = min(dynamic_shop_slots, multiworld.worlds[player].options.shop_item_slots) single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots) multiworld.random.shuffle(single_purchase_slots) - if multiworld.randomize_shop_inventories[player]: + if multiworld.worlds[player].options.randomize_shop_inventories: default_shop_table = [i for l in [shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if - not multiworld.retro_bow[player] or x != 'arrows'] for i in l] + not multiworld.worlds[player].options.retro_bow or x != 'arrows'] for i in l] new_basic_shop = multiworld.random.sample(default_shop_table, k=3) new_dark_shop = multiworld.random.sample(default_shop_table, k=3) for name, shop in player_shop_table.items(): typ, shop_id, keeper, custom, locked, items, sram_offset = shop if not locked: new_items = multiworld.random.sample(default_shop_table, k=len(items)) - if multiworld.randomize_shop_inventories[player] == RandomizeShopInventories.option_randomize_by_shop_type: + if multiworld.worlds[player].options.randomize_shop_inventories == RandomizeShopInventories.option_randomize_by_shop_type: if items == _basic_shop_defaults: new_items = new_basic_shop elif items == _dark_world_shop_defaults: new_items = new_dark_shop keeper = multiworld.random.choice([0xA0, 0xC1, 0xFF]) player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset) - if multiworld.mode[player] == "inverted": + if multiworld.worlds[player].options.mode == "inverted": # make sure that blue potion is available in inverted, special case locked = None; lock when done. player_shop_table["Dark Lake Hylia Shop"] = \ player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None) @@ -237,7 +237,7 @@ def create_shops(multiworld, player: int): add_rule(loc, lambda state, spot=loc: shop_price_rules(state, player, spot)) loc.shop = shop loc.shop_slot = index - if ((not (multiworld.shuffle_capacity_upgrades[player] and type == ShopType.UpgradeShop)) + if ((not (multiworld.worlds[player].options.shuffle_capacity_upgrades and type == ShopType.UpgradeShop)) and not single_purchase_slots.pop()): loc.shop_slot_disabled = True loc.locked = True @@ -309,18 +309,18 @@ def set_up_shops(multiworld, player: int): from .Options import small_key_shuffle # TODO: move hard+ mode changes for shields here, utilizing the new shops - if multiworld.retro_bow[player]: + if multiworld.worlds[player].options.retro_bow: rss = multiworld.get_region('Red Shield Shop', player).shop replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50], ['Blue Shield', 50], ['Small Heart', 10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them. - if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: + if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal: replacement_items.append(['Small Key (Universal)', 100]) replacement_item = multiworld.random.choice(replacement_items) rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1]) rss.locked = True - if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal or multiworld.retro_bow[player]: + if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal or multiworld.worlds[player].options.retro_bow: for shop in multiworld.random.sample([s for s in multiworld.shops if s.custom and not s.locked and s.type == ShopType.Shop and s.region.player == player], 5): @@ -328,19 +328,19 @@ def set_up_shops(multiworld, player: int): slots = [0, 1, 2] multiworld.random.shuffle(slots) slots = iter(slots) - if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: + if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal: shop.add_inventory(next(slots), 'Small Key (Universal)', 100) - if multiworld.retro_bow[player]: + if multiworld.worlds[player].options.retro_bow: shop.push_inventory(next(slots), 'Single Arrow', 80) - if multiworld.shuffle_capacity_upgrades[player]: + if multiworld.worlds[player].options.shuffle_capacity_upgrades: for shop in multiworld.shops: if shop.type == ShopType.UpgradeShop and shop.region.player == player and \ shop.region.name == "Capacity Upgrade": shop.clear_inventory() - if (multiworld.shuffle_shop_inventories[player] or multiworld.randomize_shop_prices[player] - or multiworld.randomize_cost_types[player]): + if (multiworld.worlds[player].options.shuffle_shop_inventories or multiworld.worlds[player].options.randomize_shop_prices + or multiworld.worlds[player].options.randomize_cost_types): shops = [] total_inventory = [] for shop in multiworld.shops: @@ -352,7 +352,7 @@ def set_up_shops(multiworld, player: int): for item in total_inventory: item["price_type"], item["price"] = get_price(multiworld, item, player) - if multiworld.shuffle_shop_inventories[player]: + if multiworld.worlds[player].options.shuffle_shop_inventories: multiworld.random.shuffle(total_inventory) i = 0 @@ -434,39 +434,39 @@ def get_price(multiworld, item, player: int, price_type=None): price_types = [price_type] else: price_types = [ShopPriceType.Rupees] # included as a chance to not change price - if multiworld.randomize_cost_types[player]: + if multiworld.worlds[player].options.randomize_cost_types: price_types += [ ShopPriceType.Hearts, ShopPriceType.Bombs, ShopPriceType.Magic, ] - if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: + if multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal: if item and item["item"] == "Small Key (Universal)": price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for repeatable keys else: price_types.append(ShopPriceType.Keys) - if multiworld.retro_bow[player]: + if multiworld.worlds[player].options.retro_bow: if item and item["item"] == "Single Arrow": price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for arrows else: price_types.append(ShopPriceType.Arrows) - diff = multiworld.item_pool[player].value + diff = multiworld.worlds[player].options.item_pool.value if item: # This is for a shop's regular inventory, the item is already determined, and we will decide the price here price = item["price"] - if multiworld.randomize_shop_prices[player]: + if multiworld.worlds[player].options.randomize_shop_prices: adjust = 2 if price < 100 else 5 - price = int((price / adjust) * (0.5 + multiworld.per_slot_randoms[player].random() * 1.5)) * adjust - multiworld.per_slot_randoms[player].shuffle(price_types) + price = int((price / adjust) * (0.5 + multiworld.worlds[player].random.random() * 1.5)) * adjust + multiworld.worlds[player].random.shuffle(price_types) for p_type in price_types: if any(x in item['item'] for x in price_blacklist[p_type]): continue return p_type, price_chart[p_type](price, diff) else: # This is an AP location and the price will be adjusted after an item is shuffled into it - p_type = multiworld.per_slot_randoms[player].choice(price_types) - return p_type, price_chart[p_type](min(int(multiworld.per_slot_randoms[player].randint(8, 56) - * multiworld.shop_price_modifier[player] / 100) * 5, 9999), diff) + p_type = multiworld.worlds[player].random.choice(price_types) + return p_type, price_chart[p_type](min(int(multiworld.worlds[player].random.randint(8, 56) + * multiworld.worlds[player].options.shop_price_modifier / 100) * 5, 9999), diff) def shop_price_rules(state: CollectionState, player: int, location: ALttPLocation): diff --git a/worlds/alttp/StateHelpers.py b/worlds/alttp/StateHelpers.py index 8661632b83..6ac3c4b8f8 100644 --- a/worlds/alttp/StateHelpers.py +++ b/worlds/alttp/StateHelpers.py @@ -6,7 +6,7 @@ def is_not_bunny(state: CollectionState, region: LTTPRegion, player: int) -> boo if state.has('Moon Pearl', player): return True - return region.is_light_world if state.multiworld.mode[player] != 'inverted' else region.is_dark_world + return region.is_light_world if state.multiworld.worlds[player].options.mode != 'inverted' else region.is_dark_world def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bool: @@ -24,7 +24,7 @@ def can_buy(state: CollectionState, item: str, player: int) -> bool: def can_shoot_arrows(state: CollectionState, player: int, count: int = 0) -> bool: - if state.multiworld.retro_bow[player]: + if state.multiworld.worlds[player].options.retro_bow: return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_buy(state, 'Single Arrow', player) return (state.has('Bow', player) or state.has('Silver Bow', player)) and can_hold_arrows(state, player, count) @@ -74,9 +74,9 @@ def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16, elif state.has('Magic Upgrade (1/2)', player): basemagic = 16 if can_buy_unlimited(state, 'Green Potion', player) or can_buy_unlimited(state, 'Blue Potion', player): - if state.multiworld.item_functionality[player] == 'hard' and not fullrefill: + if state.multiworld.worlds[player].options.item_functionality == 'hard' and not fullrefill: basemagic = basemagic + int(basemagic * 0.5 * bottle_count(state, player)) - elif state.multiworld.item_functionality[player] == 'expert' and not fullrefill: + elif state.multiworld.worlds[player].options.item_functionality == 'expert' and not fullrefill: basemagic = basemagic + int(basemagic * 0.25 * bottle_count(state, player)) else: basemagic = basemagic + basemagic * bottle_count(state, player) @@ -99,12 +99,12 @@ def can_hold_arrows(state: CollectionState, player: int, quantity: int): def can_use_bombs(state: CollectionState, player: int, quantity: int = 1) -> bool: - bombs = 0 if state.multiworld.bombless_start[player] else 10 + bombs = 0 if state.multiworld.worlds[player].options.bombless_start else 10 bombs += ((state.count("Bomb Upgrade (+5)", player) * 5) + (state.count("Bomb Upgrade (+10)", player) * 10) + (state.count("Bomb Upgrade (50)", player) * 50)) # Bomb Upgrade (+5) beyond the 6th gives +10 bombs += max(0, ((state.count("Bomb Upgrade (+5)", player) - 6) * 10)) - if (not state.multiworld.shuffle_capacity_upgrades[player]) and state.has("Capacity Upgrade Shop", player): + if (not state.multiworld.worlds[player].options.shuffle_capacity_upgrades) and state.has("Capacity Upgrade Shop", player): bombs += 40 return bombs >= min(quantity, 50) @@ -120,7 +120,7 @@ def can_activate_crystal_switch(state: CollectionState, player: int) -> bool: def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) -> bool: - if state.multiworld.enemy_shuffle[player]: + if state.multiworld.worlds[player].options.enemy_shuffle: # I don't fully understand Enemizer's logic for placing enemies in spots where they need to be killable, if any. # Just go with maximal requirements for now. return (has_melee_weapon(state, player) @@ -135,7 +135,7 @@ def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player))) or can_shoot_arrows(state, player) or state.has('Fire Rod', player) - or (state.multiworld.enemy_health[player] in ("easy", "default") + or (state.multiworld.worlds[player].options.enemy_health in ("easy", "default") and can_use_bombs(state, player, enemies * 4))) @@ -152,7 +152,7 @@ def can_get_good_bee(state: CollectionState, player: int) -> bool: def can_retrieve_tablet(state: CollectionState, player: int) -> bool: return state.has('Book of Mudora', player) and (has_beam_sword(state, player) or - (state.multiworld.swordless[player] and + (state.multiworld.worlds[player].options.swordless and state.has("Hammer", player))) @@ -179,7 +179,7 @@ def has_fire_source(state: CollectionState, player: int) -> bool: def can_melt_things(state: CollectionState, player: int) -> bool: return state.has('Fire Rod', player) or \ (state.has('Bombos', player) and - (state.multiworld.swordless[player] or + (state.multiworld.worlds[player].options.swordless or has_sword(state, player))) @@ -192,19 +192,19 @@ def has_turtle_rock_medallion(state: CollectionState, player: int) -> bool: def can_boots_clip_lw(state: CollectionState, player: int) -> bool: - if state.multiworld.mode[player] == 'inverted': + if state.multiworld.worlds[player].options.mode == 'inverted': return state.has('Pegasus Boots', player) and state.has('Moon Pearl', player) return state.has('Pegasus Boots', player) def can_boots_clip_dw(state: CollectionState, player: int) -> bool: - if state.multiworld.mode[player] != 'inverted': + if state.multiworld.worlds[player].options.mode != 'inverted': return state.has('Pegasus Boots', player) and state.has('Moon Pearl', player) return state.has('Pegasus Boots', player) def can_get_glitched_speed_dw(state: CollectionState, player: int) -> bool: rules = [state.has('Pegasus Boots', player), any([state.has('Hookshot', player), has_sword(state, player)])] - if state.multiworld.mode[player] != 'inverted': + if state.multiworld.worlds[player].options.mode != 'inverted': rules.append(state.has('Moon Pearl', player)) return all(rules) diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py index 2b18f67ed9..25511f320d 100644 --- a/worlds/alttp/UnderworldGlitchRules.py +++ b/worlds/alttp/UnderworldGlitchRules.py @@ -59,7 +59,7 @@ def dungeon_reentry_rules(world, player, clip: LTTPEntrance, dungeon_region: str # since the clip links directly to the exterior region. -def underworld_glitches_rules(world, player): +def underworld_glitches_rules(world, player): # Ice Palace Entrance Clip # This is the easiest one since it's a simple internal clip. # Need to also add melting to freezor chest since it's otherwise assumed. @@ -88,12 +88,12 @@ def underworld_glitches_rules(world, player): # We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT. # First we require a certain type of entrance shuffle, then build the rule from its pieces. if not world.worlds[player].swamp_patch_required: - if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: + if world.worlds[player].options.entrance_shuffle in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']: rule_map = { 'Misery Mire (Entrance)': (lambda state: True), 'Tower of Hera (Bottom)': (lambda state: state.can_reach('Tower of Hera Big Key Door', 'Entrance', player)) } - inverted = world.mode[player] == 'inverted' + inverted = world.worlds[player].options.mode == 'inverted' hera_rule = lambda state: (state.has('Moon Pearl', player) or not inverted) and \ rule_map.get(world.get_entrance('Tower of Hera', player).connected_region.name, lambda state: False)(state) gt_rule = lambda state: (state.has('Moon Pearl', player) or inverted) and \ diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 4a026f109b..1934138afa 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -313,74 +313,62 @@ class ALTTPWorld(World): break def generate_early(self): - # write old options - import dataclasses - is_first = self.player == min(self.multiworld.get_game_players(self.game)) - - for field in dataclasses.fields(self.options_dataclass): - if is_first: - setattr(self.multiworld, field.name, {}) - getattr(self.multiworld, field.name)[self.player] = getattr(self.options, field.name) - # end of old options re-establisher - - player = self.player multiworld = self.multiworld - self.fix_trock_doors = (multiworld.entrance_shuffle[player] != 'vanilla' - or multiworld.mode[player] == 'inverted') - self.fix_skullwoods_exit = multiworld.entrance_shuffle[player] not in ['vanilla', 'simple', 'restricted', - 'dungeons_simple'] - self.fix_palaceofdarkness_exit = multiworld.entrance_shuffle[player] not in ['dungeons_simple', 'vanilla', - 'simple', 'restricted'] - self.fix_trock_exit = multiworld.entrance_shuffle[player] not in ['vanilla', 'simple', 'restricted', - 'dungeons_simple'] + self.fix_trock_doors = (self.options.entrance_shuffle != 'vanilla' or self.options.mode == 'inverted') + self.fix_skullwoods_exit = self.options.entrance_shuffle not in ['vanilla', 'simple', 'restricted', 'dungeons_simple'] + self.fix_palaceofdarkness_exit = self.options.entrance_shuffle not in ['dungeons_simple', 'vanilla', 'simple', 'restricted'] + self.fix_trock_exit = self.options.entrance_shuffle not in ['vanilla', 'simple', 'restricted', 'dungeons_simple'] # fairy bottle fills bottle_options = [ "Bottle (Red Potion)", "Bottle (Green Potion)", "Bottle (Blue Potion)", "Bottle (Bee)", "Bottle (Good Bee)" ] - if multiworld.item_pool[player] not in ["hard", "expert"]: + if self.options.item_pool not in ["hard", "expert"]: bottle_options.append("Bottle (Fairy)") self.waterfall_fairy_bottle_fill = self.random.choice(bottle_options) self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options) - if multiworld.mode[player] == 'standard': - if multiworld.small_key_shuffle[player]: - if (multiworld.small_key_shuffle[player] not in - (small_key_shuffle.option_universal, small_key_shuffle.option_own_dungeons, - small_key_shuffle.option_start_with)): + if self.options.mode == 'standard': + if self.options.small_key_shuffle: + if (self.options.small_key_shuffle not in + (small_key_shuffle.option_universal, small_key_shuffle.option_own_dungeons, + small_key_shuffle.option_start_with)): self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1 - self.multiworld.local_items[self.player].value.add("Small Key (Hyrule Castle)") - self.multiworld.non_local_items[self.player].value.discard("Small Key (Hyrule Castle)") - if multiworld.big_key_shuffle[player]: - self.multiworld.local_items[self.player].value.add("Big Key (Hyrule Castle)") - self.multiworld.non_local_items[self.player].value.discard("Big Key (Hyrule Castle)") + self.options.local_items.value.add("Small Key (Hyrule Castle)") + self.options.non_local_items.value.discard("Small Key (Hyrule Castle)") + if self.options.big_key_shuffle: + self.options.local_items.value.add("Big Key (Hyrule Castle)") + self.options.non_local_items.value.discard("Big Key (Hyrule Castle)") # system for sharing ER layouts self.er_seed = str(multiworld.random.randint(0, 2 ** 64)) - if multiworld.entrance_shuffle[player] != "vanilla" and multiworld.entrance_shuffle_seed[player] != "random": - shuffle = multiworld.entrance_shuffle[player].current_key + if self.options.entrance_shuffle != "vanilla" and self.options.entrance_shuffle_seed != "random": + shuffle = self.options.entrance_shuffle.current_key if shuffle == "vanilla": self.er_seed = "vanilla" - elif (not multiworld.entrance_shuffle_seed[player].value.isdigit()) or multiworld.is_race: + elif (not self.options.entrance_shuffle_seed.value.isdigit()) or multiworld.is_race: self.er_seed = get_same_seed(multiworld, ( - shuffle, multiworld.entrance_shuffle_seed[player].value, multiworld.retro_caves[player], multiworld.mode[player], - multiworld.glitches_required[player])) + shuffle, self.options.entrance_shuffle_seed.value, + self.options.retro_caves, + self.options.mode, + self.options.glitches_required + )) else: # not a race or group seed, use set seed as is. - self.er_seed = int(multiworld.entrance_shuffle_seed[player].value) - elif multiworld.entrance_shuffle[player] == "vanilla": + self.er_seed = int(self.options.entrance_shuffle_seed.value) + elif self.options.entrance_shuffle == "vanilla": self.er_seed = "vanilla" for dungeon_item in ["small_key_shuffle", "big_key_shuffle", "compass_shuffle", "map_shuffle"]: - option = getattr(multiworld, dungeon_item)[player] + option = getattr(self.options, dungeon_item) if option == "own_world": - multiworld.local_items[player].value |= self.item_name_groups[option.item_name_group] + self.options.local_items.value |= self.item_name_groups[option.item_name_group] elif option == "different_world": - multiworld.non_local_items[player].value |= self.item_name_groups[option.item_name_group] - if multiworld.mode[player] == "standard": - multiworld.non_local_items[player].value -= {"Small Key (Hyrule Castle)"} + self.options.non_local_items.value |= self.item_name_groups[option.item_name_group] + if self.options.mode == "standard": + self.options.non_local_items.value -= {"Small Key (Hyrule Castle)"} elif option.in_dungeon: self.dungeon_local_item_names |= self.item_name_groups[option.item_name_group] if option == "original_dungeon": @@ -388,15 +376,15 @@ class ALTTPWorld(World): else: self.options.local_items.value |= self.dungeon_local_item_names - self.difficulty_requirements = difficulties[multiworld.item_pool[player].current_key] + self.difficulty_requirements = difficulties[self.options.item_pool.current_key] # enforce pre-defined local items. - if multiworld.goal[player] in ["local_triforce_hunt", "local_ganon_triforce_hunt"]: - multiworld.local_items[player].value.add('Triforce Piece') + if self.options.goal in ["local_triforce_hunt", "local_ganon_triforce_hunt"]: + self.options.local_items.value.add('Triforce Piece') # Not possible to place crystals outside boss prizes yet (might as well make it consistent with pendants too). - multiworld.non_local_items[player].value -= item_name_groups['Pendants'] - multiworld.non_local_items[player].value -= item_name_groups['Crystals'] + self.options.non_local_items.value -= item_name_groups['Pendants'] + self.options.non_local_items.value -= item_name_groups['Crystals'] create_dungeons = create_dungeons @@ -404,15 +392,15 @@ class ALTTPWorld(World): player = self.player multiworld = self.multiworld - if multiworld.mode[player] != 'inverted': + if self.options.mode != 'inverted': create_regions(multiworld, player) else: create_inverted_regions(multiworld, player) create_shops(multiworld, player) self.create_dungeons() - if (multiworld.glitches_required[player] not in ["no_glitches", "minor_glitches"] and - multiworld.entrance_shuffle[player] in [ + if (self.options.glitches_required not in ["no_glitches", "minor_glitches"] and + self.options.entrance_shuffle in [ "vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"]): self.fix_fake_world = False @@ -420,7 +408,7 @@ class ALTTPWorld(World): old_random = multiworld.random multiworld.random = random.Random(self.er_seed) - if multiworld.mode[player] != 'inverted': + if self.options.mode != 'inverted': link_entrances(multiworld, player) mark_light_world_regions(multiworld, player) else: @@ -505,8 +493,9 @@ class ALTTPWorld(World): if state.has('Silver Bow', item.player): return elif state.has('Bow', item.player) and (self.difficulty_requirements.progressive_bow_limit >= 2 - or self.multiworld.glitches_required[self.player] == 'no_glitches' - or self.multiworld.swordless[self.player]): # modes where silver bow is always required for ganon + or self.options.glitches_required == 'no_glitches' + or self.options.swordless): + # modes where silver bow is always required for ganon return 'Silver Bow' elif self.difficulty_requirements.progressive_bow_limit >= 1: return 'Bow' @@ -549,9 +538,9 @@ class ALTTPWorld(World): break else: raise FillError('Unable to place dungeon prizes') - if world.mode[player] == 'standard' and world.small_key_shuffle[player] \ - and world.small_key_shuffle[player] != small_key_shuffle.option_universal and \ - world.small_key_shuffle[player] != small_key_shuffle.option_own_dungeons: + if self.options.mode == 'standard' and self.options.small_key_shuffle \ + and self.options.small_key_shuffle != small_key_shuffle.option_universal and \ + self.options.small_key_shuffle != small_key_shuffle.option_own_dungeons: world.local_early_items[player]["Small Key (Hyrule Castle)"] = 1 @classmethod @@ -592,27 +581,27 @@ class ALTTPWorld(World): multiworld.spoiler.hashes[player] = get_hash_string(rom.hash) palettes_options = { - 'dungeon': multiworld.uw_palettes[player], - 'overworld': multiworld.ow_palettes[player], - 'hud': multiworld.hud_palettes[player], - 'sword': multiworld.sword_palettes[player], - 'shield': multiworld.shield_palettes[player], + 'dungeon': self.options.uw_palettes, + 'overworld': self.options.ow_palettes, + 'hud': self.options.hud_palettes, + 'sword': self.options.sword_palettes, + 'shield': self.options.shield_palettes, # 'link': world.link_palettes[player] } palettes_options = {key: option.current_key for key, option in palettes_options.items()} - apply_rom_settings(rom, multiworld.heartbeep[player].current_key, - multiworld.heartcolor[player].current_key, - multiworld.quickswap[player], - multiworld.menuspeed[player].current_key, - multiworld.music[player], + apply_rom_settings(rom, self.options.heartbeep.current_key, + self.options.heartcolor.current_key, + self.options.quickswap, + self.options.menuspeed.current_key, + self.options.music, multiworld.sprite[player], None, palettes_options, multiworld, player, True, - reduceflashing=multiworld.reduceflashing[player] or multiworld.is_race, - triforcehud=multiworld.triforcehud[player].current_key, - deathlink=multiworld.death_link[player], - allowcollect=multiworld.allow_collect[player]) + reduceflashing=self.options.reduceflashing or multiworld.is_race, + triforcehud=self.options.triforcehud.current_key, + deathlink=self.options.death_link, + allowcollect=self.options.allow_collect) rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.sfc") rom.write_to_file(rompath) @@ -629,7 +618,7 @@ class ALTTPWorld(World): @classmethod def stage_extend_hint_information(cls, world, hint_data: typing.Dict[int, typing.Dict[int, str]]): er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if - world.entrance_shuffle[player] != "vanilla" or world.retro_caves[player]} + world.worlds[player].options.entrance_shuffle != "vanilla" or world.worlds[player].options.retro_caves} for region in world.regions: if region.player in er_hint_data and region.locations: @@ -745,7 +734,7 @@ class ALTTPWorld(World): f" {self.pyramid_fairy_bottle_fill}") spoiler_handle.write(f"\nWaterfall Fairy ({player_name}):" f" {self.waterfall_fairy_bottle_fill}") - if self.multiworld.boss_shuffle[self.player] != "none": + if self.options.boss_shuffle != "none": def create_boss_map() -> typing.Dict: boss_map = { "Eastern Palace": self.dungeons["Eastern Palace"].boss.name, @@ -762,7 +751,7 @@ class ALTTPWorld(World): "Ganons Tower": "Agahnim 2", "Ganon": "Ganon" } - if self.multiworld.mode[self.player] != 'inverted': + if self.options.mode != 'inverted': boss_map.update({ "Ganons Tower Basement": self.dungeons["Ganons Tower"].bosses["bottom"].name, @@ -847,7 +836,7 @@ class ALTTPWorld(World): "triforce_pieces_available", "triforce_pieces_extra", ] - slot_data = {option_name: getattr(self.multiworld, option_name)[self.player].value for option_name in slot_options} + slot_data = {option_name: getattr(self.options, option_name).value for option_name in slot_options} slot_data.update({ 'mm_medalion': self.required_medallions[0], @@ -868,8 +857,8 @@ def get_same_seed(world, seed_def: tuple) -> str: class ALttPLogic(LogicMixin): def _lttp_has_key(self, item, player, count: int = 1): - if self.multiworld.glitches_required[player] == 'no_logic': + if self.multiworld.worlds[player].options.glitches_required == 'no_logic': return True - if self.multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal: + if self.multiworld.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal: return can_buy_unlimited(self, 'Small Key (Universal)', player) return self.prog_items[player][item] >= count diff --git a/worlds/alttp/test/dungeons/TestDungeon.py b/worlds/alttp/test/dungeons/TestDungeon.py index 5ab1b23065..c06955a122 100644 --- a/worlds/alttp/test/dungeons/TestDungeon.py +++ b/worlds/alttp/test/dungeons/TestDungeon.py @@ -14,8 +14,8 @@ class TestDungeon(LTTPTestBase): self.starting_regions = [] # Where to start exploring self.remove_exits = [] # Block dungeon exits self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] - self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].options.bombless_start.value = True + self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2 create_regions(self.multiworld, 1) self.multiworld.worlds[1].create_dungeons() create_shops(self.multiworld, 1) diff --git a/worlds/alttp/test/inverted/TestInverted.py b/worlds/alttp/test/inverted/TestInverted.py index a0a654991b..3c86b6ba0a 100644 --- a/worlds/alttp/test/inverted/TestInverted.py +++ b/worlds/alttp/test/inverted/TestInverted.py @@ -14,9 +14,9 @@ class TestInverted(TestBase, LTTPTestBase): def setUp(self): self.world_setup() self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] - self.multiworld.mode[1].value = 2 - self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].options.mode.value = 2 + self.multiworld.worlds[1].options.bombless_start.value = True + self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2 create_inverted_regions(self.multiworld, 1) self.world.create_dungeons() create_shops(self.multiworld, 1) diff --git a/worlds/alttp/test/inverted/TestInvertedBombRules.py b/worlds/alttp/test/inverted/TestInvertedBombRules.py index a33beca7a9..ab73d91108 100644 --- a/worlds/alttp/test/inverted/TestInvertedBombRules.py +++ b/worlds/alttp/test/inverted/TestInvertedBombRules.py @@ -12,7 +12,7 @@ class TestInvertedBombRules(LTTPTestBase): def setUp(self): self.world_setup() self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] - self.multiworld.mode[1].value = 2 + self.multiworld.worlds[1].options.mode.value = 2 create_inverted_regions(self.multiworld, 1) self.multiworld.worlds[1].create_dungeons() diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py index bf25c5c9a1..972b617a29 100644 --- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py +++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py @@ -14,10 +14,10 @@ from worlds.alttp.test import LTTPTestBase class TestInvertedMinor(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.mode[1].value = 2 - self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches") - self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].options.mode.value = 2 + self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("minor_glitches") + self.multiworld.worlds[1].options.bombless_start.value = True + self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2 self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] create_inverted_regions(self.multiworld, 1) self.world.create_dungeons() diff --git a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py index 1de22b95e5..4be51f6298 100644 --- a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py +++ b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py @@ -14,10 +14,10 @@ from worlds.alttp.test import LTTPTestBase class TestInvertedOWG(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches") - self.multiworld.mode[1].value = 2 - self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("overworld_glitches") + self.multiworld.worlds[1].options.mode.value = 2 + self.multiworld.worlds[1].options.bombless_start.value = True + self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2 self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] create_inverted_regions(self.multiworld, 1) self.world.create_dungeons() diff --git a/worlds/alttp/test/minor_glitches/TestMinor.py b/worlds/alttp/test/minor_glitches/TestMinor.py index 7663c20a29..d5ffe8cac5 100644 --- a/worlds/alttp/test/minor_glitches/TestMinor.py +++ b/worlds/alttp/test/minor_glitches/TestMinor.py @@ -11,9 +11,9 @@ from worlds.alttp.test import LTTPTestBase class TestMinor(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.glitches_required[1] = GlitchesRequired.from_any("minor_glitches") - self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("minor_glitches") + self.multiworld.worlds[1].options.bombless_start.value = True + self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2 self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] self.world.er_seed = 0 self.world.create_regions() diff --git a/worlds/alttp/test/options/TestOpenPyramid.py b/worlds/alttp/test/options/TestOpenPyramid.py index c7912c43d7..5769b337fb 100644 --- a/worlds/alttp/test/options/TestOpenPyramid.py +++ b/worlds/alttp/test/options/TestOpenPyramid.py @@ -23,7 +23,7 @@ class GoalPyramidTest(PyramidTestBase): } def testCrystalsGoalAccess(self): - self.multiworld.goal[1].value = 1 # crystals + self.multiworld.worlds[1].options.goal.value = 1 # crystals self.assertFalse(self.can_reach_entrance("Pyramid Hole")) self.collect_by_name(["Hammer", "Progressive Glove", "Moon Pearl"]) self.assertTrue(self.can_reach_entrance("Pyramid Hole")) diff --git a/worlds/alttp/test/owg/TestVanillaOWG.py b/worlds/alttp/test/owg/TestVanillaOWG.py index e51970bc50..6b6db1454b 100644 --- a/worlds/alttp/test/owg/TestVanillaOWG.py +++ b/worlds/alttp/test/owg/TestVanillaOWG.py @@ -12,9 +12,9 @@ class TestVanillaOWG(TestBase, LTTPTestBase): def setUp(self): self.world_setup() self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] - self.multiworld.glitches_required[1] = GlitchesRequired.from_any("overworld_glitches") - self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("overworld_glitches") + self.multiworld.worlds[1].options.bombless_start.value = True + self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2 self.multiworld.worlds[1].er_seed = 0 self.multiworld.worlds[1].create_regions() self.multiworld.worlds[1].create_items() diff --git a/worlds/alttp/test/vanilla/TestVanilla.py b/worlds/alttp/test/vanilla/TestVanilla.py index 9b5db7b122..031aec1ff9 100644 --- a/worlds/alttp/test/vanilla/TestVanilla.py +++ b/worlds/alttp/test/vanilla/TestVanilla.py @@ -10,10 +10,10 @@ from worlds.alttp.test import LTTPTestBase class TestVanilla(TestBase, LTTPTestBase): def setUp(self): self.world_setup() - self.multiworld.glitches_required[1] = GlitchesRequired.from_any("no_glitches") + self.multiworld.worlds[1].options.glitches_required = GlitchesRequired.from_any("no_glitches") self.multiworld.worlds[1].difficulty_requirements = difficulties['normal'] - self.multiworld.bombless_start[1].value = True - self.multiworld.shuffle_capacity_upgrades[1].value = 2 + self.multiworld.worlds[1].options.bombless_start.value = True + self.multiworld.worlds[1].options.shuffle_capacity_upgrades.value = 2 self.multiworld.worlds[1].er_seed = 0 self.multiworld.worlds[1].create_regions() self.multiworld.worlds[1].create_items() From 5088b02bfee5cf2950fac2cd03982a70abc03cb9 Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sat, 19 Apr 2025 08:42:20 -0500 Subject: [PATCH 07/46] Unittests: fix world unittests with unittest module (#4895) --- test/worlds/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/worlds/__init__.py b/test/worlds/__init__.py index cf396111bf..4bc017511c 100644 --- a/test/worlds/__init__.py +++ b/test/worlds/__init__.py @@ -12,7 +12,7 @@ def load_tests(loader, standard_tests, pattern): all_tests = [ test_case for folder in folders if os.path.exists(folder) for test_collection in loader.discover(folder, top_level_dir=file_path) - for test_suite in test_collection + for test_suite in test_collection if isinstance(test_suite, unittest.suite.TestSuite) for test_case in test_suite ] From e090153d932a772ac3da43ee044fe6df117a61d8 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 19 Apr 2025 15:44:55 +0200 Subject: [PATCH 08/46] LttP: fix generation if other games are involved (#4901) --- worlds/alttp/Rom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index f69e6bb955..99cc78e2d9 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -850,9 +850,9 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool): rom.write_byte(0x155C9, local_random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle # patch entrance/exits/holes - for region in world.regions: + for region in world.get_regions(player): for exit in region.exits: - if exit.target is not None and exit.player == player: + if exit.target is not None: if isinstance(exit.addresses, tuple): offset = exit.target room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = exit.addresses From efe2b7c539b56fc0bccae75ea5e79f645ddbc240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Sat, 19 Apr 2025 11:55:02 -0400 Subject: [PATCH 09/46] Core: Support default value with cache_self1 (#4667) * add cache_self1_default and tests * merge the two decorators * just change the defaults of the wrap lol * add test for default and default --- Utils.py | 2 ++ test/utils/test_caches.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/Utils.py b/Utils.py index 202b8da178..e4e94a45f6 100644 --- a/Utils.py +++ b/Utils.py @@ -114,6 +114,8 @@ def cache_self1(function: typing.Callable[[S, T], RetType]) -> typing.Callable[[ cache[arg] = res return res + wrap.__defaults__ = function.__defaults__ + return wrap diff --git a/test/utils/test_caches.py b/test/utils/test_caches.py index fc681611f0..b6db75c960 100644 --- a/test/utils/test_caches.py +++ b/test/utils/test_caches.py @@ -35,6 +35,19 @@ class TestCacheSelf1(unittest.TestCase): self.assertFalse(o1.func(1) is o1.func(2)) self.assertFalse(o1.func(1) is o2.func(1)) + def test_cache_default(self) -> None: + class Cls: + @cache_self1 + def func(self, _: Any = 1) -> object: + return object() + + o1 = Cls() + o2 = Cls() + self.assertIs(o1.func(), o1.func()) + self.assertIs(o1.func(1), o1.func()) + self.assertIsNot(o1.func(2), o1.func()) + self.assertIsNot(o1.func(), o2.func()) + def test_gc(self) -> None: # verify that we don't keep a global reference import gc From f8579337480a7e92b6b2a53c47e6445a9d4ad410 Mon Sep 17 00:00:00 2001 From: massimilianodelliubaldini <8584296+massimilianodelliubaldini@users.noreply.github.com> Date: Sat, 19 Apr 2025 17:27:03 -0400 Subject: [PATCH 10/46] Launcher: Add search box (#4863) * Add fuzzy search box to Launcher. * move func bind to the kv and prefer substring matching (#79) * move the func bind to the kv * prefer substr matching * Remove fuzzy results, rely on substring only. * Use early return instead of else. * Add type hint to filter_clients_by_type. * Activate search on keyboard input. * Clear search box when filtering by type. * Update Launcher.py Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --------- Co-authored-by: Aaron Wagener Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- Launcher.py | 31 +++++++++++++++++++++++++++++-- data/launcher.kv | 32 ++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/Launcher.py b/Launcher.py index 29bd71764e..713c0cd318 100644 --- a/Launcher.py +++ b/Launcher.py @@ -230,10 +230,11 @@ def run_gui(path: str, args: Any) -> None: from kivy.properties import ObjectProperty from kivy.core.window import Window from kivy.metrics import dp - from kivymd.uix.button import MDIconButton + from kivymd.uix.button import MDIconButton, MDButton from kivymd.uix.card import MDCard from kivymd.uix.menu import MDDropdownMenu from kivymd.uix.snackbar import MDSnackbar, MDSnackbarText + from kivymd.uix.textfield import MDTextField from kivy.lang.builder import Builder @@ -253,6 +254,7 @@ def run_gui(path: str, args: Any) -> None: navigation: MDGridLayout = ObjectProperty(None) grid: MDGridLayout = ObjectProperty(None) button_layout: ScrollBox = ObjectProperty(None) + search_box: MDTextField = ObjectProperty(None) cards: list[LauncherCard] current_filter: Sequence[str | Type] | None @@ -338,14 +340,29 @@ def run_gui(path: str, args: Any) -> None: scroll_percent = self.button_layout.convert_distance_to_scroll(0, top) self.button_layout.scroll_y = max(0, min(1, scroll_percent[1])) - def filter_clients(self, caller): + def filter_clients_by_type(self, caller: MDButton): self._refresh_components(caller.type) + self.search_box.text = "" + + def filter_clients_by_name(self, caller: MDTextField, name: str) -> None: + if len(name) == 0: + self._refresh_components(self.current_filter) + return + + sub_matches = [ + card for card in self.cards + if name.lower() in card.component.display_name.lower() and card.component.type != Type.HIDDEN + ] + self.button_layout.layout.clear_widgets() + for card in sub_matches: + self.button_layout.layout.add_widget(card) def build(self): self.top_screen = Builder.load_file(Utils.local_path("data/launcher.kv")) self.grid = self.top_screen.ids.grid self.navigation = self.top_screen.ids.navigation self.button_layout = self.top_screen.ids.button_layout + self.search_box = self.top_screen.ids.search_box self.set_colors() self.top_screen.md_bg_color = self.theme_cls.backgroundColor @@ -353,6 +370,7 @@ def run_gui(path: str, args: Any) -> None: refresh_components = self._refresh_components Window.bind(on_drop_file=self._on_drop_file) + Window.bind(on_keyboard=self._on_keyboard) for component in components: self.cards.append(self.build_card(component)) @@ -389,6 +407,15 @@ def run_gui(path: str, args: Any) -> None: else: logging.warning(f"unable to identify component for {file}") + def _on_keyboard(self, window: Window, key: int, scancode: int, codepoint: str, modifier: list[str]): + # Activate search as soon as we start typing, no matter if we are focused on the search box or not. + # Focus first, then capture the first character we type, otherwise it gets swallowed and lost. + # Limit text input to ASCII non-control characters (space bar to tilde). + if not self.search_box.focus: + self.search_box.focus = True + if key in range(32, 126): + self.search_box.text += codepoint + def _stop(self, *largs): # ran into what appears to be https://groups.google.com/g/kivy-users/c/saWDLoYCSZ4 with PyCharm. # Closing the window explicitly cleans it up. diff --git a/data/launcher.kv b/data/launcher.kv index 8c6a8288e4..1cb4e84ab5 100644 --- a/data/launcher.kv +++ b/data/launcher.kv @@ -80,7 +80,7 @@ MDFloatLayout: id: all style: "text" type: (Type.CLIENT, Type.TOOL, Type.ADJUSTER, Type.MISC) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "asterisk" @@ -90,7 +90,7 @@ MDFloatLayout: id: client style: "text" type: (Type.CLIENT, ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "controller" @@ -100,7 +100,7 @@ MDFloatLayout: id: Tool style: "text" type: (Type.TOOL, ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "desktop-classic" @@ -110,7 +110,7 @@ MDFloatLayout: id: adjuster style: "text" type: (Type.ADJUSTER, ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "wrench" @@ -120,7 +120,7 @@ MDFloatLayout: id: misc style: "text" type: (Type.MISC, ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "dots-horizontal-circle-outline" @@ -131,7 +131,7 @@ MDFloatLayout: id: favorites style: "text" type: ("favorites", ) - on_release: app.filter_clients(self) + on_release: app.filter_clients_by_type(self) MDButtonIcon: icon: "star" @@ -141,5 +141,21 @@ MDFloatLayout: MDNavigationDrawerDivider: - ScrollBox: - id: button_layout \ No newline at end of file + MDGridLayout: + id: main_layout + cols: 1 + spacing: "10dp" + + MDTextField: + id: search_box + mode: "outlined" + set_text: app.filter_clients_by_name + + MDTextFieldLeadingIcon: + icon: "magnify" + + MDTextFieldHintText: + text: "Search" + + ScrollBox: + id: button_layout From 20651df307c9cfc059ebe48b56d78cd21fd3cfcf Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sat, 19 Apr 2025 18:21:11 -0500 Subject: [PATCH 11/46] kvui: fix kwargs on ResizableTextField and ImageButton (#4903) --- kvui.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/kvui.py b/kvui.py index 9a8b7109fa..d0d965c30b 100644 --- a/kvui.py +++ b/kvui.py @@ -98,7 +98,7 @@ class ThemedApp(MDApp): class ImageIcon(MDButtonIcon, AsyncImage): def __init__(self, *args, **kwargs): - super().__init__(args, kwargs) + super().__init__(*args, **kwargs) self.image = ApAsyncImage(**kwargs) self.add_widget(self.image) @@ -183,15 +183,17 @@ class ResizableTextField(MDTextField): height_rule = subclass.properties.get("height", None) if height_rule: height_rule.ignore_prev = True - super().__init__(args, kwargs) + super().__init__(*args, **kwargs) def on_release(self: MDButton, *args): super(MDButton, self).on_release(args) self.on_leave() + MDButton.on_release = on_release + # I was surprised to find this didn't already exist in kivy :( class HoverBehavior(object): """originally from https://stackoverflow.com/a/605348110""" @@ -904,7 +906,7 @@ class GameManager(ThemedApp): pos_hint={"center_y": 0.575}) info_button.bind(on_release=self.command_button_action) bottom_layout.add_widget(info_button) - self.textinput = CommandPromptTextInput(size_hint_y=None, height=dp(30), multiline=False, write_tab=False) + self.textinput = CommandPromptTextInput(size_hint_y=None, multiline=False, write_tab=False) self.textinput.bind(on_text_validate=self.on_message) info_button.height = self.textinput.height self.textinput.text_validate_unfocus = False From e4bc7bd1cd44a44981c6b9387e60a626ce3a7d87 Mon Sep 17 00:00:00 2001 From: SunCat Date: Sun, 20 Apr 2025 07:16:46 +0300 Subject: [PATCH 12/46] Checksfinder: Fix the last remnant of outdated game description (#4893) Co-authored-by: Scipio Wright Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/checksfinder/docs/en_ChecksFinder.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/worlds/checksfinder/docs/en_ChecksFinder.md b/worlds/checksfinder/docs/en_ChecksFinder.md index cb33ab3959..4d8fc10091 100644 --- a/worlds/checksfinder/docs/en_ChecksFinder.md +++ b/worlds/checksfinder/docs/en_ChecksFinder.md @@ -7,9 +7,9 @@ config file. ## What is considered a location check in ChecksFinder? -Location checks in are completed when the player finds a spot on a board that has the archipelago logo. The bottom of -the screen has a number next to the archipelago logo, that number is how many you can find so far. You can only get as -many checks as you have gained items, plus five to start with being available. +Location checks get cleared when you open all non-bomb cells in a board. The bottom +of the screen has a number next to the Archipelago logo that displays how many location checks are left to be sent with +your current inventory. You can only get as many checks as you have gained items plus five checks to start with. ## When the player receives an item, what happens? From 199b6bdabb0f632fb2ff03127979ea7eaaa0f044 Mon Sep 17 00:00:00 2001 From: qwint Date: Sun, 20 Apr 2025 06:04:56 -0500 Subject: [PATCH 13/46] Launcher: Update header docstring (#4777) --- Launcher.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Launcher.py b/Launcher.py index 713c0cd318..859ebf0f76 100644 --- a/Launcher.py +++ b/Launcher.py @@ -1,11 +1,11 @@ """ Archipelago Launcher -* if run with APBP as argument, launch corresponding client. -* if run with executable as argument, run it passing argv[2:] as arguments -* if run without arguments, open launcher GUI +* If run with a patch file as argument, launch corresponding client with the patch file as an argument. +* If run with component name as argument, run it passing argv[2:] as arguments. +* If run without arguments or unknown arguments, open launcher GUI. -Scroll down to components= to add components to the launcher as well as setup.py +Additional components can be added to worlds.LauncherComponents.components. """ import argparse From a26abe079ee203c81ebe42ff2290bc8831a8dc05 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 20 Apr 2025 04:07:17 -0700 Subject: [PATCH 14/46] Zillion: Some Code Cleaning (#4780) --- worlds/zillion/__init__.py | 4 ++-- worlds/zillion/client.py | 18 +++++++++--------- worlds/zillion/gen_data.py | 4 ++-- worlds/zillion/id_maps.py | 12 ++++++------ worlds/zillion/logic.py | 14 +++++++------- worlds/zillion/options.py | 16 ++++++++-------- worlds/zillion/test/TestGoal.py | 4 ++-- worlds/zillion/test/TestOptions.py | 2 +- 8 files changed, 37 insertions(+), 37 deletions(-) diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py index d0064b9cb1..588654d259 100644 --- a/worlds/zillion/__init__.py +++ b/worlds/zillion/__init__.py @@ -59,7 +59,7 @@ class ZillionWebWorld(WebWorld): "English", "setup_en.md", "setup/en", - ["beauxq"] + ["beauxq"], )] option_groups = z_option_groups @@ -365,7 +365,7 @@ class ZillionWorld(World): z_loc.zz_loc.item = multi_item multi_items[z_loc.zz_loc.name] = ( z_loc.item.name, - self.multiworld.get_player_name(z_loc.item.player) + self.multiworld.get_player_name(z_loc.item.player), ) # debug_zz_loc_ids.sort() # for name, id_ in debug_zz_loc_ids.items(): diff --git a/worlds/zillion/client.py b/worlds/zillion/client.py index 71f0615d32..1c176e7013 100644 --- a/worlds/zillion/client.py +++ b/worlds/zillion/client.py @@ -147,7 +147,7 @@ class ZillionContext(CommonContext): class ZillionManager(GameManager): logging_pairs = [ - ("Client", "Archipelago") + ("Client", "Archipelago"), ] base_title = "Archipelago Zillion Client" @@ -282,7 +282,7 @@ class ZillionContext(CommonContext): payload = { "cmd": "Get", - "keys": [f"zillion-{self.auth}-doors"] + "keys": [f"zillion-{self.auth}-doors"], } async_start(self.send_msgs([payload])) elif cmd == "Retrieved": @@ -326,7 +326,7 @@ class ZillionContext(CommonContext): n_locations = len(self.missing_locations) + len(self.checked_locations) - 1 # -1 to ignore win logger.info(f"New Check: {loc_name} ({self.ap_local_count}/{n_locations})") async_start(self.send_msgs([ - {"cmd": "LocationChecks", "locations": [server_id]} + {"cmd": "LocationChecks", "locations": [server_id]}, ])) else: # This will happen a lot in Zillion, @@ -338,7 +338,7 @@ class ZillionContext(CommonContext): if not self.finished_game: async_start(self.send_msgs([ {"cmd": "LocationChecks", "locations": [loc_name_to_id["J-6 bottom far left"]]}, - {"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL} + {"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}, ])) self.finished_game = True elif isinstance(event_from_game, events.DoorEventFromGame): @@ -347,7 +347,7 @@ class ZillionContext(CommonContext): payload = { "cmd": "Set", "key": f"zillion-{self.auth}-doors", - "operations": [{"operation": "replace", "value": doors_b64}] + "operations": [{"operation": "replace", "value": doors_b64}], } async_start(self.send_msgs([payload])) elif isinstance(event_from_game, events.MapEventFromGame): @@ -367,7 +367,7 @@ class ZillionContext(CommonContext): # TODO: colors in this text, like sni client? logger.info(f"received {self.ap_id_to_name[ap_id]} from {from_name}") self.to_game.put_nowait( - events.ItemEventToGame(zz_item_ids) + events.ItemEventToGame(zz_item_ids), ) self.next_item = len(self.items_received) @@ -398,7 +398,7 @@ async def zillion_sync_task(ctx: ZillionContext) -> None: logger.info("Start Zillion in RetroArch, then use the /sms command to connect to it.") await asyncio.wait(( asyncio.create_task(ctx.look_for_retroarch.wait()), - asyncio.create_task(ctx.exit_event.wait()) + asyncio.create_task(ctx.exit_event.wait()), ), return_when=asyncio.FIRST_COMPLETED) last_log = "" @@ -443,7 +443,7 @@ async def zillion_sync_task(ctx: ZillionContext) -> None: await asyncio.wait(( asyncio.create_task(ctx.got_slot_data.wait()), asyncio.create_task(ctx.exit_event.wait()), - asyncio.create_task(asyncio.sleep(6)) + asyncio.create_task(asyncio.sleep(6)), ), return_when=asyncio.FIRST_COMPLETED) # to not spam connect packets else: # not correct seed name log_no_spam("incorrect seed - did you mix up roms?") @@ -467,7 +467,7 @@ async def zillion_sync_task(ctx: ZillionContext) -> None: await asyncio.wait(( asyncio.create_task(ctx.got_room_info.wait()), asyncio.create_task(ctx.exit_event.wait()), - asyncio.create_task(asyncio.sleep(6)) + asyncio.create_task(asyncio.sleep(6)), ), return_when=asyncio.FIRST_COMPLETED) else: # no name found in game if not help_message_shown: diff --git a/worlds/zillion/gen_data.py b/worlds/zillion/gen_data.py index 2140733961..295cfa0889 100644 --- a/worlds/zillion/gen_data.py +++ b/worlds/zillion/gen_data.py @@ -19,7 +19,7 @@ class GenData: jsonable = { "multi_items": self.multi_items, "zz_game": self.zz_game.to_jsonable(), - "game_id": list(self.game_id) + "game_id": list(self.game_id), } return json.dumps(jsonable) @@ -37,5 +37,5 @@ class GenData: return GenData( from_json["multi_items"], ZzGame.from_jsonable(from_json["zz_game"]), - bytes(from_json["game_id"]) + bytes(from_json["game_id"]), ) diff --git a/worlds/zillion/id_maps.py b/worlds/zillion/id_maps.py index 25762f99cd..d7f746125a 100644 --- a/worlds/zillion/id_maps.py +++ b/worlds/zillion/id_maps.py @@ -42,7 +42,7 @@ _zz_empty = zz_item_name_to_zz_item["empty"] def make_id_to_others(start_char: Chars) -> tuple[ - dict[int, str], dict[int, int], dict[int, ZzItem] + dict[int, str], dict[int, int], dict[int, ZzItem], ]: """ returns id_to_name, id_to_zz_id, id_to_zz_item """ id_to_name: dict[int, str] = {} @@ -53,19 +53,19 @@ def make_id_to_others(start_char: Chars) -> tuple[ name_to_zz_item = { "Apple": _zz_rescue_0, "Champ": _zz_rescue_1, - "JJ": _zz_empty + "JJ": _zz_empty, } elif start_char == "Apple": name_to_zz_item = { "Apple": _zz_empty, "Champ": _zz_rescue_1, - "JJ": _zz_rescue_0 + "JJ": _zz_rescue_0, } else: # Champ name_to_zz_item = { "Apple": _zz_rescue_0, "Champ": _zz_empty, - "JJ": _zz_rescue_1 + "JJ": _zz_rescue_1, } for name, ap_id in item_name_to_id.items(): @@ -150,10 +150,10 @@ def get_slot_info(regions: Iterable[RegionData], rescues[str(i)] = { "start_char": ri.start_char, "room_code": ri.room_code, - "mask": ri.mask + "mask": ri.mask, } return { "start_char": start_char, "rescues": rescues, - "loc_mem_to_id": loc_memory_to_loc_id + "loc_mem_to_id": loc_memory_to_loc_id, } diff --git a/worlds/zillion/logic.py b/worlds/zillion/logic.py index f3d1814a9e..aa7b77398b 100644 --- a/worlds/zillion/logic.py +++ b/worlds/zillion/logic.py @@ -25,15 +25,15 @@ def set_randomizer_locs(cs: CollectionState, p: int, zz_r: Randomizer) -> int: z_world = cs.multiworld.worlds[p] assert isinstance(z_world, ZillionWorld) - _hash = p + hash_ = p for z_loc in z_world.my_locations: zz_name = z_loc.zz_loc.name zz_item = z_loc.item.zz_item \ if isinstance(z_loc.item, ZillionItem) and z_loc.item.player == p \ else zz_empty zz_r.locations[zz_name].item = zz_item - _hash += (hash(zz_name) * (z_loc.zz_loc.req.gun + 2)) ^ hash(zz_item) - return _hash + hash_ += (hash(zz_name) * (z_loc.zz_loc.req.gun + 2)) ^ hash(zz_item) + return hash_ def item_counts(cs: CollectionState, p: int) -> tuple[tuple[str, int], ...]: @@ -67,11 +67,11 @@ class ZillionLogicCache: returns frozenset of accessible zilliandomizer locations """ # caching this function because it would be slow - _hash = set_randomizer_locs(cs, self._player, self._zz_r) + hash_ = set_randomizer_locs(cs, self._player, self._zz_r) counts = item_counts(cs, self._player) - _hash += hash(counts) + hash_ += hash(counts) - cntr, locs = self._cache.get(_hash, _cache_miss) + cntr, locs = self._cache.get(hash_, _cache_miss) if cntr == cs.prog_items[self._player]: # print("cache hit") return locs @@ -90,6 +90,6 @@ class ZillionLogicCache: tr = frozenset(self._zz_r.get_locations(have_req)) # save result in cache - self._cache[_hash] = (cs.prog_items[self._player].copy(), tr) + self._cache[hash_] = (cs.prog_items[self._player].copy(), tr) return tr diff --git a/worlds/zillion/options.py b/worlds/zillion/options.py index 13f3d43ab0..5669f4da30 100644 --- a/worlds/zillion/options.py +++ b/worlds/zillion/options.py @@ -6,7 +6,7 @@ from Options import Choice, DefaultOnToggle, NamedRange, OptionGroup, PerGameCom from zilliandomizer.options import ( Options as ZzOptions, char_to_gun, char_to_jump, ID, - VBLR as ZzVBLR, Chars, ItemCounts as ZzItemCounts + VBLR as ZzVBLR, Chars, ItemCounts as ZzItemCounts, ) from zilliandomizer.options.parsing import validate as zz_validate @@ -23,7 +23,7 @@ class ZillionContinues(NamedRange): display_name = "continues" special_range_names = { "vanilla": 3, - "infinity": 21 + "infinity": 21, } @@ -247,7 +247,7 @@ class ZillionStartingCards(NamedRange): range_end = 10 display_name = "starting cards" special_range_names = { - "vanilla": 0 + "vanilla": 0, } @@ -315,8 +315,8 @@ class ZillionOptions(PerGameCommonOptions): z_option_groups = [ OptionGroup("item counts", [ ZillionIDCardCount, ZillionBreadCount, ZillionOpaOpaCount, ZillionZillionCount, - ZillionFloppyDiskCount, ZillionScopeCount, ZillionRedIDCardCount - ]) + ZillionFloppyDiskCount, ZillionScopeCount, ZillionRedIDCardCount, + ]), ] @@ -361,7 +361,7 @@ def validate(options: ZillionOptions) -> tuple[ZzOptions, Counter[str]]: "Zillion": options.zillion_count, "Floppy Disk": options.floppy_disk_count, "Scope": options.scope_count, - "Red ID Card": options.red_id_card_count + "Red ID Card": options.red_id_card_count, }) minimums = Counter({ "ID Card": 0, @@ -370,7 +370,7 @@ def validate(options: ZillionOptions) -> tuple[ZzOptions, Counter[str]]: "Zillion": guns_required, "Floppy Disk": floppy_req.value, "Scope": 0, - "Red ID Card": 1 + "Red ID Card": 1, }) for key in minimums: item_counts[key] = max(minimums[key], item_counts[key]) @@ -426,7 +426,7 @@ def validate(options: ZillionOptions) -> tuple[ZzOptions, Counter[str]]: bool(options.early_scope.value), True, # balance defense starting_cards.value, - map_gen + map_gen, ) zz_validate(zz_op) return zz_op, item_counts diff --git a/worlds/zillion/test/TestGoal.py b/worlds/zillion/test/TestGoal.py index 1c79305699..9b891ddca0 100644 --- a/worlds/zillion/test/TestGoal.py +++ b/worlds/zillion/test/TestGoal.py @@ -104,7 +104,7 @@ class TestGoalAppleStart(ZillionTestBase): "start_char": "Apple", "jump_levels": "balanced", "gun_levels": "low", - "zillion_count": 5 + "zillion_count": 5, } def test_guns_jj_first(self) -> None: @@ -131,7 +131,7 @@ class TestGoalChampStart(ZillionTestBase): "jump_levels": "low", "gun_levels": "balanced", "opa_opa_count": 5, - "opas_per_level": 1 + "opas_per_level": 1, } def test_jump_jj_first(self) -> None: diff --git a/worlds/zillion/test/TestOptions.py b/worlds/zillion/test/TestOptions.py index 904063fd3c..f37a1f24a7 100644 --- a/worlds/zillion/test/TestOptions.py +++ b/worlds/zillion/test/TestOptions.py @@ -18,7 +18,7 @@ class OptionsTest(ZillionTestBase): """ all of the valid values for the AP options map to valid values for ZZ options """ for option_name, vblr_class in ( ("jump_levels", ZillionJumpLevels), - ("gun_levels", ZillionGunLevels) + ("gun_levels", ZillionGunLevels), ): for value in vblr_class.name_lookup.values(): self.options = {option_name: value} From e498cc7d4853b6e7a0ba61f3ffee03b0a0c54501 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Sun, 20 Apr 2025 04:21:40 -0700 Subject: [PATCH 15/46] Tests: Don't use `type` as `Callable` (#4866) --- test/general/test_entrance_rando.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/general/test_entrance_rando.py b/test/general/test_entrance_rando.py index 56a059ecf2..65853dfc8b 100644 --- a/test/general/test_entrance_rando.py +++ b/test/general/test_entrance_rando.py @@ -1,3 +1,4 @@ +from typing import Callable import unittest from enum import IntEnum @@ -34,7 +35,7 @@ def generate_entrance_pair(region: Region, name_suffix: str, group: int): def generate_disconnected_region_grid(multiworld: MultiWorld, grid_side_length: int, region_size: int = 0, - region_type: type[Region] = Region): + region_creator: Callable[[str, int, MultiWorld], Region] = Region): """ Generates a grid-like region structure for ER testing, where menu is connected to the top-left region, and each region "in vanilla" has 2 2-way exits going either down or to the right, until reaching the goal region in the @@ -44,7 +45,7 @@ def generate_disconnected_region_grid(multiworld: MultiWorld, grid_side_length: for col in range(grid_side_length): index = row * grid_side_length + col name = f"region{index}" - region = region_type(name, 1, multiworld) + region = region_creator(name, 1, multiworld) multiworld.regions.append(region) generate_locations(region_size, 1, region=region, tag=f"_{name}") @@ -465,7 +466,7 @@ class TestRandomizeEntrances(unittest.TestCase): entrance_type = CustomEntrance multiworld = generate_test_multiworld() - generate_disconnected_region_grid(multiworld, 5, region_type=CustomRegion) + generate_disconnected_region_grid(multiworld, 5, region_creator=CustomRegion) self.assertRaises(EntranceRandomizationError, randomize_entrances, multiworld.worlds[1], False, directionally_matched_group_lookup) From eb1fef1f923a9e6eb37a5cda2bbdab71d7422bc4 Mon Sep 17 00:00:00 2001 From: shananas <47014056+shananas@users.noreply.github.com> Date: Sun, 20 Apr 2025 08:20:23 -0400 Subject: [PATCH 16/46] KH2: Update Docs (#4869) --- worlds/kh2/docs/setup_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/worlds/kh2/docs/setup_en.md b/worlds/kh2/docs/setup_en.md index 2e1022f3ef..e0fc23e024 100644 --- a/worlds/kh2/docs/setup_en.md +++ b/worlds/kh2/docs/setup_en.md @@ -10,7 +10,7 @@ Kingdom Hearts II Final Mix from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts) or [Steam](https://store.steampowered.com/app/2552430/KINGDOM_HEARTS_HD_1525_ReMIX/) - Follow this Guide to set up these requirements [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/) - 1. Version 25.01.26.0 or greater OpenKH Mod Manager with Panacea + 1. Version 25.03.16.0 or greater OpenKH Mod Manager with Panacea 2. Lua Backend from the OpenKH Mod Manager 3. Install the mod `KH2FM-Mods-Num/GoA-ROM-Edition` using OpenKH Mod Manager - Needed for Archipelago From a76ee010ebdd3e89a69e1fe2705581728cc9c1b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Sun, 20 Apr 2025 08:21:02 -0400 Subject: [PATCH 17/46] Stardew Valley: Make Bus and Boat Require Money (#4833) --- worlds/stardew_valley/logic/money_logic.py | 12 ++++++------ worlds/stardew_valley/rules.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/worlds/stardew_valley/logic/money_logic.py b/worlds/stardew_valley/logic/money_logic.py index e272436fd8..f5ca991e72 100644 --- a/worlds/stardew_valley/logic/money_logic.py +++ b/worlds/stardew_valley/logic/money_logic.py @@ -35,8 +35,8 @@ GrindLogicMixin, ShippingLogicMixin]]): @cache_self1 def can_have_earned_total(self, amount: int) -> StardewRule: - if amount < 1000: - return True_() + if amount <= 1000: + return self.logic.true_ pierre_rule = self.logic.region.can_reach_all((Region.pierre_store, Region.forest)) willy_rule = self.logic.region.can_reach_all((Region.fish_shop, LogicRegion.fishing)) @@ -44,19 +44,19 @@ GrindLogicMixin, ShippingLogicMixin]]): robin_rule = self.logic.region.can_reach_all((Region.carpenter, Region.secret_woods)) shipping_rule = self.logic.shipping.can_use_shipping_bin - if amount < 2000: + if amount <= 2500: selling_any_rule = pierre_rule | willy_rule | clint_rule | robin_rule | shipping_rule return selling_any_rule - if amount < 5000: + if amount <= 5000: selling_all_rule = (pierre_rule & willy_rule & clint_rule & robin_rule) | shipping_rule return selling_all_rule - if amount < 10000: + if amount <= 10000: return shipping_rule seed_rules = self.logic.region.can_reach(Region.pierre_store) - if amount < 40000: + if amount <= 40000: return shipping_rule & seed_rules percent_progression_items_needed = min(90, amount // 20000) diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py index bdfbc20488..4b1ff2ad56 100644 --- a/worlds/stardew_valley/rules.py +++ b/worlds/stardew_valley/rules.py @@ -201,7 +201,7 @@ def set_entrance_rules(logic: StardewLogic, multiworld, player, world_options: S movie_theater_rule = logic.has_movie_theater() set_entrance_rule(multiworld, player, Entrance.enter_movie_theater, movie_theater_rule) set_entrance_rule(multiworld, player, Entrance.purchase_movie_ticket, movie_theater_rule) - set_entrance_rule(multiworld, player, Entrance.take_bus_to_desert, logic.received("Bus Repair")) + set_entrance_rule(multiworld, player, Entrance.take_bus_to_desert, logic.received("Bus Repair") & logic.money.can_spend(500)) set_entrance_rule(multiworld, player, Entrance.enter_skull_cavern, logic.received(Wallet.skull_key)) set_entrance_rule(multiworld, player, LogicEntrance.talk_to_mines_dwarf, logic.wallet.can_speak_dwarf() & logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron)) @@ -362,7 +362,7 @@ def set_island_entrances_rules(logic: StardewLogic, multiworld, player, world_op Entrance.use_island_obelisk: logic.can_use_obelisk(Transportation.island_obelisk), Entrance.use_farm_obelisk: logic.can_use_obelisk(Transportation.farm_obelisk), Entrance.fish_shop_to_boat_tunnel: boat_repaired, - Entrance.boat_to_ginger_island: boat_repaired, + Entrance.boat_to_ginger_island: boat_repaired & logic.money.can_spend(1000), Entrance.island_south_to_west: logic.received("Island West Turtle"), Entrance.island_south_to_north: logic.received("Island North Turtle"), Entrance.island_west_to_islandfarmhouse: logic.received("Island Farmhouse"), From b756a67c2ac0be869fbb001f6c1d726c94efe821 Mon Sep 17 00:00:00 2001 From: Trevor L <80716066+TRPG0@users.noreply.github.com> Date: Sun, 20 Apr 2025 06:31:58 -0600 Subject: [PATCH 18/46] BRC: Update Setup Guide (#4861) Co-authored-by: Scipio Wright Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/bomb_rush_cyberfunk/docs/setup_en.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/worlds/bomb_rush_cyberfunk/docs/setup_en.md b/worlds/bomb_rush_cyberfunk/docs/setup_en.md index 14da25adb3..aac6e86612 100644 --- a/worlds/bomb_rush_cyberfunk/docs/setup_en.md +++ b/worlds/bomb_rush_cyberfunk/docs/setup_en.md @@ -16,8 +16,11 @@ Cyberfunk root folder. *Do not use any pre-release versions of BepInEx 6.* 2. Start Bomb Rush Cyberfunk once so that BepInEx can create its required configuration files. -3. Download the zip archive from the [releases](https://github.com/TRPG0/BRC-Archipelago/releases) page, and extract its -contents into `BepInEx\plugins`. +3. Download `ModLocalizer.dll` from its [releases](https://github.com/TRPG0/BRC-ModLocalizer/releases) page, and put it +in `BepInEx\plugins`. + +4. Download the zip archive for the Archipelago plugin from its [releases](https://github.com/TRPG0/BRC-Archipelago/releases) +page, and extract the contents into `BepInEx\plugins`. After installing Archipelago, there are some additional mods that can also be installed for a better experience: From 04aa4715269c2f7aecfe83e514ea0c042fa73eba Mon Sep 17 00:00:00 2001 From: Omnises Nihilis <38057571+Omnises@users.noreply.github.com> Date: Sun, 20 Apr 2025 05:43:52 -0700 Subject: [PATCH 19/46] KH2: Update Docs (#4871) --- worlds/kh2/docs/setup_en.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/worlds/kh2/docs/setup_en.md b/worlds/kh2/docs/setup_en.md index e0fc23e024..a1248d1095 100644 --- a/worlds/kh2/docs/setup_en.md +++ b/worlds/kh2/docs/setup_en.md @@ -27,7 +27,7 @@ Kingdom Hearts II Final Mix from the [Epic Games Store](https://store.epicgames. Load this mod just like the GoA ROM you did during the KH2 Rando setup. `JaredWeakStrike/APCompanion`
Have this mod second-highest priority below the .zip seed.
-This mod is based upon Num's Garden of Assemblege Mod and requires it to work. Without Num this could not be possible. +This mod is based upon Num's Garden of Assemblage Mod and requires it to work. Without Num this could not be possible.

Required: Auto Save Mod and KH2 Lua Library

@@ -35,7 +35,7 @@ Load these mods just like you loaded the GoA ROM mod during the KH2 Rando setup.

Optional QoL Mods: AP QoL and Bear Skip

-`JaredWeakStrike/AP_QOL` Makes the urns minigames much faster, makes Cavern of Remembrance orbs drop significantly more drive orbs for refilling drive/leveling master form, skips the animation when using the bulky vendor RC, skips carpet escape auto scroller in Agrabah 2, and prevents the wardrobe in the Beasts Castle wardrobe push minigame from waking up while being pushed. +`JaredWeakStrike/AP_QOL` Makes the urns minigames much faster, makes Cavern of Remembrance orbs drop significantly more drive orbs for refilling drive/leveling master form, skips the animation when using the bulky vendor RC, skips carpet escape auto-scroller in Agrabah 2, and prevents the wardrobe in the Beasts Castle wardrobe push minigame from waking up while being pushed. `shananas/BearSkip` Skips all minigames in 100 Acre Woods except the Spooky Cave minigame since there are chests in Spooky Cave you can only get during the minigame. For Spooky Cave, Pooh is moved to the other side of the invisible wall that prevents you from using his RC to finish the minigame. @@ -83,6 +83,9 @@ Enter The room's port number into the top box where the x's are and pres - Loading into Simulated Twilight Town Instead of the GOA. - To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Panacea and Lua Backend Steps. +- Using a seed from the standalone KH2 Randomizer Seed Generator. + - The Archipelago version of the KH2 Randomizer does not use this Seed Generator; refer to the [Archipelago Setup](https://archipelago.gg/tutorial/Archipelago/setup/en) to learn how to generate and play a seed through Archipelago. +

Best Practices

- Make a save at the start of the GoA before opening anything. This will be the file to select when loading an autosave if/when your game crashes. @@ -139,4 +142,4 @@ This pack will handle logic, received items, checked locations and autotabbing f - Why should I install the auto save mod at `KH2FM-Mods-equations19/auto-save` and `KH2FM-Mods-equations19/KH2-Lua-Library`? - Because Kingdom Hearts 2 is prone to crashes and will keep you from losing your progress. Both mods are needed for auto save to work. - How do I load an auto save? - - To load an auto-save, hold down the Select or your equivalent on your prefered controller while choosing a file. Make sure to hold the button down the whole time. + - To load an auto-save, hold down the Select or your equivalent on your preferred controller while choosing a file. Make sure to hold the button down the whole time. From b76f2163a4902ddb33d101dd5551e54bc31cc4ef Mon Sep 17 00:00:00 2001 From: Silvris <58583688+Silvris@users.noreply.github.com> Date: Sun, 20 Apr 2025 08:08:30 -0500 Subject: [PATCH 20/46] MM2: Fix invalid weakness failsafe and refactor weakness tests (#4899) --- worlds/mm2/rules.py | 4 +-- worlds/mm2/test/test_weakness.py | 50 +++++++++++++++++++++----------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/worlds/mm2/rules.py b/worlds/mm2/rules.py index d84c13c827..67431c9956 100644 --- a/worlds/mm2/rules.py +++ b/worlds/mm2/rules.py @@ -215,7 +215,7 @@ def set_rules(world: "MM2World") -> None: continue highest, wp = max(zip(weapon_weight.values(), weapon_weight.keys())) uses = weapon_energy[wp] // weapon_costs[wp] - if int(uses * boss_damage[wp]) > boss_health[boss]: + if int(uses * boss_damage[wp]) >= boss_health[boss]: used = ceil(boss_health[boss] / boss_damage[wp]) weapon_energy[wp] -= weapon_costs[wp] * used boss_health[boss] = 0 @@ -226,7 +226,7 @@ def set_rules(world: "MM2World") -> None: # it should be impossible to be out of energy, simply because even if every boss took 1 from # Quick Boomerang and no other, it would only be 28 off from defeating all 9, # which Metal Blade should be able to cover - wp, max_uses = max((weapon, weapon_energy[weapon] // weapon_costs[weapon]) + max_uses, wp = max((weapon_energy[weapon] // weapon_costs[weapon], weapon) for weapon in weapon_weight if weapon != 0 and (weapon != 8 or boss != 12)) # Wily Machine cannot under any circumstances take damage from Time Stopper, prevent this diff --git a/worlds/mm2/test/test_weakness.py b/worlds/mm2/test/test_weakness.py index c294ce5ac9..817241c40b 100644 --- a/worlds/mm2/test/test_weakness.py +++ b/worlds/mm2/test/test_weakness.py @@ -2,9 +2,9 @@ from math import ceil from . import MM2TestBase from ..options import bosses +from ..rules import minimum_weakness_requirement -# Need to figure out how this test should work def validate_wily_5(base: MM2TestBase) -> None: world = base.multiworld.worlds[base.player] weapon_damage = world.weapon_damage @@ -67,38 +67,54 @@ def validate_wily_5(base: MM2TestBase) -> None: weapon_weight.pop(wp) -class StrictWeaknessTests(MM2TestBase): +class WeaknessTests(MM2TestBase): options = { - "strict_weakness": True, "yoku_jumps": True, - "enable_lasers": True + "enable_lasers": True, } - def test_that_every_boss_has_a_weakness(self) -> None: world = self.multiworld.worlds[self.player] weapon_damage = world.weapon_damage for boss in range(14): - if not any(weapon_damage[weapon][boss] for weapon in range(9)): + if not any(weapon_damage[weapon][boss] >= minimum_weakness_requirement[weapon] for weapon in range(9)): self.fail(f"Boss {boss} generated without weakness! Seed: {self.multiworld.seed}") def test_wily_5(self) -> None: validate_wily_5(self) -class RandomStrictWeaknessTests(MM2TestBase): +class StrictWeaknessTests(WeaknessTests): + options = { + "strict_weakness": True, + **WeaknessTests.options + } + + +class RandomWeaknessTests(WeaknessTests): + options = { + "random_weakness": "randomized", + **WeaknessTests.options + } + + +class ShuffledWeaknessTests(WeaknessTests): + options = { + "random_weakness": "shuffled", + **WeaknessTests.options + } + + +class RandomStrictWeaknessTests(WeaknessTests): options = { "strict_weakness": True, "random_weakness": "randomized", - "yoku_jumps": True, - "enable_lasers": True + **WeaknessTests.options } - def test_that_every_boss_has_a_weakness(self) -> None: - world = self.multiworld.worlds[self.player] - weapon_damage = world.weapon_damage - for boss in range(14): - if not any(weapon_damage[weapon][boss] for weapon in range(9)): - self.fail(f"Boss {boss} generated without weakness! Seed: {self.multiworld.seed}") - def test_wily_5(self) -> None: - validate_wily_5(self) +class ShuffledStrictWeaknessTests(WeaknessTests): + options = { + "strict_weakness": True, + "random_weakness": "shuffled", + **WeaknessTests.options + } From be0f23beb3ff546fa493f2cbd0831879254e7fb4 Mon Sep 17 00:00:00 2001 From: LiquidCat64 <74896918+LiquidCat64@users.noreply.github.com> Date: Sun, 20 Apr 2025 07:46:57 -0600 Subject: [PATCH 21/46] CV64: Some DeathLink Adjustments (#4727) --- worlds/cv64/client.py | 44 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/worlds/cv64/client.py b/worlds/cv64/client.py index cec5f551b9..849ca44a8a 100644 --- a/worlds/cv64/client.py +++ b/worlds/cv64/client.py @@ -10,12 +10,20 @@ from worlds._bizhawk.client import BizHawkClient if TYPE_CHECKING: from worlds._bizhawk.context import BizHawkClientContext +DEATHLINK_AREA_NUMBERS = [0, 1, 1, 2, 2, 2, 2, 3, 4, 5, 5, 5, 5, 5, 5, 5, + 7, 9, 8, 6, 12, 12, 13, 11, 12, 5, 2, 10, 13, 13] + +DEATHLINK_AREA_NAMES = ["Forest of Silence", "Castle Wall", "Villa", "Tunnel", "Underground Waterway", "Castle Center", + "Duel Tower", "Tower of Execution", "Tower of Science", "Tower of Sorcery", "Room of Clocks", + "Clock Tower", "Castle Keep", "Level: You Cheated"] + class Castlevania64Client(BizHawkClient): game = "Castlevania 64" system = "N64" patch_suffix = ".apcv64" self_induced_death = False + time_of_sent_death = None received_deathlinks = 0 death_causes = [] currently_shopping = False @@ -62,15 +70,19 @@ class Castlevania64Client(BizHawkClient): return if "tags" not in args: return - if "DeathLink" in args["tags"] and args["data"]["source"] != ctx.slot_info[ctx.slot].name: + if "DeathLink" in args["tags"] and args["data"]["time"] != self.time_of_sent_death: self.received_deathlinks += 1 if "cause" in args["data"]: cause = args["data"]["cause"] + # If the other game sent a death with a blank string for the cause, use the default death message. + if cause == "": + cause = f"{args['data']['source']} killed you without a word!" # Truncate the death cause message at 120 characters. if len(cause) > 120: cause = cause[0:120] else: - cause = f"{args['data']['source']} killed you!" + # If the other game sent a death with no cause at all, use the default death message. + cause = f"{args['data']['source']} killed you without a word!" self.death_causes.append(cause) async def game_watcher(self, ctx: "BizHawkClientContext") -> None: @@ -115,11 +127,30 @@ class Castlevania64Client(BizHawkClient): if "DeathLink" in ctx.tags and save_struct[0xA4] & 0x80 and not self.self_induced_death and not \ deathlink_induced_death: self.self_induced_death = True - if save_struct[0xA4] & 0x08: - # Special death message for dying while having the Vamp status. - await ctx.send_death(f"{ctx.player_names[ctx.slot]} became a vampire and drank your blood!") + + # If the player died at the Castle Keep exterior map on one of the Room of Clocks boss towers + # (determinable by checking the entrance value as well as the map value), consider Room of Clocks the + # actual area of death. + if save_struct[0xAD] == 0x14 and save_struct[0xAF] in [0, 1]: + area_of_death = DEATHLINK_AREA_NAMES[10] + # Otherwise, determine what area the player perished in from the current map ID. else: - await ctx.send_death(f"{ctx.player_names[ctx.slot]} perished. Dracula has won!") + area_of_death = DEATHLINK_AREA_NAMES[DEATHLINK_AREA_NUMBERS[save_struct[0xAD]]] + + # If we had the Vamp status while dying, use a special message. + if save_struct[0xA4] & 0x08: + death_message = (f"{ctx.player_names[ctx.slot]} became a vampire at {area_of_death} and drank your " + f"blood!") + # Otherwise, use the generic one. + else: + death_message = f"{ctx.player_names[ctx.slot]} perished in {area_of_death}. Dracula has won!" + + # Send the death. + await ctx.send_death(death_message) + + # Record the time in which the death was sent so when we receive the packet we can tell it wasn't our + # own death. ctx.on_deathlink overwrites it later, so it MUST be grabbed now. + self.time_of_sent_death = ctx.last_death_link # Write any DeathLinks received along with the corresponding death cause starting with the oldest. # To minimize Bizhawk Write jank, the DeathLink write will be prioritized over the item received one. @@ -208,6 +239,7 @@ class Castlevania64Client(BizHawkClient): # Send game clear if we're in either any ending cutscene or the credits state. if not ctx.finished_game and (0x26 <= int(cutscene_value) <= 0x2E or game_state == 0x0000000B): + ctx.finished_game = True await ctx.send_msgs([{ "cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL From 33dc845de8b252a781c80d2d27039f3e74fc0333 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Sun, 20 Apr 2025 09:48:09 -0400 Subject: [PATCH 22/46] TUNIC: Fix UT Issue with Fewer Shops Option (#4873) --- worlds/tunic/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index ff1dc414c3..86a91a336b 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -162,9 +162,14 @@ class TunicWorld(World): self.options.shuffle_ladders.value = self.passthrough["shuffle_ladders"] self.options.grass_randomizer.value = self.passthrough.get("grass_randomizer", 0) self.options.breakable_shuffle.value = self.passthrough.get("breakable_shuffle", 0) - self.options.fixed_shop.value = self.options.fixed_shop.option_false self.options.laurels_location.value = self.options.laurels_location.option_anywhere self.options.combat_logic.value = self.passthrough["combat_logic"] + + self.options.fixed_shop.value = self.options.fixed_shop.option_false + if ("ziggurat2020_3, ziggurat2020_1_zig2_skip" in self.passthrough["Entrance Rando"].keys() + or "ziggurat2020_3, ziggurat2020_1_zig2_skip" in self.passthrough["Entrance Rando"].values()): + self.options.fixed_shop.value = self.options.fixed_shop.option_true + else: self.using_ut = False else: From 22941168cdd475c2ce07bb867bd1171b58b84bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:17:22 -0400 Subject: [PATCH 23/46] Stardew Valley: Refactor Animals to use Content Packs (#4320) --- worlds/stardew_valley/content/game_content.py | 21 ++++-- worlds/stardew_valley/content/unpacking.py | 4 ++ .../content/vanilla/ginger_island.py | 20 +++++- .../content/vanilla/the_farm.py | 66 ++++++++++++++++++- worlds/stardew_valley/data/animal.py | 23 +++++++ worlds/stardew_valley/data/bundle_data.py | 4 +- worlds/stardew_valley/data/museum_data.py | 9 +-- worlds/stardew_valley/logic/animal_logic.py | 59 +++++------------ worlds/stardew_valley/logic/festival_logic.py | 31 +++++++-- worlds/stardew_valley/logic/logic.py | 25 +++---- .../logic/logic_and_mods_design.md | 14 +++- worlds/stardew_valley/logic/source_logic.py | 12 +++- worlds/stardew_valley/strings/animal_names.py | 5 +- .../strings/animal_product_names.py | 19 +++++- worlds/stardew_valley/strings/metal_names.py | 2 - 15 files changed, 229 insertions(+), 85 deletions(-) create mode 100644 worlds/stardew_valley/data/animal.py diff --git a/worlds/stardew_valley/content/game_content.py b/worlds/stardew_valley/content/game_content.py index 8a72a4811d..c3f8e9f8e9 100644 --- a/worlds/stardew_valley/content/game_content.py +++ b/worlds/stardew_valley/content/game_content.py @@ -1,9 +1,10 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Dict, Iterable, Set, Any, Mapping, Type, Tuple, Union +from typing import Iterable, Set, Any, Mapping, Type, Tuple, Union from .feature import booksanity, cropsanity, fishsanity, friendsanity, skill_progression, building_progression, tool_progression +from ..data.animal import Animal from ..data.building import Building from ..data.fish_data import FishItem from ..data.game_item import GameItem, Source, ItemTag @@ -18,12 +19,13 @@ class StardewContent: # regions -> To be used with can reach rule - game_items: Dict[str, GameItem] = field(default_factory=dict) - fishes: Dict[str, FishItem] = field(default_factory=dict) - villagers: Dict[str, Villager] = field(default_factory=dict) - farm_buildings: Dict[str, Building] = field(default_factory=dict) - skills: Dict[str, Skill] = field(default_factory=dict) - quests: Dict[str, Any] = field(default_factory=dict) + game_items: dict[str, GameItem] = field(default_factory=dict) + fishes: dict[str, FishItem] = field(default_factory=dict) + villagers: dict[str, Villager] = field(default_factory=dict) + farm_buildings: dict[str, Building] = field(default_factory=dict) + animals: dict[str, Animal] = field(default_factory=dict) + skills: dict[str, Skill] = field(default_factory=dict) + quests: dict[str, Any] = field(default_factory=dict) def find_sources_of_type(self, types: Union[Type[Source], Tuple[Type[Source]]]) -> Iterable[Source]: for item in self.game_items.values(): @@ -109,6 +111,11 @@ class ContentPack: def farm_building_hook(self, content: StardewContent): ... + animals: Iterable[Animal] = () + + def animal_hook(self, content: StardewContent): + ... + skills: Iterable[Skill] = () def skill_hook(self, content: StardewContent): diff --git a/worlds/stardew_valley/content/unpacking.py b/worlds/stardew_valley/content/unpacking.py index 2d50f7718b..faa7cb5399 100644 --- a/worlds/stardew_valley/content/unpacking.py +++ b/worlds/stardew_valley/content/unpacking.py @@ -65,6 +65,10 @@ def register_pack(content: StardewContent, pack: ContentPack): content.farm_buildings[building.name] = building pack.farm_building_hook(content) + for animal in pack.animals: + content.animals[animal.name] = animal + pack.animal_hook(content) + for skill in pack.skills: content.skills[skill.name] = skill pack.skill_hook(content) diff --git a/worlds/stardew_valley/content/vanilla/ginger_island.py b/worlds/stardew_valley/content/vanilla/ginger_island.py index 2fbcb03279..edb135ea30 100644 --- a/worlds/stardew_valley/content/vanilla/ginger_island.py +++ b/worlds/stardew_valley/content/vanilla/ginger_island.py @@ -1,15 +1,19 @@ from .pelican_town import pelican_town as pelican_town_content_pack from ..game_content import ContentPack, StardewContent from ...data import villagers_data, fish_data -from ...data.game_item import ItemTag, Tag +from ...data.animal import Animal, AnimalName, OstrichIncubatorSource +from ...data.game_item import ItemTag, Tag, CustomRuleSource from ...data.harvest import ForagingSource, HarvestFruitTreeSource, HarvestCropSource from ...data.requirement import WalnutRequirement from ...data.shop import ShopSource +from ...strings.animal_product_names import AnimalProduct from ...strings.book_names import Book +from ...strings.building_names import Building from ...strings.crop_names import Fruit, Vegetable from ...strings.fish_names import Fish from ...strings.forageable_names import Forageable, Mushroom from ...strings.fruit_tree_names import Sapling +from ...strings.generic_names import Generic from ...strings.metal_names import Fossil, Mineral from ...strings.region_names import Region, LogicRegion from ...strings.season_names import Season @@ -51,6 +55,13 @@ ginger_island_content_pack = GingerIslandContentPack( Vegetable.taro_root: (HarvestCropSource(seed=Seed.taro, seasons=(Season.summer,)),), Fruit.pineapple: (HarvestCropSource(seed=Seed.pineapple, seasons=(Season.summer,)),), + # Temporary animal stuff, will be moved once animal products are properly content-packed + AnimalProduct.ostrich_egg_starter: (CustomRuleSource(lambda logic: logic.tool.can_forage(Generic.any, Region.island_north, True) + & logic.has(Forageable.journal_scrap) + & logic.region.can_reach(Region.volcano_floor_5)),), + AnimalProduct.ostrich_egg: (CustomRuleSource(lambda logic: logic.has(AnimalProduct.ostrich_egg_starter) + | logic.animal.has_animal(AnimalName.ostrich)),), + }, shop_sources={ Seed.taro: (ShopSource(items_price=((2, Fossil.bone_fragment),), shop_region=Region.island_trader),), @@ -81,5 +92,12 @@ ginger_island_content_pack = GingerIslandContentPack( ), villagers=( villagers_data.leo, + ), + animals=( + Animal(AnimalName.ostrich, + required_building=Building.barn, + sources=( + OstrichIncubatorSource(AnimalProduct.ostrich_egg_starter), + )), ) ) diff --git a/worlds/stardew_valley/content/vanilla/the_farm.py b/worlds/stardew_valley/content/vanilla/the_farm.py index 68d0bf10f6..183025e43f 100644 --- a/worlds/stardew_valley/content/vanilla/the_farm.py +++ b/worlds/stardew_valley/content/vanilla/the_farm.py @@ -1,7 +1,12 @@ from .pelican_town import pelican_town as pelican_town_content_pack from ..game_content import ContentPack +from ...data.animal import IncubatorSource, Animal, AnimalName from ...data.harvest import FruitBatsSource, MushroomCaveSource +from ...data.shop import ShopSource +from ...strings.animal_product_names import AnimalProduct +from ...strings.building_names import Building from ...strings.forageable_names import Forageable, Mushroom +from ...strings.region_names import Region the_farm = ContentPack( "The Farm (Vanilla)", @@ -39,5 +44,64 @@ the_farm = ContentPack( Mushroom.red: ( MushroomCaveSource(), ), - } + }, + animals=( + Animal(AnimalName.chicken, + required_building=Building.coop, + sources=( + ShopSource(shop_region=Region.ranch, money_price=800), + # For now there is no way to obtain the starter item, so this adds additional rules in the system for nothing. + # IncubatorSource(AnimalProduct.egg_starter) + )), + Animal(AnimalName.cow, + required_building=Building.barn, + sources=( + ShopSource(shop_region=Region.ranch, money_price=1500), + )), + Animal(AnimalName.goat, + required_building=Building.big_barn, + sources=( + ShopSource(shop_region=Region.ranch, money_price=4000), + )), + Animal(AnimalName.duck, + required_building=Building.big_coop, + sources=( + ShopSource(shop_region=Region.ranch, money_price=1200), + # For now there is no way to obtain the starter item, so this adds additional rules in the system for nothing. + # IncubatorSource(AnimalProduct.duck_egg_starter) + )), + Animal(AnimalName.sheep, + required_building=Building.deluxe_barn, + sources=( + ShopSource(shop_region=Region.ranch, money_price=8000), + )), + Animal(AnimalName.rabbit, + required_building=Building.deluxe_coop, + sources=( + ShopSource(shop_region=Region.ranch, money_price=8000), + )), + Animal(AnimalName.pig, + required_building=Building.deluxe_barn, + sources=( + ShopSource(shop_region=Region.ranch, money_price=16000), + )), + Animal(AnimalName.void_chicken, + required_building=Building.big_coop, + sources=( + IncubatorSource(AnimalProduct.void_egg_starter), + )), + Animal(AnimalName.golden_chicken, + required_building=Building.big_coop, + sources=( + IncubatorSource(AnimalProduct.golden_egg_starter), + )), + Animal(AnimalName.dinosaur, + required_building=Building.big_coop, + sources=( + # We should use the starter item here, but since the dinosaur egg is also an artifact, it's part of the museum rules + # and I do not want to touch it yet. + # IncubatorSource(AnimalProduct.dinosaur_egg_starter), + IncubatorSource(AnimalProduct.dinosaur_egg), + )), + ) ) diff --git a/worlds/stardew_valley/data/animal.py b/worlds/stardew_valley/data/animal.py new file mode 100644 index 0000000000..8121b6f681 --- /dev/null +++ b/worlds/stardew_valley/data/animal.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass, field + +from .game_item import Source +from ..strings.animal_names import Animal as AnimalName + +assert AnimalName + + +@dataclass(frozen=True) +class Animal: + name: str + required_building: str = field(kw_only=True) + sources: tuple[Source, ...] = field(kw_only=True) + + +@dataclass(frozen=True) +class IncubatorSource(Source): + egg_item: str + + +@dataclass(frozen=True) +class OstrichIncubatorSource(Source): + egg_item: str diff --git a/worlds/stardew_valley/data/bundle_data.py b/worlds/stardew_valley/data/bundle_data.py index 75f0f75a23..3a5523ecdd 100644 --- a/worlds/stardew_valley/data/bundle_data.py +++ b/worlds/stardew_valley/data/bundle_data.py @@ -143,7 +143,7 @@ duck_egg = BundleItem(AnimalProduct.duck_egg) rabbit_foot = BundleItem(AnimalProduct.rabbit_foot) dinosaur_egg = BundleItem(AnimalProduct.dinosaur_egg) void_egg = BundleItem(AnimalProduct.void_egg) -ostrich_egg = BundleItem(AnimalProduct.ostrich_egg, source=BundleItem.Sources.island, ) +ostrich_egg = BundleItem(AnimalProduct.ostrich_egg, source=BundleItem.Sources.content) golden_egg = BundleItem(AnimalProduct.golden_egg) truffle_oil = BundleItem(ArtisanGood.truffle_oil) @@ -832,7 +832,7 @@ calico_items = [calico_egg.as_amount(200), calico_egg.as_amount(200), calico_egg magic_rock_candy, mega_bomb.as_amount(10), mystery_box.as_amount(10), mixed_seeds.as_amount(50), strawberry_seeds.as_amount(20), spicy_eel.as_amount(5), crab_cakes.as_amount(5), eggplant_parmesan.as_amount(5), - pumpkin_soup.as_amount(5), lucky_lunch.as_amount(5)] + pumpkin_soup.as_amount(5), lucky_lunch.as_amount(5) ] calico_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.calico, calico_items, 2, 2) raccoon_bundle = BundleTemplate(CCRoom.bulletin_board, BundleName.raccoon, raccoon_foraging_items, 4, 4) diff --git a/worlds/stardew_valley/data/museum_data.py b/worlds/stardew_valley/data/museum_data.py index b81c518a37..0607261ec6 100644 --- a/worlds/stardew_valley/data/museum_data.py +++ b/worlds/stardew_valley/data/museum_data.py @@ -3,12 +3,13 @@ from __future__ import annotations from dataclasses import dataclass from typing import List, Tuple, Union, Optional -from ..strings.monster_names import Monster +from ..strings.animal_product_names import AnimalProduct from ..strings.fish_names import WaterChest from ..strings.forageable_names import Forageable -from ..strings.metal_names import Mineral, Artifact, Fossil -from ..strings.region_names import Region from ..strings.geode_names import Geode +from ..strings.metal_names import Mineral, Artifact, Fossil +from ..strings.monster_names import Monster +from ..strings.region_names import Region @dataclass(frozen=True) @@ -105,7 +106,7 @@ class Artifact: geodes=(Geode.artifact_trove, WaterChest.fishing_chest)) ornamental_fan = create_artifact("Ornamental Fan", 7.4, (Region.beach, Region.forest, Region.town), geodes=(Geode.artifact_trove, WaterChest.fishing_chest)) - dinosaur_egg = create_artifact("Dinosaur Egg", 11.4, (Region.skull_cavern), + dinosaur_egg = create_artifact(AnimalProduct.dinosaur_egg, 11.4, (Region.skull_cavern), monsters=Monster.pepper_rex) rare_disc = create_artifact("Rare Disc", 5.6, Region.stardew_valley, geodes=(Geode.artifact_trove, WaterChest.fishing_chest), diff --git a/worlds/stardew_valley/logic/animal_logic.py b/worlds/stardew_valley/logic/animal_logic.py index eb1ebeeec5..071133d5ce 100644 --- a/worlds/stardew_valley/logic/animal_logic.py +++ b/worlds/stardew_valley/logic/animal_logic.py @@ -1,25 +1,15 @@ -from typing import Union +import typing from .base_logic import BaseLogicMixin, BaseLogic -from .building_logic import BuildingLogicMixin -from .has_logic import HasLogicMixin -from .money_logic import MoneyLogicMixin -from ..stardew_rule import StardewRule, true_ -from ..strings.animal_names import Animal, coop_animals, barn_animals +from ..stardew_rule import StardewRule from ..strings.building_names import Building from ..strings.forageable_names import Forageable -from ..strings.generic_names import Generic -from ..strings.region_names import Region +from ..strings.machine_names import Machine -cost_and_building_by_animal = { - Animal.chicken: (800, Building.coop), - Animal.cow: (1500, Building.barn), - Animal.goat: (4000, Building.big_barn), - Animal.duck: (1200, Building.big_coop), - Animal.sheep: (8000, Building.deluxe_barn), - Animal.rabbit: (8000, Building.deluxe_coop), - Animal.pig: (16000, Building.deluxe_barn) -} +if typing.TYPE_CHECKING: + from .logic import StardewLogic +else: + StardewLogic = object class AnimalLogicMixin(BaseLogicMixin): @@ -28,32 +18,19 @@ class AnimalLogicMixin(BaseLogicMixin): self.animal = AnimalLogic(*args, **kwargs) -class AnimalLogic(BaseLogic[Union[HasLogicMixin, MoneyLogicMixin, BuildingLogicMixin]]): +class AnimalLogic(BaseLogic[StardewLogic]): - def can_buy_animal(self, animal: str) -> StardewRule: - try: - price, building = cost_and_building_by_animal[animal] - except KeyError: - return true_ - return self.logic.money.can_spend_at(Region.ranch, price) & self.logic.building.has_building(building) + def can_incubate(self, egg_item: str) -> StardewRule: + return self.logic.building.has_building(Building.coop) & self.logic.has(egg_item) - def has_animal(self, animal: str) -> StardewRule: - if animal == Generic.any: - return self.has_any_animal() - elif animal == Building.coop: - return self.has_any_coop_animal() - elif animal == Building.barn: - return self.has_any_barn_animal() - return self.logic.has(animal) + def can_ostrich_incubate(self, egg_item: str) -> StardewRule: + return self.logic.building.has_building(Building.barn) & self.logic.has(Machine.ostrich_incubator) & self.logic.has(egg_item) - def has_happy_animal(self, animal: str) -> StardewRule: - return self.has_animal(animal) & self.logic.has(Forageable.hay) + def has_animal(self, animal_name: str) -> StardewRule: + animal = self.content.animals.get(animal_name) + assert animal is not None, f"Animal {animal_name} not found." - def has_any_animal(self) -> StardewRule: - return self.has_any_coop_animal() | self.has_any_barn_animal() + return self.logic.source.has_access_to_any(animal.sources) & self.logic.building.has_building(animal.required_building) - def has_any_coop_animal(self) -> StardewRule: - return self.logic.has_any(*coop_animals) - - def has_any_barn_animal(self) -> StardewRule: - return self.logic.has_any(*barn_animals) + def has_happy_animal(self, animal_name: str) -> StardewRule: + return self.logic.animal.has_animal(animal_name) & self.logic.has(Forageable.hay) diff --git a/worlds/stardew_valley/logic/festival_logic.py b/worlds/stardew_valley/logic/festival_logic.py index 2b22617202..939e904951 100644 --- a/worlds/stardew_valley/logic/festival_logic.py +++ b/worlds/stardew_valley/logic/festival_logic.py @@ -17,6 +17,7 @@ from .skill_logic import SkillLogicMixin from .time_logic import TimeLogicMixin from ..options import FestivalLocations from ..stardew_rule import StardewRule +from ..strings.animal_product_names import AnimalProduct from ..strings.book_names import Book from ..strings.craftable_names import Fishing from ..strings.crop_names import Fruit, Vegetable @@ -154,18 +155,37 @@ SkillLogicMixin, RegionLogicMixin, ActionLogicMixin, MonsterLogicMixin, Relation if self.options.festival_locations != FestivalLocations.option_hard: return self.logic.true_ - animal_rule = self.logic.animal.has_animal(Generic.any) + # Other animal products are not counted in the animal product category + good_animal_products = [ + AnimalProduct.duck_egg, AnimalProduct.duck_feather, AnimalProduct.egg, AnimalProduct.goat_milk, AnimalProduct.golden_egg, AnimalProduct.large_egg, + AnimalProduct.large_goat_milk, AnimalProduct.large_milk, AnimalProduct.milk, AnimalProduct.ostrich_egg, AnimalProduct.rabbit_foot, + AnimalProduct.void_egg, AnimalProduct.wool + ] + if AnimalProduct.ostrich_egg not in self.content.game_items: + # When ginger island is excluded, ostrich egg is not available + good_animal_products.remove(AnimalProduct.ostrich_egg) + animal_rule = self.logic.has_any(*good_animal_products) + artisan_rule = self.logic.artisan.can_keg(Generic.any) | self.logic.artisan.can_preserves_jar(Generic.any) - cooking_rule = self.logic.money.can_spend_at(Region.saloon, 220) # Salads at the bar are good enough + + # Salads at the bar are good enough + cooking_rule = self.logic.money.can_spend_at(Region.saloon, 220) + fish_rule = self.logic.skill.can_fish(difficulty=50) - forage_rule = self.logic.region.can_reach_any((Region.forest, Region.backwoods)) # Hazelnut always available since the grange display is in fall - mineral_rule = self.logic.action.can_open_geode(Generic.any) # More than half the minerals are good enough + + # Hazelnut always available since the grange display is in fall + forage_rule = self.logic.region.can_reach_any((Region.forest, Region.backwoods)) + + # More than half the minerals are good enough + mineral_rule = self.logic.action.can_open_geode(Generic.any) + good_fruits = (fruit for fruit in (Fruit.apple, Fruit.banana, Forageable.coconut, Forageable.crystal_fruit, Fruit.mango, Fruit.orange, Fruit.peach, Fruit.pomegranate, Fruit.strawberry, Fruit.melon, Fruit.rhubarb, Fruit.pineapple, Fruit.ancient_fruit, Fruit.starfruit) if fruit in self.content.game_items) fruit_rule = self.logic.has_any(*good_fruits) + good_vegetables = (vegeteable for vegeteable in (Vegetable.amaranth, Vegetable.artichoke, Vegetable.beet, Vegetable.cauliflower, Forageable.fiddlehead_fern, Vegetable.kale, @@ -173,8 +193,7 @@ SkillLogicMixin, RegionLogicMixin, ActionLogicMixin, MonsterLogicMixin, Relation if vegeteable in self.content.game_items) vegetable_rule = self.logic.has_any(*good_vegetables) - return animal_rule & artisan_rule & cooking_rule & fish_rule & \ - forage_rule & fruit_rule & mineral_rule & vegetable_rule + return animal_rule & artisan_rule & cooking_rule & fish_rule & forage_rule & fruit_rule & mineral_rule & vegetable_rule def can_win_fishing_competition(self) -> StardewRule: return self.logic.skill.can_fish(difficulty=60) diff --git a/worlds/stardew_valley/logic/logic.py b/worlds/stardew_valley/logic/logic.py index aa4cd075d3..3848e393d2 100644 --- a/worlds/stardew_valley/logic/logic.py +++ b/worlds/stardew_valley/logic/logic.py @@ -149,42 +149,37 @@ class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, Travelin # self.received("Deluxe Fertilizer Recipe") & self.has(MetalBar.iridium) & self.has(SVItem.sap), # | (self.ability.can_cook() & self.relationship.has_hearts(NPC.emily, 3) & self.has(Forageable.leek) & self.has(Forageable.dandelion) & # | (self.ability.can_cook() & self.relationship.has_hearts(NPC.jodi, 7) & self.has(AnimalProduct.cow_milk) & self.has(Ingredient.sugar)), - Animal.chicken: self.animal.can_buy_animal(Animal.chicken), - Animal.cow: self.animal.can_buy_animal(Animal.cow), - Animal.dinosaur: self.building.has_building(Building.big_coop) & self.has(AnimalProduct.dinosaur_egg), - Animal.duck: self.animal.can_buy_animal(Animal.duck), - Animal.goat: self.animal.can_buy_animal(Animal.goat), - Animal.ostrich: self.building.has_building(Building.barn) & self.has(AnimalProduct.ostrich_egg) & self.has(Machine.ostrich_incubator), - Animal.pig: self.animal.can_buy_animal(Animal.pig), - Animal.rabbit: self.animal.can_buy_animal(Animal.rabbit), - Animal.sheep: self.animal.can_buy_animal(Animal.sheep), AnimalProduct.any_egg: self.has_any(AnimalProduct.chicken_egg, AnimalProduct.duck_egg), AnimalProduct.brown_egg: self.animal.has_animal(Animal.chicken), AnimalProduct.chicken_egg: self.has_any(AnimalProduct.egg, AnimalProduct.brown_egg, AnimalProduct.large_egg, AnimalProduct.large_brown_egg), AnimalProduct.cow_milk: self.has_any(AnimalProduct.milk, AnimalProduct.large_milk), - AnimalProduct.duck_egg: self.animal.has_animal(Animal.duck), + AnimalProduct.duck_egg: self.animal.has_animal(Animal.duck), # Should also check starter AnimalProduct.duck_feather: self.animal.has_happy_animal(Animal.duck), - AnimalProduct.egg: self.animal.has_animal(Animal.chicken), - AnimalProduct.goat_milk: self.has(Animal.goat), - AnimalProduct.golden_egg: self.received(AnimalProduct.golden_egg) & (self.money.can_spend_at(Region.ranch, 100000) | self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 100)), + AnimalProduct.egg: self.animal.has_animal(Animal.chicken), # Should also check starter + AnimalProduct.goat_milk: self.animal.has_animal(Animal.goat), + AnimalProduct.golden_egg: self.has(AnimalProduct.golden_egg_starter), # Should also check golden chicken if there was an alternative to obtain it without golden egg AnimalProduct.large_brown_egg: self.animal.has_happy_animal(Animal.chicken), AnimalProduct.large_egg: self.animal.has_happy_animal(Animal.chicken), AnimalProduct.large_goat_milk: self.animal.has_happy_animal(Animal.goat), AnimalProduct.large_milk: self.animal.has_happy_animal(Animal.cow), AnimalProduct.milk: self.animal.has_animal(Animal.cow), - AnimalProduct.ostrich_egg: self.tool.can_forage(Generic.any, Region.island_north, True) & self.has(Forageable.journal_scrap) & self.region.can_reach(Region.volcano_floor_5), AnimalProduct.rabbit_foot: self.animal.has_happy_animal(Animal.rabbit), AnimalProduct.roe: self.skill.can_fish() & self.building.has_building(Building.fish_pond), AnimalProduct.squid_ink: self.mine.can_mine_in_the_mines_floor_81_120() | (self.building.has_building(Building.fish_pond) & self.has(Fish.squid)), AnimalProduct.sturgeon_roe: self.has(Fish.sturgeon) & self.building.has_building(Building.fish_pond), AnimalProduct.truffle: self.animal.has_animal(Animal.pig) & self.season.has_any_not_winter(), - AnimalProduct.void_egg: self.money.can_spend_at(Region.sewer, 5000) | (self.building.has_building(Building.fish_pond) & self.has(Fish.void_salmon)), + AnimalProduct.void_egg: self.has(AnimalProduct.void_egg_starter), # Should also check void chicken if there was an alternative to obtain it without void egg AnimalProduct.wool: self.animal.has_animal(Animal.rabbit) | self.animal.has_animal(Animal.sheep), AnimalProduct.slime_egg_green: self.has(Machine.slime_egg_press) & self.has(Loot.slime), AnimalProduct.slime_egg_blue: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(3), AnimalProduct.slime_egg_red: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(6), AnimalProduct.slime_egg_purple: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(9), AnimalProduct.slime_egg_tiger: self.has(Fish.lionfish) & self.building.has_building(Building.fish_pond), + AnimalProduct.duck_egg_starter: self.logic.false_, # It could be purchased at the Feast of the Winter Star, but it's random every year, so not considering it yet... + AnimalProduct.dinosaur_egg_starter: self.logic.false_, # Dinosaur eggs are also part of the museum rules, and I don't want to touch them yet. + AnimalProduct.egg_starter: self.logic.false_, # It could be purchased at the Desert Festival, but festival logic is quite a mess, so not considering it yet... + AnimalProduct.golden_egg_starter: self.received(AnimalProduct.golden_egg) & (self.money.can_spend_at(Region.ranch, 100000) | self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 100)), + AnimalProduct.void_egg_starter: self.money.can_spend_at(Region.sewer, 5000) | (self.building.has_building(Building.fish_pond) & self.has(Fish.void_salmon)), ArtisanGood.aged_roe: self.artisan.can_preserves_jar(AnimalProduct.roe), ArtisanGood.battery_pack: (self.has(Machine.lightning_rod) & self.season.has_any_not_winter()) | self.has(Machine.solar_panel), ArtisanGood.caviar: self.artisan.can_preserves_jar(AnimalProduct.sturgeon_roe), diff --git a/worlds/stardew_valley/logic/logic_and_mods_design.md b/worlds/stardew_valley/logic/logic_and_mods_design.md index 87631175b3..bf6684a354 100644 --- a/worlds/stardew_valley/logic/logic_and_mods_design.md +++ b/worlds/stardew_valley/logic/logic_and_mods_design.md @@ -72,4 +72,16 @@ of source (Monster drop and fish can have foraging sources). if easy logic is disabled. For instance, anything that requires money could be accessible as soon as you can sell something to someone (even wood). Items are classified by their source. An item with a fishing or a crab pot source is considered a fish, an item dropping from a monster is a monster drop. An -item with a foraging source is a forageable. Items can fit in multiple categories. +item with a foraging source is a forageable. Items can fit in multiple categories. + +## Prefer rich class to anemic list of sources + +For game mechanic that might need more logic/interaction than a simple game item, prefer creating a class than just listing the sources and adding generic +requirements to them. This will simplify the implementation of more complex mechanics and increase cohesion. + +For instance, `Building` can be upgraded. Instead of having a simple source for the `Big Coop` being a shop source with an additional requirement being having +the previous building, the `Building` class has knowledge of the upgrade system and know from which building it can be upgraded. + +Another example is `Animal`. Instead of a shopping source with a requirement of having a `Coop`, the `Chicken` knows that a building is required. This way, a +potential source of chicken from incubating an egg would not require an additional requirement of having a coop (assuming the incubator could be obtained +without a big coop). diff --git a/worlds/stardew_valley/logic/source_logic.py b/worlds/stardew_valley/logic/source_logic.py index f1c6fe3d7b..67ce55177c 100644 --- a/worlds/stardew_valley/logic/source_logic.py +++ b/worlds/stardew_valley/logic/source_logic.py @@ -1,6 +1,7 @@ import functools from typing import Union, Any, Iterable +from .animal_logic import AnimalLogicMixin from .artisan_logic import ArtisanLogicMixin from .base_logic import BaseLogicMixin, BaseLogic from .grind_logic import GrindLogicMixin @@ -11,6 +12,7 @@ from .received_logic import ReceivedLogicMixin from .region_logic import RegionLogicMixin from .requirement_logic import RequirementLogicMixin from .tool_logic import ToolLogicMixin +from ..data.animal import IncubatorSource, OstrichIncubatorSource from ..data.artisan import MachineSource from ..data.game_item import GenericSource, Source, GameItem, CustomRuleSource from ..data.harvest import ForagingSource, FruitBatsSource, MushroomCaveSource, SeasonalForagingSource, \ @@ -25,7 +27,7 @@ class SourceLogicMixin(BaseLogicMixin): class SourceLogic(BaseLogic[Union[SourceLogicMixin, HasLogicMixin, ReceivedLogicMixin, HarvestingLogicMixin, MoneyLogicMixin, RegionLogicMixin, -ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]): +ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin, AnimalLogicMixin]]): def has_access_to_item(self, item: GameItem): rules = [] @@ -81,6 +83,14 @@ ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin]]): def _(self, source: HarvestCropSource): return self.logic.harvesting.can_harvest_crop_from(source) + @has_access_to.register + def _(self, source: IncubatorSource): + return self.logic.animal.can_incubate(source.egg_item) + + @has_access_to.register + def _(self, source: OstrichIncubatorSource): + return self.logic.animal.can_ostrich_incubate(source.egg_item) + @has_access_to.register def _(self, source: MachineSource): return self.logic.artisan.can_produce_from(source) diff --git a/worlds/stardew_valley/strings/animal_names.py b/worlds/stardew_valley/strings/animal_names.py index ecae0d7680..59f2f3c9c7 100644 --- a/worlds/stardew_valley/strings/animal_names.py +++ b/worlds/stardew_valley/strings/animal_names.py @@ -8,6 +8,5 @@ class Animal: rabbit = "Rabbit" goat = "Goat" ostrich = "Ostrich" - -coop_animals = [Animal.chicken, "Rabbit", "Duck", "Dinosaur"] -barn_animals = [Animal.cow, "Sheep", "Pig", "Ostrich"] \ No newline at end of file + void_chicken = "Void Chicken" + golden_chicken = "Golden Chicken" diff --git a/worlds/stardew_valley/strings/animal_product_names.py b/worlds/stardew_valley/strings/animal_product_names.py index f89b610ae8..1b7490a607 100644 --- a/worlds/stardew_valley/strings/animal_product_names.py +++ b/worlds/stardew_valley/strings/animal_product_names.py @@ -3,17 +3,32 @@ class AnimalProduct: brown_egg = "Egg (Brown)" chicken_egg = "Chicken Egg" cow_milk = "Cow Milk" + dinosaur_egg_starter = "Dinosaur Egg (Starter)" + """This item does not really exist and should never end up being displayed. + It's there to patch the loop in logic because of the Dinosaur-and-egg problem.""" dinosaur_egg = "Dinosaur Egg" + duck_egg_starter = "Duck Egg (Starter)" + """This item does not really exist and should never end up being displayed. + It's there to patch the loop in logic because of the Chicken-and-egg problem.""" duck_egg = "Duck Egg" duck_feather = "Duck Feather" + egg_starter = "Egg (Starter)" + """This item does not really exist and should never end up being displayed. + It's there to patch the loop in logic because of the Chicken-and-egg problem.""" egg = "Egg" goat_milk = "Goat Milk" + golden_egg_starter = "Golden Egg (Starter)" + """This item does not really exist and should never end up being displayed. + It's there to patch the loop in logic because of the Chicken-and-egg problem.""" golden_egg = "Golden Egg" large_brown_egg = "Large Egg (Brown)" large_egg = "Large Egg" large_goat_milk = "Large Goat Milk" large_milk = "Large Milk" milk = "Milk" + ostrich_egg_starter = "Ostrich Egg (Starter)" + """This item does not really exist and should never end up being displayed. + It's there to patch the loop in logic because of the Chicken-and-egg problem.""" ostrich_egg = "Ostrich Egg" rabbit_foot = "Rabbit's Foot" roe = "Roe" @@ -25,6 +40,8 @@ class AnimalProduct: squid_ink = "Squid Ink" sturgeon_roe = "Sturgeon Roe" truffle = "Truffle" + void_egg_starter = "Void Egg (Starter)" + """This item does not really exist and should never end up being displayed. + It's there to patch the loop in logic because of the Chicken-and-egg problem.""" void_egg = "Void Egg" wool = "Wool" - diff --git a/worlds/stardew_valley/strings/metal_names.py b/worlds/stardew_valley/strings/metal_names.py index 7798c06def..7efdc7ed33 100644 --- a/worlds/stardew_valley/strings/metal_names.py +++ b/worlds/stardew_valley/strings/metal_names.py @@ -142,5 +142,3 @@ class ModFossil: pterodactyl_phalange = "Pterodactyl Phalange" pterodactyl_vertebra = "Pterodactyl Vertebra" pterodactyl_claw = "Pterodactyl Claw" - - From 543dcb27d85510f1eec01c3744172d41440c4cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Sun, 20 Apr 2025 10:51:03 -0400 Subject: [PATCH 24/46] Stardew Valley: Exclude maximum one resource packs from pool when in start inventory (#4839) Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- worlds/stardew_valley/__init__.py | 6 ++--- worlds/stardew_valley/data/items.csv | 4 +-- worlds/stardew_valley/items.py | 27 +++++++++---------- .../strings/wallet_item_names.py | 1 + worlds/stardew_valley/test/TestGeneration.py | 4 +-- worlds/stardew_valley/test/TestItemLink.py | 8 +++--- worlds/stardew_valley/test/TestItems.py | 18 ++++++++++++- 7 files changed, 42 insertions(+), 26 deletions(-) diff --git a/worlds/stardew_valley/__init__.py b/worlds/stardew_valley/__init__.py index bad0ab9e68..7f420eb81d 100644 --- a/worlds/stardew_valley/__init__.py +++ b/worlds/stardew_valley/__init__.py @@ -1,7 +1,7 @@ import logging import typing from random import Random -from typing import Dict, Any, Iterable, Optional, List, TextIO, cast +from typing import Dict, Any, Iterable, Optional, List, TextIO from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState from Options import PerGameCommonOptions @@ -148,8 +148,8 @@ class StardewValleyWorld(World): self.precollect_building_items() items_to_exclude = [excluded_items for excluded_items in self.multiworld.precollected_items[self.player] - if not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK, - Group.FRIENDSHIP_PACK)] + if item_table[excluded_items.name].has_any_group(Group.MAXIMUM_ONE) + or not item_table[excluded_items.name].has_any_group(Group.RESOURCE_PACK, Group.FRIENDSHIP_PACK)] if self.options.season_randomization == SeasonRandomization.option_disabled: items_to_exclude = [item for item in items_to_exclude diff --git a/worlds/stardew_valley/data/items.csv b/worlds/stardew_valley/data/items.csv index 44e16c5d50..11a22e952d 100644 --- a/worlds/stardew_valley/data/items.csv +++ b/worlds/stardew_valley/data/items.csv @@ -748,7 +748,7 @@ id,name,classification,groups,mod_name 5208,Chest,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5209,Stone Chest,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5210,Quality Bobber,filler,RESOURCE_PACK, -5211,Mini-Obelisk,filler,"EXACTLY_TWO,RESOURCE_PACK", +5211,Mini-Obelisk,filler,"AT_LEAST_TWO,RESOURCE_PACK", 5212,Monster Musk,filler,RESOURCE_PACK, 5213,Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5214,Quality Sprinkler,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", @@ -760,7 +760,7 @@ id,name,classification,groups,mod_name 5220,Lightning Rod,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5221,Resource Pack: 5000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5222,Resource Pack: 10000 Money,useful,"BASE_RESOURCE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", -5223,Junimo Chest,filler,"EXACTLY_TWO,RESOURCE_PACK", +5223,Junimo Chest,filler,"AT_LEAST_TWO,RESOURCE_PACK", 5224,Horse Flute,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5225,Pierre's Missing Stocklist,useful,"MAXIMUM_ONE,RESOURCE_PACK,RESOURCE_PACK_USEFUL", 5226,Hopper,filler,"RESOURCE_PACK,RESOURCE_PACK_USEFUL", diff --git a/worlds/stardew_valley/items.py b/worlds/stardew_valley/items.py index b4b1175c1d..a0f901a209 100644 --- a/worlds/stardew_valley/items.py +++ b/worlds/stardew_valley/items.py @@ -69,7 +69,7 @@ class Group(enum.Enum): TRAP = enum.auto() BONUS = enum.auto() MAXIMUM_ONE = enum.auto() - EXACTLY_TWO = enum.auto() + AT_LEAST_TWO = enum.auto() DEPRECATED = enum.auto() RESOURCE_PACK_USEFUL = enum.auto() SPECIAL_ORDER_BOARD = enum.auto() @@ -181,7 +181,7 @@ def create_items(item_factory: StardewItemFactory, locations_count: int, items_t items += unique_filler_items logger.debug(f"Created {len(unique_filler_items)} unique filler items") - resource_pack_items = fill_with_resource_packs_and_traps(item_factory, options, random, items, locations_count) + resource_pack_items = fill_with_resource_packs_and_traps(item_factory, options, random, items + items_to_exclude, locations_count - len(items)) items += resource_pack_items logger.debug(f"Created {len(resource_pack_items)} resource packs") @@ -711,7 +711,7 @@ def weapons_count(options: StardewValleyOptions): def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options: StardewValleyOptions, random: Random, items_already_added: List[Item], - number_locations: int) -> List[Item]: + available_item_slots: int) -> List[Item]: include_traps = options.trap_items != TrapItems.option_no_traps items_already_added_names = [item.name for item in items_already_added] useful_resource_packs = [pack for pack in items_by_group[Group.RESOURCE_PACK_USEFUL] @@ -734,10 +734,9 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options priority_filler_items = remove_excluded_items(priority_filler_items, options) number_priority_items = len(priority_filler_items) - required_resource_pack = number_locations - len(items_already_added) - if required_resource_pack < number_priority_items: + if available_item_slots < number_priority_items: chosen_priority_items = [item_factory(resource_pack) for resource_pack in - random.sample(priority_filler_items, required_resource_pack)] + random.sample(priority_filler_items, available_item_slots)] return chosen_priority_items items = [] @@ -745,24 +744,24 @@ def fill_with_resource_packs_and_traps(item_factory: StardewItemFactory, options ItemClassification.trap if resource_pack.classification == ItemClassification.trap else ItemClassification.useful) for resource_pack in priority_filler_items] items.extend(chosen_priority_items) - required_resource_pack -= number_priority_items + available_item_slots -= number_priority_items all_filler_packs = [filler_pack for filler_pack in all_filler_packs if Group.MAXIMUM_ONE not in filler_pack.groups or (filler_pack.name not in [priority_item.name for priority_item in priority_filler_items] and filler_pack.name not in items_already_added_names)] - while required_resource_pack > 0: + while available_item_slots > 0: resource_pack = random.choice(all_filler_packs) - exactly_2 = Group.EXACTLY_TWO in resource_pack.groups - while exactly_2 and required_resource_pack == 1: + exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups + while exactly_2 and available_item_slots == 1: resource_pack = random.choice(all_filler_packs) - exactly_2 = Group.EXACTLY_TWO in resource_pack.groups + exactly_2 = Group.AT_LEAST_TWO in resource_pack.groups classification = ItemClassification.useful if resource_pack.classification == ItemClassification.progression else resource_pack.classification items.append(item_factory(resource_pack, classification)) - required_resource_pack -= 1 + available_item_slots -= 1 if exactly_2: items.append(item_factory(resource_pack, classification)) - required_resource_pack -= 1 + available_item_slots -= 1 if exactly_2 or Group.MAXIMUM_ONE in resource_pack.groups: all_filler_packs.remove(resource_pack) @@ -803,7 +802,7 @@ def generate_filler_choice_pool(options: StardewValleyOptions) -> list[str]: def remove_limited_amount_packs(packs): - return [pack for pack in packs if Group.MAXIMUM_ONE not in pack.groups and Group.EXACTLY_TWO not in pack.groups] + return [pack for pack in packs if Group.MAXIMUM_ONE not in pack.groups and Group.AT_LEAST_TWO not in pack.groups] def get_all_filler_items(include_traps: bool, exclude_ginger_island: bool) -> List[ItemData]: diff --git a/worlds/stardew_valley/strings/wallet_item_names.py b/worlds/stardew_valley/strings/wallet_item_names.py index 32655efe88..743d1f0c01 100644 --- a/worlds/stardew_valley/strings/wallet_item_names.py +++ b/worlds/stardew_valley/strings/wallet_item_names.py @@ -9,3 +9,4 @@ class Wallet: dark_talisman = "Dark Talisman" club_card = "Club Card" mastery_of_the_five_ways = "Mastery Of The Five Ways" + key_to_the_town = "Key To The Town" diff --git a/worlds/stardew_valley/test/TestGeneration.py b/worlds/stardew_valley/test/TestGeneration.py index 35cd2007eb..77092c78fc 100644 --- a/worlds/stardew_valley/test/TestGeneration.py +++ b/worlds/stardew_valley/test/TestGeneration.py @@ -66,7 +66,7 @@ class TestBaseItemGeneration(SVTestBase): def test_does_not_create_or_create_two_of_exactly_two_items(self): all_created_items = self.get_all_created_items() - for exactly_two_item in items.items_by_group[items.Group.EXACTLY_TWO]: + for exactly_two_item in items.items_by_group[items.Group.AT_LEAST_TWO]: with self.subTest(f"{exactly_two_item.name}"): count = all_created_items.count(exactly_two_item.name) self.assertTrue(count == 0 or count == 2) @@ -114,7 +114,7 @@ class TestNoGingerIslandItemGeneration(SVTestBase): def test_does_not_create_exactly_two_items(self): all_created_items = self.get_all_created_items() - for exactly_two_item in items.items_by_group[items.Group.EXACTLY_TWO]: + for exactly_two_item in items.items_by_group[items.Group.AT_LEAST_TWO]: with self.subTest(f"{exactly_two_item.name}"): count = all_created_items.count(exactly_two_item.name) self.assertTrue(count == 0 or count == 2) diff --git a/worlds/stardew_valley/test/TestItemLink.py b/worlds/stardew_valley/test/TestItemLink.py index 39bf553cab..3a0d976511 100644 --- a/worlds/stardew_valley/test/TestItemLink.py +++ b/worlds/stardew_valley/test/TestItemLink.py @@ -19,7 +19,7 @@ class TestItemLinksEverythingIncluded(SVTestBase): continue filler_generated.append(filler) self.assertNotIn(Group.MAXIMUM_ONE, item_table[filler].groups) - self.assertNotIn(Group.EXACTLY_TWO, item_table[filler].groups) + self.assertNotIn(Group.AT_LEAST_TWO, item_table[filler].groups) if Group.TRAP in item_table[filler].groups: at_least_one_trap = True if Group.GINGER_ISLAND in item_table[filler].groups: @@ -46,7 +46,7 @@ class TestItemLinksNoIsland(SVTestBase): filler_generated.append(filler) self.assertNotIn(Group.GINGER_ISLAND, item_table[filler].groups) self.assertNotIn(Group.MAXIMUM_ONE, item_table[filler].groups) - self.assertNotIn(Group.EXACTLY_TWO, item_table[filler].groups) + self.assertNotIn(Group.AT_LEAST_TWO, item_table[filler].groups) if Group.TRAP in item_table[filler].groups: at_least_one_trap = True if len(filler_generated) >= max_number_filler: @@ -70,7 +70,7 @@ class TestItemLinksNoTraps(SVTestBase): filler_generated.append(filler) self.assertNotIn(Group.TRAP, item_table[filler].groups) self.assertNotIn(Group.MAXIMUM_ONE, item_table[filler].groups) - self.assertNotIn(Group.EXACTLY_TWO, item_table[filler].groups) + self.assertNotIn(Group.AT_LEAST_TWO, item_table[filler].groups) if Group.GINGER_ISLAND in item_table[filler].groups: at_least_one_island = True if len(filler_generated) >= max_number_filler: @@ -94,7 +94,7 @@ class TestItemLinksNoTrapsAndIsland(SVTestBase): self.assertNotIn(Group.GINGER_ISLAND, item_table[filler].groups) self.assertNotIn(Group.TRAP, item_table[filler].groups) self.assertNotIn(Group.MAXIMUM_ONE, item_table[filler].groups) - self.assertNotIn(Group.EXACTLY_TWO, item_table[filler].groups) + self.assertNotIn(Group.AT_LEAST_TWO, item_table[filler].groups) if len(filler_generated) >= max_number_filler: break self.assertGreaterEqual(len(filler_generated), max_number_filler) diff --git a/worlds/stardew_valley/test/TestItems.py b/worlds/stardew_valley/test/TestItems.py index 9cff146597..1d6f968955 100644 --- a/worlds/stardew_valley/test/TestItems.py +++ b/worlds/stardew_valley/test/TestItems.py @@ -1,4 +1,4 @@ -from BaseClasses import MultiWorld, get_seed +from BaseClasses import MultiWorld, get_seed, ItemClassification from . import setup_solo_multiworld, SVTestCase, solo_multiworld from .options.presets import allsanity_no_mods_6_x_x, get_minsanity_options from .. import StardewValleyWorld @@ -72,6 +72,22 @@ class TestItems(SVTestCase): self.assertEqual(len(starting_seasons_rolled), 4) +class TestStartInventoryFillersAreProperlyExcluded(SVTestCase): + def test_given_maximum_one_resource_pack_in_start_inventory_when_create_items_then_item_is_properly_excluded(self): + assert item_table[Wallet.key_to_the_town].classification == ItemClassification.useful \ + and {Group.MAXIMUM_ONE, Group.RESOURCE_PACK_USEFUL}.issubset(item_table[Wallet.key_to_the_town].groups), \ + "'Key to the Town' is no longer suitable to test this usecase." + + options = { + "start_inventory": { + Wallet.key_to_the_town: 1, + } + } + + with solo_multiworld(options, world_caching=False) as (multiworld, world): + self.assertNotIn(world.create_item(Wallet.key_to_the_town), multiworld.get_items()) + + class TestMetalDetectors(SVTestCase): def test_minsanity_1_metal_detector(self): options = get_minsanity_options() From b59162737d7c6b379bf0963ec664208cdc153dfe Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 20 Apr 2025 23:04:40 +0200 Subject: [PATCH 25/46] LttP: increase gen rate of pedestal goal with limited rupee pool (#4905) * LttP: increase gen rate of pedestal goal with limited rupee pool * improve chance further if retro bow is involved --- worlds/alttp/ItemPool.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 2b99ef8a73..0bcc189f9c 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -707,13 +707,20 @@ def get_pool_core(world, player: int): else: break - if goal == 'pedestal': - place_item('Master Sword Pedestal', 'Triforce') - pool.remove("Rupees (20)") - if retro_bow: replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (70)'} pool = ['Rupees (5)' if item in replace else item for item in pool] + + if goal == 'pedestal': + place_item('Master Sword Pedestal', 'Triforce') + for rupee_name in ("Rupees (5)", "Rupees (20)", "Rupees (50)", "Rupees (100)", "Rupees (300)"): + try: + pool.remove(rupee_name) + except ValueError: + pass + else: + break + if world.worlds[player].options.small_key_shuffle == small_key_shuffle.option_universal: pool.extend(diff.universal_keys) if mode == 'standard': From b62c1364a9c6c211a25fdfd0fe6ec01c55fda225 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:43:05 +0200 Subject: [PATCH 26/46] MultiServer.py: Another Hint Priority + Item Links bug oh boy (#4874) Basically, hints for itemlink worlds' locations get stored in ctx.hints under 1. the location's player 2. **every individual player** that is participating in the itemlink. Right now, the updatehint code tries to replace and resend the hint under the itemlinked player, which doesn't work. --- MultiServer.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/MultiServer.py b/MultiServer.py index c9e0ad8bfa..4295f28c58 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -1983,11 +1983,13 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict): new_hint = new_hint.re_prioritize(ctx, status) if hint == new_hint: return - ctx.replace_hint(client.team, hint.finding_player, hint, new_hint) - ctx.replace_hint(client.team, hint.receiving_player, hint, new_hint) + + concerning_slots = ctx.slot_set(hint.receiving_player) | {hint.finding_player} + for slot in concerning_slots: + ctx.replace_hint(client.team, slot, hint, new_hint) ctx.save() - ctx.on_changed_hints(client.team, hint.finding_player) - ctx.on_changed_hints(client.team, hint.receiving_player) + for slot in concerning_slots: + ctx.on_changed_hints(client.team, slot) elif cmd == 'StatusUpdate': update_client_status(ctx, client, args["status"]) From 1a6de25ab6b83fe28ec3031a5bd4815f72182da1 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Mon, 21 Apr 2025 00:43:31 +0200 Subject: [PATCH 27/46] Core, all worlds: Hard-deprecate old options API (by August 10th 2024) (#3284) * Core: deprecate old options API * also deprecate assigning options via option_definitions --------- Co-authored-by: alwaysintreble --- BaseClasses.py | 2 +- worlds/AutoWorld.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index ec3fa9cef1..bf115e3f9c 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -223,7 +223,7 @@ class MultiWorld(): AutoWorld.AutoWorldRegister.world_types[self.game[player]].options_dataclass.type_hints} for option_key in all_keys: option = Utils.DeprecateDict(f"Getting options from multiworld is now deprecated. " - f"Please use `self.options.{option_key}` instead.") + f"Please use `self.options.{option_key}` instead.", True) option.update(getattr(args, option_key, {})) setattr(self, option_key, option) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index d1f4a772ee..b4ff24190f 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -12,6 +12,7 @@ from typing import (Any, Callable, ClassVar, Dict, FrozenSet, Iterable, List, Ma from Options import item_and_loc_options, ItemsAccessibility, OptionGroup, PerGameCommonOptions from BaseClasses import CollectionState +from Utils import deprecate if TYPE_CHECKING: from BaseClasses import MultiWorld, Item, Location, Tutorial, Region, Entrance @@ -75,9 +76,8 @@ class AutoWorldRegister(type): # TODO - remove this once all worlds use options dataclasses if "options_dataclass" not in dct and "option_definitions" in dct: # TODO - switch to deprecate after a version - if __debug__: - logging.warning(f"{name} Assigned options through option_definitions which is now deprecated. " - "Please use options_dataclass instead.") + deprecate(f"{name} Assigned options through option_definitions which is now deprecated. " + "Please use options_dataclass instead.") dct["options_dataclass"] = make_dataclass(f"{name}Options", dct["option_definitions"].items(), bases=(PerGameCommonOptions,)) From 6613c296523185ffd4ea9c37c4c6ffe21e797935 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Mon, 21 Apr 2025 00:53:40 +0200 Subject: [PATCH 28/46] Core: print both world source paths in case of conflict (#4751) --- worlds/AutoWorld.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index b4ff24190f..67455a1a21 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -83,11 +83,13 @@ class AutoWorldRegister(type): # construct class new_class = super().__new__(mcs, name, bases, dct) + new_class.__file__ = sys.modules[new_class.__module__].__file__ if "game" in dct: if dct["game"] in AutoWorldRegister.world_types: - raise RuntimeError(f"""Game {dct["game"]} already registered.""") + raise RuntimeError(f"""Game {dct["game"]} already registered in + {AutoWorldRegister.world_types[dct["game"]].__file__} when attempting to register from + {new_class.__file__}.""") AutoWorldRegister.world_types[dct["game"]] = new_class - new_class.__file__ = sys.modules[new_class.__module__].__file__ if ".apworld" in new_class.__file__: new_class.zip_path = pathlib.Path(new_class.__file__).parents[1] if "settings_key" not in dct: From d5d56ede8bfa71b7ec4818b2467fbe16354c82d7 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Mon, 21 Apr 2025 15:20:22 -0400 Subject: [PATCH 29/46] TUNIC: Remove Outdated Plando Code (#4908) --- worlds/tunic/__init__.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index 86a91a336b..29791b4f43 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -678,18 +678,6 @@ class TunicWorld(World): for _ in range(self.options.start_inventory_from_pool[start_item]): slot_data[start_item].extend(["Your Pocket", self.player]) - for plando_item in self.multiworld.plando_items[self.player]: - if plando_item["from_pool"]: - items_to_find = set() - for item_type in [key for key in ["item", "items"] if key in plando_item]: - for item in plando_item[item_type]: - items_to_find.add(item) - for item in items_to_find: - if item in slot_data_item_names: - slot_data[item] = [] - for item_location in self.multiworld.find_item_locations(item, self.player): - slot_data[item].extend(self.get_real_location(item_location)) - return slot_data # for the universal tracker, doesn't get called in standard gen From d309de25570e5132388aee1fecc569f77dce4975 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Mon, 21 Apr 2025 16:06:24 -0400 Subject: [PATCH 30/46] Lingo: Rework Early Good Items (#4910) --- worlds/lingo/__init__.py | 37 ++++++++++++++++++++++++---------- worlds/lingo/player_logic.py | 39 ++++++++---------------------------- 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py index 05509a394b..c27aaed73a 100644 --- a/worlds/lingo/__init__.py +++ b/worlds/lingo/__init__.py @@ -3,7 +3,7 @@ Archipelago init file for Lingo """ from logging import warning -from BaseClasses import CollectionState, Item, ItemClassification, Tutorial +from BaseClasses import CollectionState, Item, ItemClassification, Tutorial, Location, LocationProgressType from Options import OptionError from worlds.AutoWorld import WebWorld, World from .datatypes import Room, RoomEntrance @@ -80,10 +80,6 @@ class LingoWorld(World): for item in self.player_logic.real_items: state.collect(self.create_item(item), True) - # Exception to the above: a forced good item is not considered a "real item", but needs to be here anyway. - if self.player_logic.forced_good_item != "": - state.collect(self.create_item(self.player_logic.forced_good_item), True) - all_locations = self.multiworld.get_locations(self.player) state.sweep_for_advancements(locations=all_locations) @@ -105,11 +101,6 @@ class LingoWorld(World): def create_items(self): pool = [self.create_item(name) for name in self.player_logic.real_items] - if self.player_logic.forced_good_item != "": - new_item = self.create_item(self.player_logic.forced_good_item) - location_obj = self.multiworld.get_location("Second Room - Good Luck", self.player) - location_obj.place_locked_item(new_item) - item_difference = len(self.player_logic.real_locations) - len(pool) if item_difference: trap_percentage = self.options.trap_percentage @@ -138,7 +129,7 @@ class LingoWorld(World): trap_counts = {name: int(weight * traps / total_weight) for name, weight in self.options.trap_weights.items()} - + trap_difference = traps - sum(trap_counts.values()) if trap_difference > 0: allowed_traps = [name for name in TRAP_ITEMS if self.options.trap_weights[name] > 0] @@ -169,6 +160,30 @@ class LingoWorld(World): def set_rules(self): self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) + def place_good_item(self, progitempool: list[Item], fill_locations: list[Location]): + if len(self.player_logic.good_item_options) == 0: + return + + good_location = self.get_location("Second Room - Good Luck") + if good_location.progress_type == LocationProgressType.EXCLUDED or good_location not in fill_locations: + return + + good_items = list(filter(lambda progitem: progitem.player == self.player and + progitem.name in self.player_logic.good_item_options, progitempool)) + + if len(good_items) == 0: + return + + good_item = self.random.choice(good_items) + good_location.place_locked_item(good_item) + + progitempool.remove(good_item) + fill_locations.remove(good_location) + + def fill_hook(self, progitempool: list[Item], usefulitempool: list[Item], filleritempool: list[Item], + fill_locations: list[Location]): + self.place_good_item(progitempool, fill_locations) + def fill_slot_data(self): slot_options = [ "death_link", "victory_condition", "shuffle_colors", "shuffle_doors", "shuffle_paintings", "shuffle_panels", diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py index 83217d7311..9363dfedb6 100644 --- a/worlds/lingo/player_logic.py +++ b/worlds/lingo/player_logic.py @@ -95,7 +95,7 @@ class LingoPlayerLogic: painting_mapping: Dict[str, str] - forced_good_item: str + good_item_options: List[str] panel_reqs: Dict[str, Dict[str, AccessRequirements]] door_reqs: Dict[str, Dict[str, AccessRequirements]] @@ -151,7 +151,7 @@ class LingoPlayerLogic: self.mastery_location = "" self.level_2_location = "" self.painting_mapping = {} - self.forced_good_item = "" + self.good_item_options = [] self.panel_reqs = {} self.door_reqs = {} self.mastery_reqs = [] @@ -344,23 +344,23 @@ class LingoPlayerLogic: # Starting Room - Back Right Door gives access to OPEN and DEAD END. # Starting Room - Exit Door gives access to OPEN and TRACE. - good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"] + self.good_item_options = ["Starting Room - Back Right Door", "Second Room - Exit Door"] if not color_shuffle: if not world.options.enable_pilgrimage: # HOT CRUST and THIS. - good_item_options.append("Pilgrim Room - Sun Painting") + self.good_item_options.append("Pilgrim Room - Sun Painting") if world.options.group_doors: # WELCOME BACK, CLOCKWISE, and DRAWL + RUNS. - good_item_options.append("Welcome Back Doors") + self.good_item_options.append("Welcome Back Doors") else: # WELCOME BACK and CLOCKWISE. - good_item_options.append("Welcome Back Area - Shortcut to Starting Room") + self.good_item_options.append("Welcome Back Area - Shortcut to Starting Room") if world.options.group_doors: # Color hallways access (NOTE: reconsider when sunwarp shuffling exists). - good_item_options.append("Rhyme Room Doors") + self.good_item_options.append("Rhyme Room Doors") # When painting shuffle is off, most Starting Room paintings give color hallways access. The Wondrous's # painting does not, but it gives access to SHRINK and WELCOME BACK. @@ -376,30 +376,7 @@ class LingoPlayerLogic: continue pdoor = DOORS_BY_ROOM[painting_obj.required_door.room][painting_obj.required_door.door] - good_item_options.append(pdoor.item_name) - - # Copied from The Witness -- remove any plandoed items from the possible good items set. - for v in world.multiworld.plando_items[world.player]: - if v.get("from_pool", True): - for item_key in {"item", "items"}: - if item_key in v: - if type(v[item_key]) is str: - if v[item_key] in good_item_options: - good_item_options.remove(v[item_key]) - elif type(v[item_key]) is dict: - for item, weight in v[item_key].items(): - if weight and item in good_item_options: - good_item_options.remove(item) - else: - # Other type of iterable - for item in v[item_key]: - if item in good_item_options: - good_item_options.remove(item) - - if len(good_item_options) > 0: - self.forced_good_item = world.random.choice(good_item_options) - self.real_items.remove(self.forced_good_item) - self.real_locations.remove("Second Room - Good Luck") + self.good_item_options.append(pdoor.item_name) def randomize_paintings(self, world: "LingoWorld") -> bool: self.painting_mapping.clear() From 57d3c52df96a8af1980397b140e91477893712d2 Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Mon, 21 Apr 2025 17:41:20 -0400 Subject: [PATCH 31/46] TUNIC: More varied reserved locations for local_fill option (#4653) * Make reserved locations more varied * Use CollectionState(self.multiworld) instead of whatever it used to be --- worlds/tunic/__init__.py | 7 ++++--- worlds/tunic/locations.py | 17 ----------------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py index 29791b4f43..9d97e5711b 100644 --- a/worlds/tunic/__init__.py +++ b/worlds/tunic/__init__.py @@ -3,7 +3,7 @@ from logging import warning from BaseClasses import Region, Location, Item, Tutorial, ItemClassification, MultiWorld, CollectionState from .items import (item_name_to_id, item_table, item_name_groups, fool_tiers, filler_items, slot_data_item_names, combat_items) -from .locations import location_table, location_name_groups, standard_location_name_to_id, hexagon_locations, sphere_one +from .locations import location_table, location_name_groups, standard_location_name_to_id, hexagon_locations from .rules import set_location_rules, set_region_rules, randomize_ability_unlocks, gold_hexagon from .er_rules import set_er_location_rules from .regions import tunic_regions @@ -451,9 +451,10 @@ class TunicWorld(World): def pre_fill(self) -> None: if self.options.local_fill > 0 and self.multiworld.players > 1: # we need to reserve a couple locations so that we don't fill up every sphere 1 location - reserved_locations: Set[str] = set(self.random.sample(sphere_one, 2)) + sphere_one_locs = self.multiworld.get_reachable_locations(CollectionState(self.multiworld), self.player) + reserved_locations: Set[Location] = set(self.random.sample(sphere_one_locs, 2)) viable_locations = [loc for loc in self.multiworld.get_unfilled_locations(self.player) - if loc.name not in reserved_locations + if loc not in reserved_locations and loc.name not in self.options.priority_locations.value] if len(viable_locations) < self.amount_to_local_fill: diff --git a/worlds/tunic/locations.py b/worlds/tunic/locations.py index 18c0fb3c13..ced3d2233b 100644 --- a/worlds/tunic/locations.py +++ b/worlds/tunic/locations.py @@ -322,23 +322,6 @@ hexagon_locations: Dict[str, str] = { "Blue Questagon": "Rooted Ziggurat Lower - Hexagon Blue", } -sphere_one: List[str] = [ - "Overworld - [Central] Chest Across From Well", - "Overworld - [Northwest] Chest Near Quarry Gate", - "Overworld - [Northwest] Shadowy Corner Chest", - "Overworld - [Southwest] Chest Guarded By Turret", - "Overworld - [Southwest] South Chest Near Guard", - "Overworld - [Southwest] Obscured in Tunnel to Beach", - "Overworld - [Northwest] Chest Near Turret", - "Overworld - [Northwest] Page By Well", - "Overworld - [West] Chest Behind Moss Wall", - "Overworld - [Southwest] Key Pickup", - "Overworld - [West] Key Pickup", - "Overworld - [West] Obscured Behind Windmill", - "Overworld - [West] Obscured Near Well", - "Overworld - [West] Page On Teleporter" -] - standard_location_name_to_id: Dict[str, int] = {name: location_base_id + index for index, name in enumerate(location_table)} all_locations = location_table.copy() From bad6a4b211f99d9f439a9971b78e1d06e9f05fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:31:08 -0400 Subject: [PATCH 32/46] Stardew Valley: remove BaseLogic generic so importing mixins is no longer needed (#4916) * remove BaseLogic generic so importing mixins is no longer needed * self review --- worlds/stardew_valley/logic/ability_logic.py | 17 +------------- worlds/stardew_valley/logic/action_logic.py | 8 +------ worlds/stardew_valley/logic/animal_logic.py | 7 +----- worlds/stardew_valley/logic/arcade_logic.py | 6 +---- worlds/stardew_valley/logic/artisan_logic.py | 6 +---- worlds/stardew_valley/logic/base_logic.py | 16 +++++++------ worlds/stardew_valley/logic/book_logic.py | 6 +---- worlds/stardew_valley/logic/building_logic.py | 12 +--------- worlds/stardew_valley/logic/bundle_logic.py | 14 ++--------- worlds/stardew_valley/logic/combat_logic.py | 7 +----- worlds/stardew_valley/logic/cooking_logic.py | 13 +---------- worlds/stardew_valley/logic/crafting_logic.py | 13 +---------- worlds/stardew_valley/logic/farming_logic.py | 9 +++----- worlds/stardew_valley/logic/festival_logic.py | 19 +-------------- worlds/stardew_valley/logic/fishing_logic.py | 14 ++--------- worlds/stardew_valley/logic/gift_logic.py | 3 +-- worlds/stardew_valley/logic/goal_logic.py | 9 +------- worlds/stardew_valley/logic/grind_logic.py | 2 +- .../stardew_valley/logic/harvesting_logic.py | 11 +-------- worlds/stardew_valley/logic/has_logic.py | 2 +- .../logic/logic_and_mods_design.md | 2 +- worlds/stardew_valley/logic/mine_logic.py | 12 +--------- worlds/stardew_valley/logic/money_logic.py | 17 +------------- worlds/stardew_valley/logic/monster_logic.py | 2 +- worlds/stardew_valley/logic/museum_logic.py | 2 +- worlds/stardew_valley/logic/pet_logic.py | 7 +----- worlds/stardew_valley/logic/quality_logic.py | 2 +- worlds/stardew_valley/logic/quest_logic.py | 21 ++--------------- worlds/stardew_valley/logic/received_logic.py | 2 +- worlds/stardew_valley/logic/region_logic.py | 2 +- .../logic/relationship_logic.py | 16 +------------ .../stardew_valley/logic/requirement_logic.py | 18 ++------------- worlds/stardew_valley/logic/season_logic.py | 2 +- worlds/stardew_valley/logic/shipping_logic.py | 2 +- worlds/stardew_valley/logic/skill_logic.py | 18 +-------------- worlds/stardew_valley/logic/source_logic.py | 15 ++---------- .../logic/special_order_logic.py | 23 ++----------------- worlds/stardew_valley/logic/time_logic.py | 2 +- worlds/stardew_valley/logic/tool_logic.py | 8 +------ .../logic/traveling_merchant_logic.py | 2 +- worlds/stardew_valley/logic/wallet_logic.py | 2 +- worlds/stardew_valley/logic/walnut_logic.py | 9 +------- .../mods/logic/deepwoods_logic.py | 13 ++--------- .../mods/logic/elevator_logic.py | 3 +-- .../stardew_valley/mods/logic/item_logic.py | 22 ++---------------- .../stardew_valley/mods/logic/magic_logic.py | 7 +----- .../stardew_valley/mods/logic/quests_logic.py | 13 ++--------- .../stardew_valley/mods/logic/skills_logic.py | 16 +------------ .../mods/logic/special_orders_logic.py | 14 +---------- worlds/stardew_valley/mods/logic/sve_logic.py | 19 ++------------- 50 files changed, 70 insertions(+), 417 deletions(-) diff --git a/worlds/stardew_valley/logic/ability_logic.py b/worlds/stardew_valley/logic/ability_logic.py index 2038d995a7..52dbd5abaf 100644 --- a/worlds/stardew_valley/logic/ability_logic.py +++ b/worlds/stardew_valley/logic/ability_logic.py @@ -1,23 +1,9 @@ -import typing -from typing import Union - from .base_logic import BaseLogicMixin, BaseLogic -from .mine_logic import MineLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .skill_logic import SkillLogicMixin -from .tool_logic import ToolLogicMixin -from ..mods.logic.magic_logic import MagicLogicMixin from ..stardew_rule import StardewRule from ..strings.region_names import Region from ..strings.skill_names import Skill, ModSkill from ..strings.tool_names import ToolMaterial, Tool -if typing.TYPE_CHECKING: - from ..mods.logic.mod_logic import ModLogicMixin -else: - ModLogicMixin = object - class AbilityLogicMixin(BaseLogicMixin): def __init__(self, *args, **kwargs): @@ -25,8 +11,7 @@ class AbilityLogicMixin(BaseLogicMixin): self.ability = AbilityLogic(*args, **kwargs) -class AbilityLogic(BaseLogic[Union[AbilityLogicMixin, RegionLogicMixin, ReceivedLogicMixin, ToolLogicMixin, SkillLogicMixin, MineLogicMixin, MagicLogicMixin, -ModLogicMixin]]): +class AbilityLogic(BaseLogic): def can_mine_perfectly(self) -> StardewRule: return self.logic.mine.can_progress_in_the_mines_from_floor(160) diff --git a/worlds/stardew_valley/logic/action_logic.py b/worlds/stardew_valley/logic/action_logic.py index 5b117de68c..64cb18c001 100644 --- a/worlds/stardew_valley/logic/action_logic.py +++ b/worlds/stardew_valley/logic/action_logic.py @@ -1,11 +1,5 @@ -from typing import Union - from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .tool_logic import ToolLogicMixin from ..stardew_rule import StardewRule, True_ from ..strings.generic_names import Generic from ..strings.geode_names import Geode @@ -19,7 +13,7 @@ class ActionLogicMixin(BaseLogicMixin): self.action = ActionLogic(*args, **kwargs) -class ActionLogic(BaseLogic[Union[ActionLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, ToolLogicMixin]]): +class ActionLogic(BaseLogic): def can_watch(self, channel: str = None): tv_rule = True_() diff --git a/worlds/stardew_valley/logic/animal_logic.py b/worlds/stardew_valley/logic/animal_logic.py index 071133d5ce..701cdeb1aa 100644 --- a/worlds/stardew_valley/logic/animal_logic.py +++ b/worlds/stardew_valley/logic/animal_logic.py @@ -6,11 +6,6 @@ from ..strings.building_names import Building from ..strings.forageable_names import Forageable from ..strings.machine_names import Machine -if typing.TYPE_CHECKING: - from .logic import StardewLogic -else: - StardewLogic = object - class AnimalLogicMixin(BaseLogicMixin): def __init__(self, *args, **kwargs): @@ -18,7 +13,7 @@ class AnimalLogicMixin(BaseLogicMixin): self.animal = AnimalLogic(*args, **kwargs) -class AnimalLogic(BaseLogic[StardewLogic]): +class AnimalLogic(BaseLogic): def can_incubate(self, egg_item: str) -> StardewRule: return self.logic.building.has_building(Building.coop) & self.logic.has(egg_item) diff --git a/worlds/stardew_valley/logic/arcade_logic.py b/worlds/stardew_valley/logic/arcade_logic.py index 5e6a02a184..74a2396410 100644 --- a/worlds/stardew_valley/logic/arcade_logic.py +++ b/worlds/stardew_valley/logic/arcade_logic.py @@ -1,8 +1,4 @@ -from typing import Union - from .base_logic import BaseLogic, BaseLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin from .. import options from ..stardew_rule import StardewRule, True_ from ..strings.region_names import Region @@ -14,7 +10,7 @@ class ArcadeLogicMixin(BaseLogicMixin): self.arcade = ArcadeLogic(*args, **kwargs) -class ArcadeLogic(BaseLogic[Union[ArcadeLogicMixin, RegionLogicMixin, ReceivedLogicMixin]]): +class ArcadeLogic(BaseLogic): def has_jotpk_power_level(self, power_level: int) -> StardewRule: if self.options.arcade_machine_locations != options.ArcadeMachineLocations.option_full_shuffling: diff --git a/worlds/stardew_valley/logic/artisan_logic.py b/worlds/stardew_valley/logic/artisan_logic.py index 23f0ae03b7..93c45530af 100644 --- a/worlds/stardew_valley/logic/artisan_logic.py +++ b/worlds/stardew_valley/logic/artisan_logic.py @@ -1,8 +1,4 @@ -from typing import Union - from .base_logic import BaseLogic, BaseLogicMixin -from .has_logic import HasLogicMixin -from .time_logic import TimeLogicMixin from ..data.artisan import MachineSource from ..data.game_item import ItemTag from ..stardew_rule import StardewRule @@ -20,7 +16,7 @@ class ArtisanLogicMixin(BaseLogicMixin): self.artisan = ArtisanLogic(*args, **kwargs) -class ArtisanLogic(BaseLogic[Union[ArtisanLogicMixin, TimeLogicMixin, HasLogicMixin]]): +class ArtisanLogic(BaseLogic): def initialize_rules(self): # TODO remove this one too once fish are converted to sources self.registry.artisan_good_rules.update({ArtisanGood.specific_smoked_fish(fish): self.can_smoke(fish) for fish in all_fish}) diff --git a/worlds/stardew_valley/logic/base_logic.py b/worlds/stardew_valley/logic/base_logic.py index 761ee54157..dce1c328a7 100644 --- a/worlds/stardew_valley/logic/base_logic.py +++ b/worlds/stardew_valley/logic/base_logic.py @@ -1,11 +1,15 @@ from __future__ import annotations -from typing import TypeVar, Generic, Dict, Collection +import typing +from typing import Dict, Collection from ..content.game_content import StardewContent from ..options import StardewValleyOptions from ..stardew_rule import StardewRule +if typing.TYPE_CHECKING: + from .logic import StardewLogic + class LogicRegistry: @@ -30,18 +34,16 @@ class BaseLogicMixin: pass -T = TypeVar("T", bound=BaseLogicMixin) - - -class BaseLogic(BaseLogicMixin, Generic[T]): +class BaseLogic(BaseLogicMixin): player: int registry: LogicRegistry options: StardewValleyOptions content: StardewContent regions: Collection[str] - logic: T + logic: StardewLogic - def __init__(self, player: int, registry: LogicRegistry, options: StardewValleyOptions, content: StardewContent, regions: Collection[str], logic: T): + def __init__(self, player: int, registry: LogicRegistry, options: StardewValleyOptions, content: StardewContent, regions: Collection[str], + logic: StardewLogic): super().__init__(player, registry, options, content, regions, logic) self.player = player self.registry = registry diff --git a/worlds/stardew_valley/logic/book_logic.py b/worlds/stardew_valley/logic/book_logic.py index 464056ee06..50cc38587b 100644 --- a/worlds/stardew_valley/logic/book_logic.py +++ b/worlds/stardew_valley/logic/book_logic.py @@ -1,9 +1,5 @@ -from typing import Union - from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin from ..stardew_rule import StardewRule @@ -13,7 +9,7 @@ class BookLogicMixin(BaseLogicMixin): self.book = BookLogic(*args, **kwargs) -class BookLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin]]): +class BookLogic(BaseLogic): @cache_self1 def has_book_power(self, book: str) -> StardewRule: diff --git a/worlds/stardew_valley/logic/building_logic.py b/worlds/stardew_valley/logic/building_logic.py index 58a375d046..0d96f216e0 100644 --- a/worlds/stardew_valley/logic/building_logic.py +++ b/worlds/stardew_valley/logic/building_logic.py @@ -1,21 +1,11 @@ -import typing from functools import cached_property -from typing import Union from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin from ..stardew_rule import StardewRule, true_ from ..strings.building_names import Building from ..strings.region_names import Region -if typing.TYPE_CHECKING: - from .source_logic import SourceLogicMixin -else: - SourceLogicMixin = object - AUTO_BUILDING_BUILDINGS = {Building.shipping_bin, Building.pet_bowl, Building.farm_house} @@ -25,7 +15,7 @@ class BuildingLogicMixin(BaseLogicMixin): self.building = BuildingLogic(*args, **kwargs) -class BuildingLogic(BaseLogic[Union[BuildingLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, SourceLogicMixin]]): +class BuildingLogic(BaseLogic): @cache_self1 def can_build(self, building_name: str) -> StardewRule: diff --git a/worlds/stardew_valley/logic/bundle_logic.py b/worlds/stardew_valley/logic/bundle_logic.py index 8ede4de5e7..9af91c731c 100644 --- a/worlds/stardew_valley/logic/bundle_logic.py +++ b/worlds/stardew_valley/logic/bundle_logic.py @@ -1,16 +1,7 @@ from functools import cached_property -from typing import Union, List +from typing import List from .base_logic import BaseLogicMixin, BaseLogic -from .fishing_logic import FishingLogicMixin -from .has_logic import HasLogicMixin -from .money_logic import MoneyLogicMixin -from .quality_logic import QualityLogicMixin -from .quest_logic import QuestLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .skill_logic import SkillLogicMixin -from .time_logic import TimeLogicMixin from ..bundles.bundle import Bundle from ..stardew_rule import StardewRule, True_ from ..strings.ap_names.community_upgrade_names import CommunityUpgrade @@ -27,8 +18,7 @@ class BundleLogicMixin(BaseLogicMixin): self.bundle = BundleLogic(*args, **kwargs) -class BundleLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, TimeLogicMixin, RegionLogicMixin, MoneyLogicMixin, QualityLogicMixin, FishingLogicMixin, -SkillLogicMixin, QuestLogicMixin]]): +class BundleLogic(BaseLogic): # Should be cached def can_complete_bundle(self, bundle: Bundle) -> StardewRule: item_rules = [] diff --git a/worlds/stardew_valley/logic/combat_logic.py b/worlds/stardew_valley/logic/combat_logic.py index 849bf14b22..14e8978de2 100644 --- a/worlds/stardew_valley/logic/combat_logic.py +++ b/worlds/stardew_valley/logic/combat_logic.py @@ -1,12 +1,7 @@ from functools import cached_property -from typing import Union from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from ..mods.logic.magic_logic import MagicLogicMixin from ..stardew_rule import StardewRule, False_ from ..strings.ap_names.ap_weapon_names import APWeapon from ..strings.performance_names import Performance @@ -20,7 +15,7 @@ class CombatLogicMixin(BaseLogicMixin): self.combat = CombatLogic(*args, **kwargs) -class CombatLogic(BaseLogic[Union[HasLogicMixin, CombatLogicMixin, RegionLogicMixin, ReceivedLogicMixin, MagicLogicMixin]]): +class CombatLogic(BaseLogic): @cache_self1 def can_fight_at_level(self, level: str) -> StardewRule: if level == Performance.basic: diff --git a/worlds/stardew_valley/logic/cooking_logic.py b/worlds/stardew_valley/logic/cooking_logic.py index 339b2b9817..0959b90a8f 100644 --- a/worlds/stardew_valley/logic/cooking_logic.py +++ b/worlds/stardew_valley/logic/cooking_logic.py @@ -1,17 +1,7 @@ from functools import cached_property -from typing import Union from Utils import cache_self1 -from .action_logic import ActionLogicMixin from .base_logic import BaseLogicMixin, BaseLogic -from .building_logic import BuildingLogicMixin -from .has_logic import HasLogicMixin -from .money_logic import MoneyLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .relationship_logic import RelationshipLogicMixin -from .season_logic import SeasonLogicMixin -from .skill_logic import SkillLogicMixin from ..data.recipe_data import RecipeSource, StarterSource, ShopSource, SkillSource, FriendshipSource, \ QueenOfSauceSource, CookingRecipe, ShopFriendshipSource from ..data.recipe_source import CutsceneSource, ShopTradeSource @@ -29,8 +19,7 @@ class CookingLogicMixin(BaseLogicMixin): self.cooking = CookingLogic(*args, **kwargs) -class CookingLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, MoneyLogicMixin, ActionLogicMixin, -BuildingLogicMixin, RelationshipLogicMixin, SkillLogicMixin, CookingLogicMixin]]): +class CookingLogic(BaseLogic): @cached_property def can_cook_in_kitchen(self) -> StardewRule: return self.logic.building.has_building(Building.kitchen) | self.logic.skill.has_level(Skill.foraging, 9) diff --git a/worlds/stardew_valley/logic/crafting_logic.py b/worlds/stardew_valley/logic/crafting_logic.py index b768a74b92..01dfc5173c 100644 --- a/worlds/stardew_valley/logic/crafting_logic.py +++ b/worlds/stardew_valley/logic/crafting_logic.py @@ -1,15 +1,5 @@ -from typing import Union - from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .has_logic import HasLogicMixin -from .money_logic import MoneyLogicMixin -from .quest_logic import QuestLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .relationship_logic import RelationshipLogicMixin -from .skill_logic import SkillLogicMixin -from .special_order_logic import SpecialOrderLogicMixin from .. import options from ..data.craftable_data import CraftingRecipe from ..data.recipe_source import CutsceneSource, ShopTradeSource, ArchipelagoSource, LogicSource, SpecialOrderSource, \ @@ -25,8 +15,7 @@ class CraftingLogicMixin(BaseLogicMixin): self.crafting = CraftingLogic(*args, **kwargs) -class CraftingLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, MoneyLogicMixin, RelationshipLogicMixin, -SkillLogicMixin, SpecialOrderLogicMixin, CraftingLogicMixin, QuestLogicMixin]]): +class CraftingLogic(BaseLogic): @cache_self1 def can_craft(self, recipe: CraftingRecipe = None) -> StardewRule: if recipe is None: diff --git a/worlds/stardew_valley/logic/farming_logic.py b/worlds/stardew_valley/logic/farming_logic.py index cb8a55e6b4..54c8c8af20 100644 --- a/worlds/stardew_valley/logic/farming_logic.py +++ b/worlds/stardew_valley/logic/farming_logic.py @@ -3,11 +3,6 @@ from typing import Union, Tuple from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .season_logic import SeasonLogicMixin -from .tool_logic import ToolLogicMixin from .. import options from ..stardew_rule import StardewRule, True_, false_ from ..strings.fertilizer_names import Fertilizer @@ -29,7 +24,7 @@ class FarmingLogicMixin(BaseLogicMixin): self.farming = FarmingLogic(*args, **kwargs) -class FarmingLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin, FarmingLogicMixin]]): +class FarmingLogic(BaseLogic): @cached_property def has_farming_tools(self) -> StardewRule: @@ -45,6 +40,8 @@ class FarmingLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogi if tier >= 3: return self.logic.has(Fertilizer.deluxe) + return self.logic.false_ + @cache_self1 def can_plant_and_grow_item(self, seasons: Union[str, Tuple[str]]) -> StardewRule: if seasons == (): # indoor farming diff --git a/worlds/stardew_valley/logic/festival_logic.py b/worlds/stardew_valley/logic/festival_logic.py index 939e904951..b48668964d 100644 --- a/worlds/stardew_valley/logic/festival_logic.py +++ b/worlds/stardew_valley/logic/festival_logic.py @@ -1,20 +1,4 @@ -from typing import Union - -from .action_logic import ActionLogicMixin -from .animal_logic import AnimalLogicMixin -from .artisan_logic import ArtisanLogicMixin from .base_logic import BaseLogicMixin, BaseLogic -from .fishing_logic import FishingLogicMixin -from .gift_logic import GiftLogicMixin -from .has_logic import HasLogicMixin -from .money_logic import MoneyLogicMixin -from .monster_logic import MonsterLogicMixin -from .museum_logic import MuseumLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .relationship_logic import RelationshipLogicMixin -from .skill_logic import SkillLogicMixin -from .time_logic import TimeLogicMixin from ..options import FestivalLocations from ..stardew_rule import StardewRule from ..strings.animal_product_names import AnimalProduct @@ -36,8 +20,7 @@ class FestivalLogicMixin(BaseLogicMixin): self.festival = FestivalLogic(*args, **kwargs) -class FestivalLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, FestivalLogicMixin, ArtisanLogicMixin, AnimalLogicMixin, MoneyLogicMixin, TimeLogicMixin, -SkillLogicMixin, RegionLogicMixin, ActionLogicMixin, MonsterLogicMixin, RelationshipLogicMixin, FishingLogicMixin, MuseumLogicMixin, GiftLogicMixin]]): +class FestivalLogic(BaseLogic): def initialize_rules(self): self.registry.festival_rules.update({ diff --git a/worlds/stardew_valley/logic/fishing_logic.py b/worlds/stardew_valley/logic/fishing_logic.py index 1bb4cccea6..85a9b12040 100644 --- a/worlds/stardew_valley/logic/fishing_logic.py +++ b/worlds/stardew_valley/logic/fishing_logic.py @@ -1,17 +1,8 @@ -from typing import Union - from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .season_logic import SeasonLogicMixin -from .skill_logic import SkillLogicMixin -from .tool_logic import ToolLogicMixin from ..data import fish_data from ..data.fish_data import FishItem -from ..options import ExcludeGingerIsland -from ..options import SpecialOrderLocations +from ..options import ExcludeGingerIsland, SpecialOrderLocations from ..stardew_rule import StardewRule, True_, False_ from ..strings.ap_names.mods.mod_items import SVEQuestItem from ..strings.craftable_names import Fishing @@ -28,8 +19,7 @@ class FishingLogicMixin(BaseLogicMixin): self.fishing = FishingLogic(*args, **kwargs) -class FishingLogic(BaseLogic[Union[HasLogicMixin, FishingLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin, -SkillLogicMixin]]): +class FishingLogic(BaseLogic): def can_fish_in_freshwater(self) -> StardewRule: return self.logic.skill.can_fish() & self.logic.region.can_reach_any((Region.forest, Region.town, Region.mountain)) diff --git a/worlds/stardew_valley/logic/gift_logic.py b/worlds/stardew_valley/logic/gift_logic.py index 527da68764..11667783d6 100644 --- a/worlds/stardew_valley/logic/gift_logic.py +++ b/worlds/stardew_valley/logic/gift_logic.py @@ -1,7 +1,6 @@ from functools import cached_property from .base_logic import BaseLogic, BaseLogicMixin -from .has_logic import HasLogicMixin from ..stardew_rule import StardewRule from ..strings.animal_product_names import AnimalProduct from ..strings.gift_names import Gift @@ -13,7 +12,7 @@ class GiftLogicMixin(BaseLogicMixin): self.gifts = GiftLogic(*args, **kwargs) -class GiftLogic(BaseLogic[HasLogicMixin]): +class GiftLogic(BaseLogic): @cached_property def has_any_universal_love(self) -> StardewRule: diff --git a/worlds/stardew_valley/logic/goal_logic.py b/worlds/stardew_valley/logic/goal_logic.py index 6ffa4da15a..6dbb5f8987 100644 --- a/worlds/stardew_valley/logic/goal_logic.py +++ b/worlds/stardew_valley/logic/goal_logic.py @@ -1,5 +1,3 @@ -import typing - from .base_logic import BaseLogic, BaseLogicMixin from ..data.craftable_data import all_crafting_recipes_by_name from ..data.recipe_data import all_cooking_recipes_by_name @@ -12,11 +10,6 @@ from ..strings.quest_names import Quest from ..strings.season_names import Season from ..strings.wallet_item_names import Wallet -if typing.TYPE_CHECKING: - from .logic import StardewLogic -else: - StardewLogic = object - class GoalLogicMixin(BaseLogicMixin): def __init__(self, *args, **kwargs): @@ -24,7 +17,7 @@ class GoalLogicMixin(BaseLogicMixin): self.goal = GoalLogic(*args, **kwargs) -class GoalLogic(BaseLogic[StardewLogic]): +class GoalLogic(BaseLogic): def can_complete_community_center(self) -> StardewRule: return self.logic.bundle.can_complete_community_center diff --git a/worlds/stardew_valley/logic/grind_logic.py b/worlds/stardew_valley/logic/grind_logic.py index 9550a12830..e18c13c15a 100644 --- a/worlds/stardew_valley/logic/grind_logic.py +++ b/worlds/stardew_valley/logic/grind_logic.py @@ -38,7 +38,7 @@ class GrindLogicMixin(BaseLogicMixin): self.grind = GrindLogic(*args, **kwargs) -class GrindLogic(BaseLogic[Union[GrindLogicMixin, HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, BookLogicMixin, TimeLogicMixin, ToolLogicMixin]]): +class GrindLogic(BaseLogic): def can_grind_mystery_boxes(self, quantity: int) -> StardewRule: opening_rule = self.logic.region.can_reach(Region.blacksmith) diff --git a/worlds/stardew_valley/logic/harvesting_logic.py b/worlds/stardew_valley/logic/harvesting_logic.py index 3b4d41953c..6478e34953 100644 --- a/worlds/stardew_valley/logic/harvesting_logic.py +++ b/worlds/stardew_valley/logic/harvesting_logic.py @@ -1,15 +1,7 @@ from functools import cached_property -from typing import Union from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .farming_logic import FarmingLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .season_logic import SeasonLogicMixin -from .time_logic import TimeLogicMixin -from .tool_logic import ToolLogicMixin from ..data.harvest import ForagingSource, HarvestFruitTreeSource, HarvestCropSource from ..stardew_rule import StardewRule from ..strings.ap_names.community_upgrade_names import CommunityUpgrade @@ -22,8 +14,7 @@ class HarvestingLogicMixin(BaseLogicMixin): self.harvesting = HarvestingLogic(*args, **kwargs) -class HarvestingLogic(BaseLogic[Union[HarvestingLogicMixin, HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, ToolLogicMixin, -FarmingLogicMixin, TimeLogicMixin]]): +class HarvestingLogic(BaseLogic): @cached_property def can_harvest_from_fruit_bats(self) -> StardewRule: diff --git a/worlds/stardew_valley/logic/has_logic.py b/worlds/stardew_valley/logic/has_logic.py index 5d4b700e3b..79c4c53167 100644 --- a/worlds/stardew_valley/logic/has_logic.py +++ b/worlds/stardew_valley/logic/has_logic.py @@ -2,7 +2,7 @@ from .base_logic import BaseLogic from ..stardew_rule import StardewRule, And, Or, Has, Count, true_, false_, HasProgressionPercent -class HasLogicMixin(BaseLogic[None]): +class HasLogicMixin(BaseLogic): true_ = true_ false_ = false_ diff --git a/worlds/stardew_valley/logic/logic_and_mods_design.md b/worlds/stardew_valley/logic/logic_and_mods_design.md index bf6684a354..fc69e2c807 100644 --- a/worlds/stardew_valley/logic/logic_and_mods_design.md +++ b/worlds/stardew_valley/logic/logic_and_mods_design.md @@ -12,7 +12,7 @@ class TimeLogicMixin(BaseLogicMixin): self.time = TimeLogic(*args, **kwargs) -class TimeLogic(BaseLogic[Union[TimeLogicMixin, ReceivedLogicMixin]]): +class TimeLogic(BaseLogic): def has_lived_months(self, number: int) -> StardewRule: return self.logic.received(Event.month_end, number) diff --git a/worlds/stardew_valley/logic/mine_logic.py b/worlds/stardew_valley/logic/mine_logic.py index e332241c10..2dacf67460 100644 --- a/worlds/stardew_valley/logic/mine_logic.py +++ b/worlds/stardew_valley/logic/mine_logic.py @@ -1,14 +1,5 @@ -from typing import Union - from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .combat_logic import CombatLogicMixin -from .cooking_logic import CookingLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .skill_logic import SkillLogicMixin -from .tool_logic import ToolLogicMixin from .. import options from ..stardew_rule import StardewRule, True_ from ..strings.performance_names import Performance @@ -23,8 +14,7 @@ class MineLogicMixin(BaseLogicMixin): self.mine = MineLogic(*args, **kwargs) -class MineLogic(BaseLogic[Union[HasLogicMixin, MineLogicMixin, RegionLogicMixin, ReceivedLogicMixin, CombatLogicMixin, ToolLogicMixin, -SkillLogicMixin, CookingLogicMixin]]): +class MineLogic(BaseLogic): # Regions def can_mine_in_the_mines_floor_1_40(self) -> StardewRule: return self.logic.region.can_reach(Region.mines_floor_5) diff --git a/worlds/stardew_valley/logic/money_logic.py b/worlds/stardew_valley/logic/money_logic.py index f5ca991e72..8f459a172b 100644 --- a/worlds/stardew_valley/logic/money_logic.py +++ b/worlds/stardew_valley/logic/money_logic.py @@ -1,25 +1,11 @@ -import typing -from typing import Union - from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .grind_logic import GrindLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .season_logic import SeasonLogicMixin -from .time_logic import TimeLogicMixin from ..data.shop import ShopSource from ..options import SpecialOrderLocations from ..stardew_rule import StardewRule, True_, HasProgressionPercent, False_, true_ from ..strings.currency_names import Currency from ..strings.region_names import Region, LogicRegion -if typing.TYPE_CHECKING: - from .shipping_logic import ShippingLogicMixin -else: - ShippingLogicMixin = object - qi_gem_rewards = ("100 Qi Gems", "50 Qi Gems", "40 Qi Gems", "35 Qi Gems", "25 Qi Gems", "20 Qi Gems", "15 Qi Gems", "10 Qi Gems") @@ -30,8 +16,7 @@ class MoneyLogicMixin(BaseLogicMixin): self.money = MoneyLogic(*args, **kwargs) -class MoneyLogic(BaseLogic[Union[RegionLogicMixin, MoneyLogicMixin, TimeLogicMixin, RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin, SeasonLogicMixin, -GrindLogicMixin, ShippingLogicMixin]]): +class MoneyLogic(BaseLogic): @cache_self1 def can_have_earned_total(self, amount: int) -> StardewRule: diff --git a/worlds/stardew_valley/logic/monster_logic.py b/worlds/stardew_valley/logic/monster_logic.py index 7e6d786972..5d2ac3d3f6 100644 --- a/worlds/stardew_valley/logic/monster_logic.py +++ b/worlds/stardew_valley/logic/monster_logic.py @@ -20,7 +20,7 @@ class MonsterLogicMixin(BaseLogicMixin): self.monster = MonsterLogic(*args, **kwargs) -class MonsterLogic(BaseLogic[Union[HasLogicMixin, MonsterLogicMixin, RegionLogicMixin, CombatLogicMixin, TimeLogicMixin]]): +class MonsterLogic(BaseLogic): @cached_property def all_monsters_by_name(self): diff --git a/worlds/stardew_valley/logic/museum_logic.py b/worlds/stardew_valley/logic/museum_logic.py index 36ba62b31f..2237cd89ea 100644 --- a/worlds/stardew_valley/logic/museum_logic.py +++ b/worlds/stardew_valley/logic/museum_logic.py @@ -22,7 +22,7 @@ class MuseumLogicMixin(BaseLogicMixin): self.museum = MuseumLogic(*args, **kwargs) -class MuseumLogic(BaseLogic[Union[ReceivedLogicMixin, HasLogicMixin, TimeLogicMixin, RegionLogicMixin, ActionLogicMixin, ToolLogicMixin, MuseumLogicMixin]]): +class MuseumLogic(BaseLogic): def can_donate_museum_items(self, number: int) -> StardewRule: return self.logic.region.can_reach(Region.museum) & self.logic.museum.can_find_museum_items(number) diff --git a/worlds/stardew_valley/logic/pet_logic.py b/worlds/stardew_valley/logic/pet_logic.py index 0438940a66..9d66e8f274 100644 --- a/worlds/stardew_valley/logic/pet_logic.py +++ b/worlds/stardew_valley/logic/pet_logic.py @@ -1,11 +1,6 @@ import math -from typing import Union from .base_logic import BaseLogicMixin, BaseLogic -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .time_logic import TimeLogicMixin -from .tool_logic import ToolLogicMixin from ..content.feature.friendsanity import pet_heart_item_name from ..stardew_rule import StardewRule, True_ from ..strings.region_names import Region @@ -17,7 +12,7 @@ class PetLogicMixin(BaseLogicMixin): self.pet = PetLogic(*args, **kwargs) -class PetLogic(BaseLogic[Union[RegionLogicMixin, ReceivedLogicMixin, TimeLogicMixin, ToolLogicMixin]]): +class PetLogic(BaseLogic): def has_pet_hearts(self, hearts: int = 1) -> StardewRule: assert hearts >= 0, "You can't have negative hearts with a pet." if hearts == 0: diff --git a/worlds/stardew_valley/logic/quality_logic.py b/worlds/stardew_valley/logic/quality_logic.py index 54e2d24265..7f5da4be53 100644 --- a/worlds/stardew_valley/logic/quality_logic.py +++ b/worlds/stardew_valley/logic/quality_logic.py @@ -14,7 +14,7 @@ class QualityLogicMixin(BaseLogicMixin): self.quality = QualityLogic(*args, **kwargs) -class QualityLogic(BaseLogic[Union[SkillLogicMixin, FarmingLogicMixin]]): +class QualityLogic(BaseLogic): @cache_self1 def can_grow_crop_quality(self, quality: str) -> StardewRule: diff --git a/worlds/stardew_valley/logic/quest_logic.py b/worlds/stardew_valley/logic/quest_logic.py index 8779848fed..e48324680d 100644 --- a/worlds/stardew_valley/logic/quest_logic.py +++ b/worlds/stardew_valley/logic/quest_logic.py @@ -1,21 +1,6 @@ -from typing import Dict, Union +from typing import Dict from .base_logic import BaseLogicMixin, BaseLogic -from .building_logic import BuildingLogicMixin -from .combat_logic import CombatLogicMixin -from .cooking_logic import CookingLogicMixin -from .fishing_logic import FishingLogicMixin -from .has_logic import HasLogicMixin -from .mine_logic import MineLogicMixin -from .money_logic import MoneyLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .relationship_logic import RelationshipLogicMixin -from .season_logic import SeasonLogicMixin -from .skill_logic import SkillLogicMixin -from .time_logic import TimeLogicMixin -from .tool_logic import ToolLogicMixin -from .wallet_logic import WalletLogicMixin from ..stardew_rule import StardewRule, Has, True_ from ..strings.ap_names.community_upgrade_names import CommunityUpgrade from ..strings.artisan_good_names import ArtisanGood @@ -43,9 +28,7 @@ class QuestLogicMixin(BaseLogicMixin): self.quest = QuestLogic(*args, **kwargs) -class QuestLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, MoneyLogicMixin, MineLogicMixin, RegionLogicMixin, RelationshipLogicMixin, ToolLogicMixin, - FishingLogicMixin, CookingLogicMixin, CombatLogicMixin, SeasonLogicMixin, SkillLogicMixin, WalletLogicMixin, QuestLogicMixin, - BuildingLogicMixin, TimeLogicMixin]]): +class QuestLogic(BaseLogic): def initialize_rules(self): self.update_rules({ diff --git a/worlds/stardew_valley/logic/received_logic.py b/worlds/stardew_valley/logic/received_logic.py index f5c5c9f7a2..68d65040c7 100644 --- a/worlds/stardew_valley/logic/received_logic.py +++ b/worlds/stardew_valley/logic/received_logic.py @@ -8,7 +8,7 @@ from ..items import item_table from ..stardew_rule import StardewRule, Received, TotalReceived -class ReceivedLogicMixin(BaseLogic[HasLogicMixin], BaseLogicMixin): +class ReceivedLogicMixin(BaseLogic, BaseLogicMixin): def received(self, item: str, count: Optional[int] = 1) -> StardewRule: assert count >= 0, "Can't receive a negative amount of item." diff --git a/worlds/stardew_valley/logic/region_logic.py b/worlds/stardew_valley/logic/region_logic.py index 69afa624f2..083f56e167 100644 --- a/worlds/stardew_valley/logic/region_logic.py +++ b/worlds/stardew_valley/logic/region_logic.py @@ -29,7 +29,7 @@ class RegionLogicMixin(BaseLogicMixin): self.region = RegionLogic(*args, **kwargs) -class RegionLogic(BaseLogic[Union[RegionLogicMixin, HasLogicMixin]]): +class RegionLogic(BaseLogic): @cache_self1 def can_reach(self, region_name: str) -> StardewRule: diff --git a/worlds/stardew_valley/logic/relationship_logic.py b/worlds/stardew_valley/logic/relationship_logic.py index 2de82bf972..e19a6e802b 100644 --- a/worlds/stardew_valley/logic/relationship_logic.py +++ b/worlds/stardew_valley/logic/relationship_logic.py @@ -1,16 +1,8 @@ import math -import typing from typing import Union from Utils import cache_self1 from .base_logic import BaseLogic, BaseLogicMixin -from .building_logic import BuildingLogicMixin -from .gift_logic import GiftLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .season_logic import SeasonLogicMixin -from .time_logic import TimeLogicMixin from ..content.feature import friendsanity from ..data.villagers_data import Villager from ..stardew_rule import StardewRule, True_, false_, true_ @@ -22,11 +14,6 @@ from ..strings.region_names import Region from ..strings.season_names import Season from ..strings.villager_names import NPC, ModNPC -if typing.TYPE_CHECKING: - from ..mods.logic.mod_logic import ModLogicMixin -else: - ModLogicMixin = object - possible_kids = ("Cute Baby", "Ugly Baby") @@ -43,8 +30,7 @@ class RelationshipLogicMixin(BaseLogicMixin): self.relationship = RelationshipLogic(*args, **kwargs) -class RelationshipLogic(BaseLogic[Union[RelationshipLogicMixin, BuildingLogicMixin, SeasonLogicMixin, TimeLogicMixin, GiftLogicMixin, RegionLogicMixin, -ReceivedLogicMixin, HasLogicMixin, ModLogicMixin]]): +class RelationshipLogic(BaseLogic): def can_date(self, npc: str) -> StardewRule: return self.logic.relationship.has_hearts(npc, 8) & self.logic.has(Gift.bouquet) diff --git a/worlds/stardew_valley/logic/requirement_logic.py b/worlds/stardew_valley/logic/requirement_logic.py index 3e83950d54..1a71810003 100644 --- a/worlds/stardew_valley/logic/requirement_logic.py +++ b/worlds/stardew_valley/logic/requirement_logic.py @@ -1,20 +1,7 @@ import functools -from typing import Union, Iterable +from typing import Iterable from .base_logic import BaseLogicMixin, BaseLogic -from .book_logic import BookLogicMixin -from .combat_logic import CombatLogicMixin -from .fishing_logic import FishingLogicMixin -from .has_logic import HasLogicMixin -from .quest_logic import QuestLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .relationship_logic import RelationshipLogicMixin -from .season_logic import SeasonLogicMixin -from .skill_logic import SkillLogicMixin -from .time_logic import TimeLogicMixin -from .tool_logic import ToolLogicMixin -from .walnut_logic import WalnutLogicMixin from ..data.game_item import Requirement from ..data.requirement import ToolRequirement, BookRequirement, SkillRequirement, SeasonRequirement, YearRequirement, CombatRequirement, QuestRequirement, \ RelationshipRequirement, FishingRequirement, WalnutRequirement, RegionRequirement @@ -26,8 +13,7 @@ class RequirementLogicMixin(BaseLogicMixin): self.requirement = RequirementLogic(*args, **kwargs) -class RequirementLogic(BaseLogic[Union[RequirementLogicMixin, HasLogicMixin, ReceivedLogicMixin, ToolLogicMixin, SkillLogicMixin, BookLogicMixin, -SeasonLogicMixin, TimeLogicMixin, CombatLogicMixin, QuestLogicMixin, RelationshipLogicMixin, FishingLogicMixin, WalnutLogicMixin, RegionLogicMixin]]): +class RequirementLogic(BaseLogic): def meet_all_requirements(self, requirements: Iterable[Requirement]): if not requirements: diff --git a/worlds/stardew_valley/logic/season_logic.py b/worlds/stardew_valley/logic/season_logic.py index 6df315c0db..eecfd48582 100644 --- a/worlds/stardew_valley/logic/season_logic.py +++ b/worlds/stardew_valley/logic/season_logic.py @@ -18,7 +18,7 @@ class SeasonLogicMixin(BaseLogicMixin): self.season = SeasonLogic(*args, **kwargs) -class SeasonLogic(BaseLogic[Union[HasLogicMixin, SeasonLogicMixin, TimeLogicMixin, ReceivedLogicMixin]]): +class SeasonLogic(BaseLogic): @cached_property def has_spring(self) -> StardewRule: diff --git a/worlds/stardew_valley/logic/shipping_logic.py b/worlds/stardew_valley/logic/shipping_logic.py index d509cc4167..9f5ff51876 100644 --- a/worlds/stardew_valley/logic/shipping_logic.py +++ b/worlds/stardew_valley/logic/shipping_logic.py @@ -20,7 +20,7 @@ class ShippingLogicMixin(BaseLogicMixin): self.shipping = ShippingLogic(*args, **kwargs) -class ShippingLogic(BaseLogic[Union[ReceivedLogicMixin, ShippingLogicMixin, BuildingLogicMixin, RegionLogicMixin, HasLogicMixin]]): +class ShippingLogic(BaseLogic): @cached_property def can_use_shipping_bin(self) -> StardewRule: diff --git a/worlds/stardew_valley/logic/skill_logic.py b/worlds/stardew_valley/logic/skill_logic.py index 6d0cd11baf..e02b180f6a 100644 --- a/worlds/stardew_valley/logic/skill_logic.py +++ b/worlds/stardew_valley/logic/skill_logic.py @@ -1,19 +1,9 @@ -import typing from functools import cached_property from typing import Union, Tuple from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .combat_logic import CombatLogicMixin -from .harvesting_logic import HarvestingLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .season_logic import SeasonLogicMixin -from .time_logic import TimeLogicMixin -from .tool_logic import ToolLogicMixin from ..data.harvest import HarvestCropSource -from ..mods.logic.magic_logic import MagicLogicMixin from ..mods.logic.mod_skills_levels import get_mod_skill_levels from ..stardew_rule import StardewRule, true_, True_, False_ from ..strings.craftable_names import Fishing @@ -25,11 +15,6 @@ from ..strings.skill_names import Skill, all_mod_skills, all_vanilla_skills from ..strings.tool_names import ToolMaterial, Tool from ..strings.wallet_item_names import Wallet -if typing.TYPE_CHECKING: - from ..mods.logic.mod_logic import ModLogicMixin -else: - ModLogicMixin = object - fishing_regions = (Region.beach, Region.town, Region.forest, Region.mountain, Region.island_south, Region.island_west) vanilla_skill_items = ("Farming Level", "Mining Level", "Foraging Level", "Fishing Level", "Combat Level") @@ -40,8 +25,7 @@ class SkillLogicMixin(BaseLogicMixin): self.skill = SkillLogic(*args, **kwargs) -class SkillLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, TimeLogicMixin, ToolLogicMixin, SkillLogicMixin, -CombatLogicMixin, MagicLogicMixin, HarvestingLogicMixin, ModLogicMixin]]): +class SkillLogic(BaseLogic): # Should be cached def can_earn_level(self, skill: str, level: int) -> StardewRule: diff --git a/worlds/stardew_valley/logic/source_logic.py b/worlds/stardew_valley/logic/source_logic.py index 67ce55177c..ecdb6f02a3 100644 --- a/worlds/stardew_valley/logic/source_logic.py +++ b/worlds/stardew_valley/logic/source_logic.py @@ -1,17 +1,7 @@ import functools -from typing import Union, Any, Iterable +from typing import Any, Iterable -from .animal_logic import AnimalLogicMixin -from .artisan_logic import ArtisanLogicMixin from .base_logic import BaseLogicMixin, BaseLogic -from .grind_logic import GrindLogicMixin -from .harvesting_logic import HarvestingLogicMixin -from .has_logic import HasLogicMixin -from .money_logic import MoneyLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .requirement_logic import RequirementLogicMixin -from .tool_logic import ToolLogicMixin from ..data.animal import IncubatorSource, OstrichIncubatorSource from ..data.artisan import MachineSource from ..data.game_item import GenericSource, Source, GameItem, CustomRuleSource @@ -26,8 +16,7 @@ class SourceLogicMixin(BaseLogicMixin): self.source = SourceLogic(*args, **kwargs) -class SourceLogic(BaseLogic[Union[SourceLogicMixin, HasLogicMixin, ReceivedLogicMixin, HarvestingLogicMixin, MoneyLogicMixin, RegionLogicMixin, -ArtisanLogicMixin, ToolLogicMixin, RequirementLogicMixin, GrindLogicMixin, AnimalLogicMixin]]): +class SourceLogic(BaseLogic): def has_access_to_item(self, item: GameItem): rules = [] diff --git a/worlds/stardew_valley/logic/special_order_logic.py b/worlds/stardew_valley/logic/special_order_logic.py index 8bcd78d7d2..a81f715c48 100644 --- a/worlds/stardew_valley/logic/special_order_logic.py +++ b/worlds/stardew_valley/logic/special_order_logic.py @@ -1,22 +1,6 @@ -from typing import Dict, Union +from typing import Dict -from .ability_logic import AbilityLogicMixin -from .arcade_logic import ArcadeLogicMixin -from .artisan_logic import ArtisanLogicMixin from .base_logic import BaseLogicMixin, BaseLogic -from .cooking_logic import CookingLogicMixin -from .has_logic import HasLogicMixin -from .mine_logic import MineLogicMixin -from .money_logic import MoneyLogicMixin -from .monster_logic import MonsterLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .relationship_logic import RelationshipLogicMixin -from .season_logic import SeasonLogicMixin -from .shipping_logic import ShippingLogicMixin -from .skill_logic import SkillLogicMixin -from .time_logic import TimeLogicMixin -from .tool_logic import ToolLogicMixin from ..content.vanilla.ginger_island import ginger_island_content_pack from ..content.vanilla.qi_board import qi_board_content_pack from ..stardew_rule import StardewRule, Has, false_ @@ -44,10 +28,7 @@ class SpecialOrderLogicMixin(BaseLogicMixin): self.special_order = SpecialOrderLogic(*args, **kwargs) -class SpecialOrderLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, TimeLogicMixin, MoneyLogicMixin, -ShippingLogicMixin, ArcadeLogicMixin, ArtisanLogicMixin, RelationshipLogicMixin, ToolLogicMixin, SkillLogicMixin, -MineLogicMixin, CookingLogicMixin, -AbilityLogicMixin, SpecialOrderLogicMixin, MonsterLogicMixin]]): +class SpecialOrderLogic(BaseLogic): def initialize_rules(self): self.update_rules({ diff --git a/worlds/stardew_valley/logic/time_logic.py b/worlds/stardew_valley/logic/time_logic.py index 2ba76579ff..d26723d2a5 100644 --- a/worlds/stardew_valley/logic/time_logic.py +++ b/worlds/stardew_valley/logic/time_logic.py @@ -22,7 +22,7 @@ class TimeLogicMixin(BaseLogicMixin): self.time = TimeLogic(*args, **kwargs) -class TimeLogic(BaseLogic[Union[TimeLogicMixin, HasLogicMixin]]): +class TimeLogic(BaseLogic): @cache_self1 def has_lived_months(self, number: int) -> StardewRule: diff --git a/worlds/stardew_valley/logic/tool_logic.py b/worlds/stardew_valley/logic/tool_logic.py index 8292325af7..dba8bb2980 100644 --- a/worlds/stardew_valley/logic/tool_logic.py +++ b/worlds/stardew_valley/logic/tool_logic.py @@ -2,12 +2,6 @@ from typing import Union, Iterable, Tuple from Utils import cache_self1 from .base_logic import BaseLogicMixin, BaseLogic -from .has_logic import HasLogicMixin -from .money_logic import MoneyLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin -from .season_logic import SeasonLogicMixin -from ..mods.logic.magic_logic import MagicLogicMixin from ..stardew_rule import StardewRule, True_, False_ from ..strings.ap_names.skill_level_names import ModSkillLevel from ..strings.region_names import Region, LogicRegion @@ -40,7 +34,7 @@ class ToolLogicMixin(BaseLogicMixin): self.tool = ToolLogic(*args, **kwargs) -class ToolLogic(BaseLogic[Union[ToolLogicMixin, HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, SeasonLogicMixin, MoneyLogicMixin, MagicLogicMixin]]): +class ToolLogic(BaseLogic): def has_all_tools(self, tools: Iterable[Tuple[str, str]]): return self.logic.and_(*(self.logic.tool.has_tool(tool, material) for tool, material in tools)) diff --git a/worlds/stardew_valley/logic/traveling_merchant_logic.py b/worlds/stardew_valley/logic/traveling_merchant_logic.py index 4123ded5bf..743ff9949b 100644 --- a/worlds/stardew_valley/logic/traveling_merchant_logic.py +++ b/worlds/stardew_valley/logic/traveling_merchant_logic.py @@ -12,7 +12,7 @@ class TravelingMerchantLogicMixin(BaseLogicMixin): self.traveling_merchant = TravelingMerchantLogic(*args, **kwargs) -class TravelingMerchantLogic(BaseLogic[Union[TravelingMerchantLogicMixin, ReceivedLogicMixin]]): +class TravelingMerchantLogic(BaseLogic): def has_days(self, number_days: int = 1): if number_days <= 0: diff --git a/worlds/stardew_valley/logic/wallet_logic.py b/worlds/stardew_valley/logic/wallet_logic.py index 3a6d126400..eb7afb9af3 100644 --- a/worlds/stardew_valley/logic/wallet_logic.py +++ b/worlds/stardew_valley/logic/wallet_logic.py @@ -10,7 +10,7 @@ class WalletLogicMixin(BaseLogicMixin): self.wallet = WalletLogic(*args, **kwargs) -class WalletLogic(BaseLogic[ReceivedLogicMixin]): +class WalletLogic(BaseLogic): def can_speak_dwarf(self) -> StardewRule: return self.logic.received(Wallet.dwarvish_translation_guide) diff --git a/worlds/stardew_valley/logic/walnut_logic.py b/worlds/stardew_valley/logic/walnut_logic.py index 4ab3b46f70..fb83b65907 100644 --- a/worlds/stardew_valley/logic/walnut_logic.py +++ b/worlds/stardew_valley/logic/walnut_logic.py @@ -1,12 +1,6 @@ from functools import cached_property -from typing import Union -from .ability_logic import AbilityLogicMixin from .base_logic import BaseLogic, BaseLogicMixin -from .combat_logic import CombatLogicMixin -from .has_logic import HasLogicMixin -from .received_logic import ReceivedLogicMixin -from .region_logic import RegionLogicMixin from ..options import ExcludeGingerIsland, Walnutsanity from ..stardew_rule import StardewRule, False_, True_ from ..strings.ap_names.ap_option_names import WalnutsanityOptionName @@ -24,8 +18,7 @@ class WalnutLogicMixin(BaseLogicMixin): self.walnut = WalnutLogic(*args, **kwargs) -class WalnutLogic(BaseLogic[Union[WalnutLogicMixin, ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, CombatLogicMixin, -AbilityLogicMixin]]): +class WalnutLogic(BaseLogic): def has_walnut(self, number: int) -> StardewRule: if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true: diff --git a/worlds/stardew_valley/mods/logic/deepwoods_logic.py b/worlds/stardew_valley/mods/logic/deepwoods_logic.py index 6e0eadfd54..17db3c0a6f 100644 --- a/worlds/stardew_valley/mods/logic/deepwoods_logic.py +++ b/worlds/stardew_valley/mods/logic/deepwoods_logic.py @@ -1,13 +1,5 @@ -from typing import Union - +from ..mod_data import ModNames from ...logic.base_logic import BaseLogicMixin, BaseLogic -from ...logic.combat_logic import CombatLogicMixin -from ...logic.cooking_logic import CookingLogicMixin -from ...logic.has_logic import HasLogicMixin -from ...logic.received_logic import ReceivedLogicMixin -from ...logic.skill_logic import SkillLogicMixin -from ...logic.tool_logic import ToolLogicMixin -from ...mods.mod_data import ModNames from ...options import ElevatorProgression from ...stardew_rule import StardewRule, True_, true_ from ...strings.ap_names.mods.mod_items import DeepWoodsItem @@ -25,8 +17,7 @@ class DeepWoodsLogicMixin(BaseLogicMixin): self.deepwoods = DeepWoodsLogic(*args, **kwargs) -class DeepWoodsLogic(BaseLogic[Union[SkillLogicMixin, ReceivedLogicMixin, HasLogicMixin, CombatLogicMixin, ToolLogicMixin, SkillLogicMixin, -CookingLogicMixin]]): +class DeepWoodsLogic(BaseLogic): def can_reach_woods_depth(self, depth: int) -> StardewRule: # Assuming you can always do the 10 first floor diff --git a/worlds/stardew_valley/mods/logic/elevator_logic.py b/worlds/stardew_valley/mods/logic/elevator_logic.py index f1d12bcb1c..8e154492e4 100644 --- a/worlds/stardew_valley/mods/logic/elevator_logic.py +++ b/worlds/stardew_valley/mods/logic/elevator_logic.py @@ -1,5 +1,4 @@ from ...logic.base_logic import BaseLogicMixin, BaseLogic -from ...logic.received_logic import ReceivedLogicMixin from ...mods.mod_data import ModNames from ...options import ElevatorProgression from ...stardew_rule import StardewRule, True_ @@ -11,7 +10,7 @@ class ModElevatorLogicMixin(BaseLogicMixin): self.elevator = ModElevatorLogic(*args, **kwargs) -class ModElevatorLogic(BaseLogic[ReceivedLogicMixin]): +class ModElevatorLogic(BaseLogic): def has_skull_cavern_elevator_to_floor(self, floor: int) -> StardewRule: if self.options.elevator_progression != ElevatorProgression.option_vanilla and ModNames.skull_cavern_elevator in self.options.mods: return self.logic.received("Progressive Skull Cavern Elevator", floor // 25) diff --git a/worlds/stardew_valley/mods/logic/item_logic.py b/worlds/stardew_valley/mods/logic/item_logic.py index fd87a4a0ac..7394d82ba1 100644 --- a/worlds/stardew_valley/mods/logic/item_logic.py +++ b/worlds/stardew_valley/mods/logic/item_logic.py @@ -1,23 +1,7 @@ -from typing import Dict, Union +from typing import Dict from ..mod_data import ModNames from ...logic.base_logic import BaseLogicMixin, BaseLogic -from ...logic.combat_logic import CombatLogicMixin -from ...logic.cooking_logic import CookingLogicMixin -from ...logic.crafting_logic import CraftingLogicMixin -from ...logic.farming_logic import FarmingLogicMixin -from ...logic.fishing_logic import FishingLogicMixin -from ...logic.has_logic import HasLogicMixin -from ...logic.money_logic import MoneyLogicMixin -from ...logic.museum_logic import MuseumLogicMixin -from ...logic.quest_logic import QuestLogicMixin -from ...logic.received_logic import ReceivedLogicMixin -from ...logic.region_logic import RegionLogicMixin -from ...logic.relationship_logic import RelationshipLogicMixin -from ...logic.season_logic import SeasonLogicMixin -from ...logic.skill_logic import SkillLogicMixin -from ...logic.time_logic import TimeLogicMixin -from ...logic.tool_logic import ToolLogicMixin from ...stardew_rule import StardewRule from ...strings.artisan_good_names import ModArtisanGood from ...strings.craftable_names import ModCraftable @@ -39,9 +23,7 @@ class ModItemLogicMixin(BaseLogicMixin): self.item = ModItemLogic(*args, **kwargs) -class ModItemLogic(BaseLogic[Union[CombatLogicMixin, ReceivedLogicMixin, CookingLogicMixin, FishingLogicMixin, HasLogicMixin, MoneyLogicMixin, -RegionLogicMixin, SeasonLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, ToolLogicMixin, CraftingLogicMixin, SkillLogicMixin, TimeLogicMixin, QuestLogicMixin, -FarmingLogicMixin]]): +class ModItemLogic(BaseLogic): def get_modded_item_rules(self) -> Dict[str, StardewRule]: items = dict() diff --git a/worlds/stardew_valley/mods/logic/magic_logic.py b/worlds/stardew_valley/mods/logic/magic_logic.py index 662ff3acae..2e3a0e1f97 100644 --- a/worlds/stardew_valley/mods/logic/magic_logic.py +++ b/worlds/stardew_valley/mods/logic/magic_logic.py @@ -1,9 +1,4 @@ -from typing import Union - from ...logic.base_logic import BaseLogicMixin, BaseLogic -from ...logic.has_logic import HasLogicMixin -from ...logic.received_logic import ReceivedLogicMixin -from ...logic.region_logic import RegionLogicMixin from ...mods.mod_data import ModNames from ...stardew_rule import StardewRule, False_ from ...strings.ap_names.skill_level_names import ModSkillLevel @@ -18,7 +13,7 @@ class MagicLogicMixin(BaseLogicMixin): # TODO add logic.mods.magic for altar -class MagicLogic(BaseLogic[Union[RegionLogicMixin, ReceivedLogicMixin, HasLogicMixin]]): +class MagicLogic(BaseLogic): def can_use_clear_debris_instead_of_tool_level(self, level: int) -> StardewRule: if ModNames.magic not in self.options.mods: return False_() diff --git a/worlds/stardew_valley/mods/logic/quests_logic.py b/worlds/stardew_valley/mods/logic/quests_logic.py index ef96982661..8b1eca7fc2 100644 --- a/worlds/stardew_valley/mods/logic/quests_logic.py +++ b/worlds/stardew_valley/mods/logic/quests_logic.py @@ -1,15 +1,7 @@ -from typing import Dict, Union +from typing import Dict from ..mod_data import ModNames from ...logic.base_logic import BaseLogic, BaseLogicMixin -from ...logic.has_logic import HasLogicMixin -from ...logic.monster_logic import MonsterLogicMixin -from ...logic.quest_logic import QuestLogicMixin -from ...logic.received_logic import ReceivedLogicMixin -from ...logic.region_logic import RegionLogicMixin -from ...logic.relationship_logic import RelationshipLogicMixin -from ...logic.season_logic import SeasonLogicMixin -from ...logic.time_logic import TimeLogicMixin from ...stardew_rule import StardewRule from ...strings.animal_product_names import AnimalProduct from ...strings.ap_names.mods.mod_items import SVEQuestItem @@ -34,8 +26,7 @@ class ModQuestLogicMixin(BaseLogicMixin): self.quest = ModQuestLogic(*args, **kwargs) -class ModQuestLogic(BaseLogic[Union[HasLogicMixin, QuestLogicMixin, ReceivedLogicMixin, RegionLogicMixin, -TimeLogicMixin, SeasonLogicMixin, RelationshipLogicMixin, MonsterLogicMixin]]): +class ModQuestLogic(BaseLogic): def get_modded_quest_rules(self) -> Dict[str, StardewRule]: quests = dict() quests.update(self._get_juna_quest_rules()) diff --git a/worlds/stardew_valley/mods/logic/skills_logic.py b/worlds/stardew_valley/mods/logic/skills_logic.py index ba9d277418..b1f10d08b9 100644 --- a/worlds/stardew_valley/mods/logic/skills_logic.py +++ b/worlds/stardew_valley/mods/logic/skills_logic.py @@ -1,17 +1,4 @@ -from typing import Union - -from .magic_logic import MagicLogicMixin -from ...logic.action_logic import ActionLogicMixin from ...logic.base_logic import BaseLogicMixin, BaseLogic -from ...logic.building_logic import BuildingLogicMixin -from ...logic.cooking_logic import CookingLogicMixin -from ...logic.crafting_logic import CraftingLogicMixin -from ...logic.fishing_logic import FishingLogicMixin -from ...logic.has_logic import HasLogicMixin -from ...logic.received_logic import ReceivedLogicMixin -from ...logic.region_logic import RegionLogicMixin -from ...logic.relationship_logic import RelationshipLogicMixin -from ...logic.tool_logic import ToolLogicMixin from ...mods.mod_data import ModNames from ...stardew_rule import StardewRule, False_, True_, And from ...strings.building_names import Building @@ -30,8 +17,7 @@ class ModSkillLogicMixin(BaseLogicMixin): self.skill = ModSkillLogic(*args, **kwargs) -class ModSkillLogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, RegionLogicMixin, ActionLogicMixin, RelationshipLogicMixin, BuildingLogicMixin, -ToolLogicMixin, FishingLogicMixin, CookingLogicMixin, CraftingLogicMixin, MagicLogicMixin]]): +class ModSkillLogic(BaseLogic): def has_mod_level(self, skill: str, level: int) -> StardewRule: if level <= 0: return True_() diff --git a/worlds/stardew_valley/mods/logic/special_orders_logic.py b/worlds/stardew_valley/mods/logic/special_orders_logic.py index 1a0934282e..f697661419 100644 --- a/worlds/stardew_valley/mods/logic/special_orders_logic.py +++ b/worlds/stardew_valley/mods/logic/special_orders_logic.py @@ -1,17 +1,6 @@ -from typing import Union - from ..mod_data import ModNames from ...data.craftable_data import all_crafting_recipes_by_name -from ...logic.action_logic import ActionLogicMixin -from ...logic.artisan_logic import ArtisanLogicMixin from ...logic.base_logic import BaseLogicMixin, BaseLogic -from ...logic.crafting_logic import CraftingLogicMixin -from ...logic.has_logic import HasLogicMixin -from ...logic.received_logic import ReceivedLogicMixin -from ...logic.region_logic import RegionLogicMixin -from ...logic.relationship_logic import RelationshipLogicMixin -from ...logic.season_logic import SeasonLogicMixin -from ...logic.wallet_logic import WalletLogicMixin from ...strings.ap_names.community_upgrade_names import CommunityUpgrade from ...strings.artisan_good_names import ArtisanGood from ...strings.craftable_names import Consumable, Edible, Bomb @@ -33,8 +22,7 @@ class ModSpecialOrderLogicMixin(BaseLogicMixin): self.special_order = ModSpecialOrderLogic(*args, **kwargs) -class ModSpecialOrderLogic(BaseLogic[Union[ActionLogicMixin, ArtisanLogicMixin, CraftingLogicMixin, HasLogicMixin, RegionLogicMixin, -ReceivedLogicMixin, RelationshipLogicMixin, SeasonLogicMixin, WalletLogicMixin]]): +class ModSpecialOrderLogic(BaseLogic): def get_modded_special_orders_rules(self): special_orders = {} if ModNames.juna in self.options.mods: diff --git a/worlds/stardew_valley/mods/logic/sve_logic.py b/worlds/stardew_valley/mods/logic/sve_logic.py index faca8d332d..7f0c12bc4f 100644 --- a/worlds/stardew_valley/mods/logic/sve_logic.py +++ b/worlds/stardew_valley/mods/logic/sve_logic.py @@ -1,21 +1,7 @@ -from typing import Union - from ..mod_regions import SVERegion from ...logic.base_logic import BaseLogicMixin, BaseLogic -from ...logic.combat_logic import CombatLogicMixin -from ...logic.cooking_logic import CookingLogicMixin -from ...logic.has_logic import HasLogicMixin -from ...logic.money_logic import MoneyLogicMixin -from ...logic.quest_logic import QuestLogicMixin -from ...logic.received_logic import ReceivedLogicMixin -from ...logic.region_logic import RegionLogicMixin -from ...logic.relationship_logic import RelationshipLogicMixin -from ...logic.season_logic import SeasonLogicMixin -from ...logic.time_logic import TimeLogicMixin -from ...logic.tool_logic import ToolLogicMixin from ...strings.ap_names.mods.mod_items import SVELocation, SVERunes, SVEQuestItem -from ...strings.quest_names import ModQuest -from ...strings.quest_names import Quest +from ...strings.quest_names import Quest, ModQuest from ...strings.region_names import Region from ...strings.tool_names import Tool, ToolMaterial from ...strings.wallet_item_names import Wallet @@ -27,8 +13,7 @@ class SVELogicMixin(BaseLogicMixin): self.sve = SVELogic(*args, **kwargs) -class SVELogic(BaseLogic[Union[HasLogicMixin, ReceivedLogicMixin, QuestLogicMixin, RegionLogicMixin, RelationshipLogicMixin, TimeLogicMixin, ToolLogicMixin, - CookingLogicMixin, MoneyLogicMixin, CombatLogicMixin, SeasonLogicMixin]]): +class SVELogic(BaseLogic): def initialize_rules(self): self.registry.sve_location_rules.update({ SVELocation.tempered_galaxy_sword: self.logic.money.can_spend_at(SVERegion.alesia_shop, 350000), From 73964b374c6d502b588e33c6c3c93045f267920b Mon Sep 17 00:00:00 2001 From: black-sliver <59490463+black-sliver@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:40:36 +0000 Subject: [PATCH 33/46] MultiServer: import get_settings from the correct module (#4914) * MultiServer: import get_settings from the correct module * MultiServer: settings: use attr inbstead of dict access --- MultiServer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/MultiServer.py b/MultiServer.py index 4295f28c58..bdc6b8c84f 100644 --- a/MultiServer.py +++ b/MultiServer.py @@ -2419,8 +2419,10 @@ async def console(ctx: Context): def parse_args() -> argparse.Namespace: + from settings import get_settings + parser = argparse.ArgumentParser() - defaults = Utils.get_settings()["server_options"].as_dict() + defaults = get_settings().server_options.as_dict() parser.add_argument('multidata', nargs="?", default=defaults["multidata"]) parser.add_argument('--host', default=defaults["host"]) parser.add_argument('--port', default=defaults["port"], type=int) From febd280fba57c2d015b7e9c8a820150b11268ec8 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Wed, 23 Apr 2025 20:30:15 +0200 Subject: [PATCH 34/46] Setup: use sha256 for timestamp server (#4892) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 00a65330a0..ebef215e2d 100644 --- a/setup.py +++ b/setup.py @@ -154,7 +154,7 @@ if os.path.exists("X:/pw.txt"): with open("X:/pw.txt", encoding="utf-8-sig") as f: pw = f.read() signtool = r'signtool sign /f X:/_SITS_Zertifikat_.pfx /p "' + pw + \ - r'" /fd sha256 /tr http://timestamp.digicert.com/ ' + r'" /fd sha256 /td sha256 /tr http://timestamp.digicert.com/ ' else: signtool = None From 29e6a10e4271ad5ae024acc32051a102c2f0eed2 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Thu, 24 Apr 2025 08:50:34 +0200 Subject: [PATCH 35/46] Setup: offer the default-on option to clean /lib folder on update (#4890) Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> --- inno_setup.iss | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/inno_setup.iss b/inno_setup.iss index 9d03ca7baf..adf9acc834 100644 --- a/inno_setup.iss +++ b/inno_setup.iss @@ -45,7 +45,8 @@ MinVersion={#min_windows} Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] -Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; +Name: "deletelib"; Description: "Clean existing /lib folder and subfolders including /worlds (leave checked if unsure)"; Check: ShouldShowDeleteLibTask [Types] Name: "full"; Description: "Full installation" @@ -83,18 +84,8 @@ Filename: "{app}\ArchipelagoLauncher"; Description: "{cm:LaunchProgram,{#StringC Type: dirifempty; Name: "{app}" [InstallDelete] -Type: files; Name: "{app}\lib\worlds\_bizhawk.apworld" -Type: files; Name: "{app}\ArchipelagoLttPClient.exe" -Type: files; Name: "{app}\ArchipelagoPokemonClient.exe" +Type: files; Name: "{app}\*.exe" Type: files; Name: "{app}\data\lua\connector_pkmn_rb.lua" -Type: filesandordirs; Name: "{app}\lib\worlds\rogue-legacy" -Type: dirifempty; Name: "{app}\lib\worlds\rogue-legacy" -Type: files; Name: "{app}\lib\worlds\sc2wol.apworld" -Type: filesandordirs; Name: "{app}\lib\worlds\sc2wol" -Type: dirifempty; Name: "{app}\lib\worlds\sc2wol" -Type: filesandordirs; Name: "{app}\lib\worlds\bk_sudoku" -Type: dirifempty; Name: "{app}\lib\worlds\bk_sudoku" -Type: files; Name: "{app}\ArchipelagoLauncher(DEBUG).exe" Type: filesandordirs; Name: "{app}\SNI\lua*" Type: filesandordirs; Name: "{app}\EnemizerCLI*" #include "installdelete.iss" @@ -261,3 +252,17 @@ begin Result := True; end; end; + +function ShouldShowDeleteLibTask: Boolean; +begin + Result := DirExists(ExpandConstant('{app}\lib')); +end; + +procedure CurStepChanged(CurStep: TSetupStep); +begin + if CurStep = ssInstall then + begin + if WizardIsTaskSelected('deletelib') then + DelTree(ExpandConstant('{app}\lib'), True, True, True); + end; +end; From a84366368f8509485b44e109c775183e8022aefc Mon Sep 17 00:00:00 2001 From: Scipio Wright Date: Thu, 24 Apr 2025 09:38:30 -0400 Subject: [PATCH 36/46] Docs: Update comment for create_item (#4919) --- docs/world api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/world api.md b/docs/world api.md index e55d4fb9f5..013b02cc20 100644 --- a/docs/world api.md +++ b/docs/world api.md @@ -561,7 +561,7 @@ from .items import is_progression # this is just a dummy def create_item(self, item: str) -> MyGameItem: - # this is called when AP wants to create an item by name (for plando) or when you call it from your own code + # this is called when AP wants to create an item by name (for plando, start inventory, item links) or when you call it from your own code classification = ItemClassification.progression if is_progression(item) else ItemClassification.filler return MyGameItem(item, classification, self.item_name_to_id[item], self.player) From 03768a5f90b44bafc7ca5a37aed7f46067bb8732 Mon Sep 17 00:00:00 2001 From: Aaron Wagener Date: Thu, 24 Apr 2025 14:23:51 -0500 Subject: [PATCH 37/46] Tests: Test that a world can generate with item links (#2081) Co-authored-by: Fabian Dill Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --- test/general/test_items.py | 49 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/test/general/test_items.py b/test/general/test_items.py index f9488e1b25..1b376b2838 100644 --- a/test/general/test_items.py +++ b/test/general/test_items.py @@ -1,7 +1,11 @@ import unittest +from argparse import Namespace +from typing import Type -from BaseClasses import CollectionState -from worlds.AutoWorld import AutoWorldRegister, call_all +from BaseClasses import CollectionState, MultiWorld +from Fill import distribute_items_restrictive +from Options import ItemLinks +from worlds.AutoWorld import AutoWorldRegister, World, call_all from . import setup_solo_multiworld @@ -83,6 +87,47 @@ class TestBase(unittest.TestCase): multiworld = setup_solo_multiworld(world_type) for item in multiworld.itempool: self.assertIn(item.name, world_type.item_name_to_id) + + def test_item_links(self) -> None: + """ + Tests item link creation by creating a multiworld of 2 worlds for every game and linking their items together. + """ + def setup_link_multiworld(world: Type[World], link_replace: bool) -> None: + multiworld = MultiWorld(2) + multiworld.game = {1: world.game, 2: world.game} + multiworld.player_name = {1: "Linker 1", 2: "Linker 2"} + multiworld.set_seed() + item_link_group = [{ + "name": "ItemLinkTest", + "item_pool": ["Everything"], + "link_replacement": link_replace, + "replacement_item": None, + }] + args = Namespace() + for name, option in world.options_dataclass.type_hints.items(): + setattr(args, name, {1: option.from_any(option.default), 2: option.from_any(option.default)}) + setattr(args, "item_links", + {1: ItemLinks.from_any(item_link_group), 2: ItemLinks.from_any(item_link_group)}) + multiworld.set_options(args) + multiworld.set_item_links() + # groups get added to state during its constructor so this has to be after item links are set + multiworld.state = CollectionState(multiworld) + gen_steps = ("generate_early", "create_regions", "create_items", "set_rules", "connect_entrances", "generate_basic") + for step in gen_steps: + call_all(multiworld, step) + # link the items together and attempt to fill + multiworld.link_items() + multiworld._all_state = None + call_all(multiworld, "pre_fill") + distribute_items_restrictive(multiworld) + call_all(multiworld, "post_fill") + self.assertTrue(multiworld.can_beat_game(CollectionState(multiworld)), f"seed = {multiworld.seed}") + + for game_name, world_type in AutoWorldRegister.world_types.items(): + with self.subTest("Can generate with link replacement", game=game_name): + setup_link_multiworld(world_type, True) + with self.subTest("Can generate without link replacement", game=game_name): + setup_link_multiworld(world_type, False) def test_itempool_not_modified(self): """Test that worlds don't modify the itempool after `create_items`""" From 5bb87c6da5b38f097ff49342470f82cd8b8a140a Mon Sep 17 00:00:00 2001 From: Jarno Date: Thu, 24 Apr 2025 21:33:30 +0200 Subject: [PATCH 38/46] Tests: Make overlapping test actually print out the overlaps (#4431) --- test/general/test_ids.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/test/general/test_ids.py b/test/general/test_ids.py index e51a070c1f..ad8aad11d1 100644 --- a/test/general/test_ids.py +++ b/test/general/test_ids.py @@ -47,13 +47,39 @@ class TestIDs(unittest.TestCase): """Test that a game doesn't have item id overlap within its own datapackage""" for gamename, world_type in AutoWorldRegister.world_types.items(): with self.subTest(game=gamename): - self.assertEqual(len(world_type.item_id_to_name), len(world_type.item_name_to_id)) + len_item_id_to_name = len(world_type.item_id_to_name) + len_item_name_to_id = len(world_type.item_name_to_id) + + if len_item_id_to_name != len_item_name_to_id: + self.assertCountEqual( + world_type.item_id_to_name.values(), + world_type.item_name_to_id.keys(), + "\nThese items have overlapping ids with other items in its own world") + self.assertCountEqual( + world_type.item_id_to_name.keys(), + world_type.item_name_to_id.values(), + "\nThese items have overlapping names with other items in its own world") + + self.assertEqual(len_item_id_to_name, len_item_name_to_id) def test_duplicate_location_ids(self): """Test that a game doesn't have location id overlap within its own datapackage""" for gamename, world_type in AutoWorldRegister.world_types.items(): with self.subTest(game=gamename): - self.assertEqual(len(world_type.location_id_to_name), len(world_type.location_name_to_id)) + len_location_id_to_name = len(world_type.location_id_to_name) + len_location_name_to_id = len(world_type.location_name_to_id) + + if len_location_id_to_name != len_location_name_to_id: + self.assertCountEqual( + world_type.location_id_to_name.values(), + world_type.location_name_to_id.keys(), + "\nThese locations have overlapping ids with other locations in its own world") + self.assertCountEqual( + world_type.location_id_to_name.keys(), + world_type.location_name_to_id.values(), + "\nThese locations have overlapping names with other locations in its own world") + + self.assertEqual(len_location_id_to_name, len_location_name_to_id) def test_postgen_datapackage(self): """Generates a solo multiworld and checks that the datapackage is still valid""" From f288e3469c9fbc1a5a96fd5fdf7574fd0c58aa10 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:55:48 +0200 Subject: [PATCH 39/46] Core: Add a function docstring to roll_settings to hopefully prevent the weights fiasco from being repeated (#3388) * Add an option docstring to roll_settings to hopefully prevent the weights fiasco from being repeated * Update Generate.py * Update Generate.py --- Generate.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Generate.py b/Generate.py index 82386644e7..5b5219841d 100644 --- a/Generate.py +++ b/Generate.py @@ -456,6 +456,14 @@ def handle_option(ret: argparse.Namespace, game_weights: dict, option_key: str, def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.bosses): + """ + Roll options from specified weights, usually originating from a .yaml options file. + + Important note: + The same weights dict is shared between all slots using the same yaml (e.g. generic weights file for filler slots). + This means it should never be modified without making a deepcopy first. + """ + from worlds import AutoWorldRegister if "linked_options" in weights: From e52d8b4dbdbf72050046f98bc8b0fb228b52c7f8 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:56:05 +0200 Subject: [PATCH 40/46] The Witness: Remove first-stage requirements of progressive items from the logic files (#4257) * Remove extraneous symbol requirements * Some missed Full Dots cases * Bruh * merge error * merge error 2 --- worlds/witness/data/WitnessLogic.txt | 162 ++++---- worlds/witness/data/WitnessLogicExpert.txt | 400 ++++++++++---------- worlds/witness/data/WitnessLogicVanilla.txt | 116 +++--- worlds/witness/data/WitnessLogicVariety.txt | 252 ++++++------ 4 files changed, 465 insertions(+), 465 deletions(-) diff --git a/worlds/witness/data/WitnessLogic.txt b/worlds/witness/data/WitnessLogic.txt index edc45222b5..3eea52b85e 100644 --- a/worlds/witness/data/WitnessLogic.txt +++ b/worlds/witness/data/WitnessLogic.txt @@ -52,11 +52,11 @@ Outside Tutorial Vault (Outside Tutorial): 158651 - 0x03481 (Vault Box) - True - True Outside Tutorial Path To Outpost (Outside Tutorial) - Outside Tutorial Outpost - 0x0A170: -158011 - 0x0A171 (Outpost Entry Panel) - True - Dots & Full Dots +158011 - 0x0A171 (Outpost Entry Panel) - True - Full Dots Door - 0x0A170 (Outpost Entry) - 0x0A171 Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: -158012 - 0x04CA4 (Outpost Exit Panel) - True - Dots & Black/White Squares & Full Dots +158012 - 0x04CA4 (Outpost Exit Panel) - True - Black/White Squares & Full Dots Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Triangles @@ -136,12 +136,12 @@ Door - 0x18269 (Upper) - 0x1C349 159000 - 0x0332B (Glass Factory Black Line Reflection EP) - True - True Symmetry Island Upper (Symmetry Island): -158065 - 0x00A52 (Laser Yellow 1) - True - Symmetry & Colored Dots -158066 - 0x00A57 (Laser Yellow 2) - 0x00A52 - Symmetry & Colored Dots -158067 - 0x00A5B (Laser Yellow 3) - 0x00A57 - Symmetry & Colored Dots -158068 - 0x00A61 (Laser Blue 1) - 0x00A52 - Symmetry & Colored Dots -158069 - 0x00A64 (Laser Blue 2) - 0x00A61 & 0x00A57 - Symmetry & Colored Dots -158070 - 0x00A68 (Laser Blue 3) - 0x00A64 & 0x00A5B - Symmetry & Colored Dots +158065 - 0x00A52 (Laser Yellow 1) - True - Colored Dots +158066 - 0x00A57 (Laser Yellow 2) - 0x00A52 - Colored Dots +158067 - 0x00A5B (Laser Yellow 3) - 0x00A57 - Colored Dots +158068 - 0x00A61 (Laser Blue 1) - 0x00A52 - Colored Dots +158069 - 0x00A64 (Laser Blue 2) - 0x00A61 & 0x00A57 - Colored Dots +158070 - 0x00A68 (Laser Blue 3) - 0x00A64 & 0x00A5B - Colored Dots 158700 - 0x0360D (Laser Panel) - 0x00A68 - True Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True @@ -157,7 +157,7 @@ Desert Obelisk (Desert) - Entry - True: 159709 - 0x00359 (Obelisk) - True - True Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: -158652 - 0x0CC7B (Vault Panel) - True - Dots & Shapers & Rotated Shapers & Negative Shapers & Full Dots +158652 - 0x0CC7B (Vault Panel) - True - Shapers & Rotated Shapers & Negative Shapers & Full Dots Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Triangles 158076 - 0x00698 (Surface 1) - True - True @@ -335,13 +335,13 @@ Quarry Boathouse Upper Middle (Quarry Boathouse) - Quarry Boathouse Upper Back - Quarry Boathouse Upper Back (Quarry Boathouse) - Quarry Boathouse Upper Middle - 0x3865F: 158155 - 0x38663 (Second Barrier Panel) - True - True Door - 0x3865F (Second Barrier) - 0x38663 -158156 - 0x021B5 (Back First Row 1) - True - Stars & Stars + Same Colored Symbol & Eraser -158157 - 0x021B6 (Back First Row 2) - 0x021B5 - Stars & Stars + Same Colored Symbol & Eraser -158158 - 0x021B7 (Back First Row 3) - 0x021B6 - Stars & Stars + Same Colored Symbol & Eraser -158159 - 0x021BB (Back First Row 4) - 0x021B7 - Stars & Stars + Same Colored Symbol & Eraser -158160 - 0x09DB5 (Back First Row 5) - 0x021BB - Stars & Stars + Same Colored Symbol & Eraser -158161 - 0x09DB1 (Back First Row 6) - 0x09DB5 - Stars & Stars + Same Colored Symbol & Eraser -158162 - 0x3C124 (Back First Row 7) - 0x09DB1 - Stars & Stars + Same Colored Symbol & Eraser +158156 - 0x021B5 (Back First Row 1) - True - Stars + Same Colored Symbol & Eraser +158157 - 0x021B6 (Back First Row 2) - 0x021B5 - Stars + Same Colored Symbol & Eraser +158158 - 0x021B7 (Back First Row 3) - 0x021B6 - Stars + Same Colored Symbol & Eraser +158159 - 0x021BB (Back First Row 4) - 0x021B7 - Stars + Same Colored Symbol & Eraser +158160 - 0x09DB5 (Back First Row 5) - 0x021BB - Stars + Same Colored Symbol & Eraser +158161 - 0x09DB1 (Back First Row 6) - 0x09DB5 - Stars + Same Colored Symbol & Eraser +158162 - 0x3C124 (Back First Row 7) - 0x09DB1 - Stars + Same Colored Symbol & Eraser 158163 - 0x09DB3 (Back First Row 8) - 0x3C124 - Stars & Eraser & Shapers 158164 - 0x09DB4 (Back First Row 9) - 0x09DB3 - Stars & Eraser & Shapers 158165 - 0x275FA (Hook Control) - True - Shapers & Eraser @@ -421,7 +421,7 @@ Door - 0x01A0E (Hedge Maze 4 Exit) - 0x01A0F Keep 2nd Pressure Plate (Keep) - Keep 3rd Pressure Plate - True: 158199 - 0x0A3B9 (Reset Pressure Plates 2) - True - True -158200 - 0x01BE9 (Pressure Plates 2) - 0x0A3B9 - Stars & Stars + Same Colored Symbol & Black/White Squares +158200 - 0x01BE9 (Pressure Plates 2) - 0x0A3B9 - Stars + Same Colored Symbol & Black/White Squares Door - 0x01BEA (Pressure Plates 2 Exit) - 0x01BE9 Keep 3rd Pressure Plate (Keep) - Keep 4th Pressure Plate - 0x01CD5: @@ -441,7 +441,7 @@ Keep Tower (Keep) - Keep - 0x04F8F: 158206 - 0x0361B (Tower Shortcut Panel) - True - True Door - 0x04F8F (Tower Shortcut) - 0x0361B 158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Dots +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Black/White Squares & Colored Squares & Stars + Same Colored Symbol & Dots Laser - 0x014BB (Laser) - 0x0360E | 0x03317 159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True 159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True @@ -537,11 +537,11 @@ Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 158221 - 0x28AE3 (Vines) - 0x18590 - True 158222 - 0x28938 (Apple Tree) - 0x28AE3 - True 158223 - 0x079DF (Triple Exit) - 0x28938 - True -158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Rotated Shapers & Dots & Full Dots -158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Shapers & Dots & Full Dots -158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Shapers & Rotated Shapers & Dots & Full Dots -158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Rotated Shapers & Dots & Full Dots -158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Rotated Shapers & Dots & Full Dots +158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Rotated Shapers & Full Dots +158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Shapers & Full Dots +158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Shapers & Rotated Shapers & Full Dots +158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Rotated Shapers & Full Dots +158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Rotated Shapers & Full Dots Door - 0x034F5 (Wooden Roof Stairs) - 0x28AC1 158225 - 0x28998 (RGB House Entry Panel) - True - Stars & Rotated Shapers Door - 0x28A61 (RGB House Entry) - 0x28998 @@ -575,7 +575,7 @@ Town Red Rooftop (Town): 158224 - 0x28B39 (Tall Hexagonal) - 0x079DF - True Town Wooden Rooftop (Town): -158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Rotated Shapers & Dots & Eraser & Full Dots +158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Rotated Shapers & Eraser & Full Dots Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True @@ -934,29 +934,29 @@ Treehouse Second Purple Bridge (Treehouse) - Treehouse Left Orange Bridge - 0x17 158368 - 0x17DC6 (Second Purple Bridge 7) - 0x17D91 - Stars & Colored Squares Treehouse Left Orange Bridge (Treehouse) - Treehouse Laser Room Front Platform - 0x17DDB - Treehouse Laser Room Back Platform - 0x17DDB - Treehouse Burned House - 0x17DDB: -158376 - 0x17DB3 (Left Orange Bridge 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol -158377 - 0x17DB5 (Left Orange Bridge 2) - 0x17DB3 - Stars & Black/White Squares & Stars + Same Colored Symbol -158378 - 0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Stars & Black/White Squares & Stars + Same Colored Symbol -158379 - 0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Stars & Black/White Squares & Stars + Same Colored Symbol -158380 - 0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Stars & Black/White Squares & Colored Squares & Stars + Same Colored Symbol -158381 - 0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Stars & Black/White Squares & Colored Squares & Stars + Same Colored Symbol -158382 - 0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Stars & Black/White Squares & Colored Squares & Stars + Same Colored Symbol -158383 - 0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Stars & Colored Squares & Stars + Same Colored Symbol -158384 - 0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Stars & Colored Squares & Stars + Same Colored Symbol -158385 - 0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Stars & Colored Squares & Stars + Same Colored Symbol -158386 - 0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Stars & Colored Squares & Stars + Same Colored Symbol -158387 - 0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Stars & Black/White Squares & Stars + Same Colored Symbol -158388 - 0x17DAE (Left Orange Bridge 13) - 0x17DEC - Stars & Black/White Squares & Stars + Same Colored Symbol -158389 - 0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Stars & Black/White Squares & Stars + Same Colored Symbol -158390 - 0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Stars & Black/White Squares & Stars + Same Colored Symbol +158376 - 0x17DB3 (Left Orange Bridge 1) - True - Black/White Squares & Stars + Same Colored Symbol +158377 - 0x17DB5 (Left Orange Bridge 2) - 0x17DB3 - Black/White Squares & Stars + Same Colored Symbol +158378 - 0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Black/White Squares & Stars + Same Colored Symbol +158379 - 0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Black/White Squares & Stars + Same Colored Symbol +158380 - 0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Black/White Squares & Colored Squares & Stars + Same Colored Symbol +158381 - 0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Black/White Squares & Colored Squares & Stars + Same Colored Symbol +158382 - 0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Black/White Squares & Colored Squares & Stars + Same Colored Symbol +158383 - 0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Colored Squares & Stars + Same Colored Symbol +158384 - 0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Colored Squares & Stars + Same Colored Symbol +158385 - 0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Colored Squares & Stars + Same Colored Symbol +158386 - 0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Colored Squares & Stars + Same Colored Symbol +158387 - 0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Black/White Squares & Stars + Same Colored Symbol +158388 - 0x17DAE (Left Orange Bridge 13) - 0x17DEC - Black/White Squares & Stars + Same Colored Symbol +158389 - 0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Black/White Squares & Stars + Same Colored Symbol +158390 - 0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Black/White Squares & Stars + Same Colored Symbol Treehouse Green Bridge (Treehouse) - Treehouse Green Bridge Front House - 0x17E61 - Treehouse Green Bridge Left House - 0x17E61: 158369 - 0x17E3C (Green Bridge 1) - True - Stars & Shapers 158370 - 0x17E4D (Green Bridge 2) - 0x17E3C - Stars & Shapers 158371 - 0x17E4F (Green Bridge 3) - 0x17E4D - Stars & Shapers & Rotated Shapers 158372 - 0x17E52 (Green Bridge 4 & Directional) - 0x17E4F - Stars & Rotated Shapers -158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Stars & Shapers & Stars + Same Colored Symbol -158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol +158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Shapers & Stars + Same Colored Symbol +158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Shapers & Negative Shapers & Stars + Same Colored Symbol 158375 - 0x17E61 (Green Bridge 7) - 0x17E5F - Stars & Shapers & Rotated Shapers Treehouse Green Bridge Front House (Treehouse): @@ -1005,7 +1005,7 @@ Mountainside Vault (Mountainside): Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34: 158405 - 0x0042D (River Shape) - True - True 158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True -158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol +158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Black/White Squares & Stars + Same Colored Symbol 158800 - 0xFFF00 (Box Long) - 11 Lasers + Redirect & 0x17C34 - True 159300 - 0x001A3 (River Shape EP) - True - True 159320 - 0x3370E (Arch Black EP) - True - True @@ -1023,11 +1023,11 @@ Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneW 158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Dots 158412 - 0x09E69 (Right Row 4) - 0x09E72 - Black/White Squares & Dots 158413 - 0x09E7B (Right Row 5) - 0x09E69 - Black/White Squares & Dots -158414 - 0x09E73 (Left Row 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol -158415 - 0x09E75 (Left Row 2) - 0x09E73 - Stars & Black/White Squares & Stars + Same Colored Symbol +158414 - 0x09E73 (Left Row 1) - True - Black/White Squares & Stars + Same Colored Symbol +158415 - 0x09E75 (Left Row 2) - 0x09E73 - Black/White Squares & Stars + Same Colored Symbol 158416 - 0x09E78 (Left Row 3) - 0x09E75 - Shapers 158417 - 0x09E79 (Left Row 4) - 0x09E78 - Shapers & Rotated Shapers -158418 - 0x09E6C (Left Row 5) - 0x09E79 - Stars & Black/White Squares & Stars + Same Colored Symbol +158418 - 0x09E6C (Left Row 5) - 0x09E79 - Black/White Squares & Stars + Same Colored Symbol 158419 - 0x09E6F (Left Row 6) - 0x09E6C - Stars & Rotated Shapers & Shapers 158420 - 0x09E6B (Left Row 7) - 0x09E6F - Stars & Dots 158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers @@ -1044,10 +1044,10 @@ Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 Above The Abyss - True - Mountain Pink Bridge EP - TrueOneWay: -158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol -158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Colored Squares & Stars + Same Colored Symbol -158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol -158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers +158426 - 0x09FD3 (Near Row 1) - True - Colored Squares & Stars + Same Colored Symbol +158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Colored Squares & Stars + Same Colored Symbol +158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Colored Squares & Stars + Same Colored Symbol +158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Colored Squares & Stars + Same Colored Symbol & Shapers 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Symmetry & Colored Dots Door - 0x09FFB (Staircase Near) - 0x09FD8 @@ -1055,19 +1055,19 @@ Mountain Floor 2 Above The Abyss (Mountain Floor 2) - Mountain Floor 2 Elevator Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 Mountain Floor 2 Light Bridge Room Near (Mountain Floor 2): -158431 - 0x09E86 (Light Bridge Controller Near) - True - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser +158431 - 0x09E86 (Light Bridge Controller Near) - True - Stars + Same Colored Symbol & Rotated Shapers & Eraser Mountain Floor 2 Beyond Bridge (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Far - 0x09E07 - Mountain Pink Bridge EP - TrueOneWay - Mountain Floor 2 - 0x09ED8: 158432 - 0x09FCC (Far Row 1) - True - Dots 158433 - 0x09FCE (Far Row 2) - 0x09FCC - Black/White Squares 158434 - 0x09FCF (Far Row 3) - 0x09FCE - Stars 158435 - 0x09FD0 (Far Row 4) - 0x09FCF - Rotated Shapers -158436 - 0x09FD1 (Far Row 5) - 0x09FD0 - Stars & Colored Squares & Stars + Same Colored Symbol +158436 - 0x09FD1 (Far Row 5) - 0x09FD0 - Colored Squares & Stars + Same Colored Symbol 158437 - 0x09FD2 (Far Row 6) - 0x09FD1 - Shapers Door - 0x09E07 (Staircase Far) - 0x09FD2 Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2): -158438 - 0x09ED8 (Light Bridge Controller Far) - True - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser +158438 - 0x09ED8 (Light Bridge Controller Far) - True - Stars + Same Colored Symbol & Rotated Shapers & Eraser Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay: 158613 - 0x17F93 (Elevator Discard) - True - Triangles @@ -1095,7 +1095,7 @@ Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: 158522 - 0x0383A (Right Pillar 1) - True - Stars 158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Full Dots 158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry 158526 - 0x0383D (Left Pillar 1) - True - Dots 158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares @@ -1127,16 +1127,16 @@ Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x01 158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares 158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares 158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots -158454 - 0x00190 (Blue Tunnel Right First 1) - True - Dots & Triangles & Full Dots -158455 - 0x00558 (Blue Tunnel Right First 2) - 0x00190 - Dots & Triangles & Full Dots -158456 - 0x00567 (Blue Tunnel Right First 3) - 0x00558 - Dots & Triangles & Full Dots -158457 - 0x006FE (Blue Tunnel Right First 4) - 0x00567 - Dots & Triangles & Full Dots +158454 - 0x00190 (Blue Tunnel Right First 1) - True - Triangles & Full Dots +158455 - 0x00558 (Blue Tunnel Right First 2) - 0x00190 - Triangles & Full Dots +158456 - 0x00567 (Blue Tunnel Right First 3) - 0x00558 - Triangles & Full Dots +158457 - 0x006FE (Blue Tunnel Right First 4) - 0x00567 - Triangles & Full Dots 158458 - 0x01A0D (Blue Tunnel Left First 1) - True - Symmetry & Triangles 158459 - 0x008B8 (Blue Tunnel Left Second 1) - True - Black/White Squares & Triangles 158460 - 0x00973 (Blue Tunnel Left Second 2) - 0x008B8 - Stars & Triangles -158461 - 0x0097B (Blue Tunnel Left Second 3) - 0x00973 - Stars & Triangles & Stars + Same Colored Symbol -158462 - 0x0097D (Blue Tunnel Left Second 4) - 0x0097B - Stars & Black/White Squares & Stars + Same Colored Symbol & Triangles -158463 - 0x0097E (Blue Tunnel Left Second 5) - 0x0097D - Stars & Black/White Squares & Stars + Same Colored Symbol & Colored Squares +158461 - 0x0097B (Blue Tunnel Left Second 3) - 0x00973 - Triangles & Stars + Same Colored Symbol +158462 - 0x0097D (Blue Tunnel Left Second 4) - 0x0097B - Black/White Squares & Stars + Same Colored Symbol & Triangles +158463 - 0x0097E (Blue Tunnel Left Second 5) - 0x0097D - Black/White Squares & Stars + Same Colored Symbol & Colored Squares 158464 - 0x00994 (Blue Tunnel Right Second 1) - True - Rotated Shapers & Triangles 158465 - 0x334D5 (Blue Tunnel Right Second 2) - 0x00994 - Rotated Shapers & Triangles 158466 - 0x00995 (Blue Tunnel Right Second 3) - 0x334D5 - Rotated Shapers & Triangles @@ -1146,40 +1146,40 @@ Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x01 158470 - 0x018A0 (Blue Tunnel Right Third 1) - True - Shapers & Symmetry 158471 - 0x00A72 (Blue Tunnel Left Fourth 1) - True - Shapers & Negative Shapers 158472 - 0x32962 (First Floor Left) - True - Rotated Shapers -158473 - 0x32966 (First Floor Grounded) - True - Stars & Black/White Squares & Stars + Same Colored Symbol +158473 - 0x32966 (First Floor Grounded) - True - Black/White Squares & Stars + Same Colored Symbol 158474 - 0x01A31 (First Floor Middle) - True - Colored Squares -158475 - 0x00B71 (First Floor Right) - True - Colored Squares & Stars & Stars + Same Colored Symbol & Eraser +158475 - 0x00B71 (First Floor Right) - True - Colored Squares & Stars + Same Colored Symbol & Eraser 158478 - 0x288EA (First Wooden Beam) - True - Shapers 158479 - 0x288FC (Second Wooden Beam) - True - Black/White Squares & Shapers & Rotated Shapers 158480 - 0x289E7 (Third Wooden Beam) - True - Stars & Black/White Squares 158481 - 0x288AA (Fourth Wooden Beam) - True - Stars & Shapers -158482 - 0x17FB9 (Left Upstairs Single) - True - Shapers & Dots & Negative Shapers & Full Dots -158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Dots & Full Dots -158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Stars & Dots & Full Dots -158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Dots & Black/White Squares & Stars + Same Colored Symbol & Stars & Full Dots -158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Shapers & Dots & Full Dots -158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Rotated Shapers & Dots & Full Dots -158488 - 0x0008F (Right Upstairs Left Row 1) - True - Dots & Invisible Dots -158489 - 0x0006B (Right Upstairs Left Row 2) - 0x0008F - Dots & Invisible Dots -158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Dots & Invisible Dots -158491 - 0x0008C (Right Upstairs Left Row 4) - 0x0008B - Dots & Invisible Dots -158492 - 0x0008A (Right Upstairs Left Row 5) - 0x0008C - Dots & Invisible Dots -158493 - 0x00089 (Right Upstairs Left Row 6) - 0x0008A - Dots & Invisible Dots -158494 - 0x0006A (Right Upstairs Left Row 7) - 0x00089 - Dots & Invisible Dots -158495 - 0x0006C (Right Upstairs Left Row 8) - 0x0006A - Dots & Invisible Dots -158496 - 0x00027 (Right Upstairs Right Row 1) - True - Dots & Invisible Dots & Symmetry -158497 - 0x00028 (Right Upstairs Right Row 2) - 0x00027 - Dots & Invisible Dots & Symmetry -158498 - 0x00029 (Right Upstairs Right Row 3) - 0x00028 - Dots & Invisible Dots & Symmetry +158482 - 0x17FB9 (Left Upstairs Single) - True - Shapers & Negative Shapers & Full Dots +158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Full Dots +158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Stars & Full Dots +158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Black/White Squares & Stars + Same Colored Symbol & Full Dots +158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Shapers & Full Dots +158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Rotated Shapers & Full Dots +158488 - 0x0008F (Right Upstairs Left Row 1) - True - Dots +158489 - 0x0006B (Right Upstairs Left Row 2) - 0x0008F - Dots +158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Dots +158491 - 0x0008C (Right Upstairs Left Row 4) - 0x0008B - Dots +158492 - 0x0008A (Right Upstairs Left Row 5) - 0x0008C - Dots +158493 - 0x00089 (Right Upstairs Left Row 6) - 0x0008A - Dots +158494 - 0x0006A (Right Upstairs Left Row 7) - 0x00089 - Dots +158495 - 0x0006C (Right Upstairs Left Row 8) - 0x0006A - Dots +158496 - 0x00027 (Right Upstairs Right Row 1) - True - Dots & Symmetry +158497 - 0x00028 (Right Upstairs Right Row 2) - 0x00027 - Dots & Symmetry +158498 - 0x00029 (Right Upstairs Right Row 3) - 0x00028 - Dots & Symmetry 158476 - 0x09DD5 (Lone Pillar) - True - Triangles Door - 0x019A5 (Pillar Door) - 0x09DD5 -158449 - 0x021D7 (Mountain Shortcut Panel) - True - Triangles & Stars & Stars + Same Colored Symbol +158449 - 0x021D7 (Mountain Shortcut Panel) - True - Triangles & Stars + Same Colored Symbol Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7 158450 - 0x17CF2 (Swamp Shortcut Panel) - True - Triangles Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True Caves Path to Challenge (Caves) - Challenge - 0x0A19A: -158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Shapers & Stars + Same Colored Symbol +158477 - 0x0A16E (Challenge Entry Panel) - True - Shapers & Stars + Same Colored Symbol Door - 0x0A19A (Challenge Entry) - 0x0A16E ==Challenge== diff --git a/worlds/witness/data/WitnessLogicExpert.txt b/worlds/witness/data/WitnessLogicExpert.txt index 23521dddeb..936001c243 100644 --- a/worlds/witness/data/WitnessLogicExpert.txt +++ b/worlds/witness/data/WitnessLogicExpert.txt @@ -13,8 +13,8 @@ Tutorial (Tutorial) - Outside Tutorial - True: 158002 - 0x00293 (Front Center) - True - Dots 158003 - 0x00295 (Center Left) - 0x00293 - Dots 158004 - 0x002C2 (Front Left) - 0x00295 - Dots -158005 - 0x0A3B5 (Back Left) - True - Dots & Full Dots -158006 - 0x0A3B2 (Back Right) - True - Dots & Full Dots +158005 - 0x0A3B5 (Back Left) - True - Full Dots +158006 - 0x0A3B2 (Back Right) - True - Full Dots 158007 - 0x03629 (Gate Open) - 0x002C2 - Symmetry & Dots 158008 - 0x03505 (Gate Close) - 0x2FAF6 & 0x03629 - True 158009 - 0x0C335 (Pillar) - True - Triangles @@ -26,22 +26,22 @@ Tutorial (Tutorial) - Outside Tutorial - True: ==Tutorial (Outside)== Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0: -158650 - 0x033D4 (Vault Panel) - True - Dots & Full Dots & Squares & Black/White Squares +158650 - 0x033D4 (Vault Panel) - True - Full Dots & Black/White Squares Door - 0x033D0 (Vault Door) - 0x033D4 -158013 - 0x0005D (Shed Row 1) - True - Dots & Full Dots -158014 - 0x0005E (Shed Row 2) - 0x0005D - Dots & Full Dots -158015 - 0x0005F (Shed Row 3) - 0x0005E - Dots & Full Dots -158016 - 0x00060 (Shed Row 4) - 0x0005F - Dots & Full Dots -158017 - 0x00061 (Shed Row 5) - 0x00060 - Dots & Full Dots -158018 - 0x018AF (Tree Row 1) - True - Squares & Black/White Squares -158019 - 0x0001B (Tree Row 2) - 0x018AF - Squares & Black/White Squares -158020 - 0x012C9 (Tree Row 3) - 0x0001B - Squares & Black/White Squares -158021 - 0x0001C (Tree Row 4) - 0x012C9 - Squares & Black/White Squares & Dots -158022 - 0x0001D (Tree Row 5) - 0x0001C - Squares & Black/White Squares & Dots -158023 - 0x0001E (Tree Row 6) - 0x0001D - Squares & Black/White Squares & Dots -158024 - 0x0001F (Tree Row 7) - 0x0001E - Squares & Black/White Squares & Dots & Full Dots -158025 - 0x00020 (Tree Row 8) - 0x0001F - Squares & Black/White Squares & Dots & Full Dots -158026 - 0x00021 (Tree Row 9) - 0x00020 - Squares & Black/White Squares & Dots & Full Dots +158013 - 0x0005D (Shed Row 1) - True - Full Dots +158014 - 0x0005E (Shed Row 2) - 0x0005D - Full Dots +158015 - 0x0005F (Shed Row 3) - 0x0005E - Full Dots +158016 - 0x00060 (Shed Row 4) - 0x0005F - Full Dots +158017 - 0x00061 (Shed Row 5) - 0x00060 - Full Dots +158018 - 0x018AF (Tree Row 1) - True - Black/White Squares +158019 - 0x0001B (Tree Row 2) - 0x018AF - Black/White Squares +158020 - 0x012C9 (Tree Row 3) - 0x0001B - Black/White Squares +158021 - 0x0001C (Tree Row 4) - 0x012C9 - Black/White Squares & Dots +158022 - 0x0001D (Tree Row 5) - 0x0001C - Black/White Squares & Dots +158023 - 0x0001E (Tree Row 6) - 0x0001D - Black/White Squares & Dots +158024 - 0x0001F (Tree Row 7) - 0x0001E - Black/White Squares & Full Dots +158025 - 0x00020 (Tree Row 8) - 0x0001F - Black/White Squares & Full Dots +158026 - 0x00021 (Tree Row 9) - 0x00020 - Black/White Squares & Full Dots Door - 0x03BA2 (Outpost Path) - 0x0A3B5 159511 - 0x03D06 (Garden EP) - True - True 159514 - 0x28A2F (Town Sewer EP) - True - True @@ -52,11 +52,11 @@ Outside Tutorial Vault (Outside Tutorial): 158651 - 0x03481 (Vault Box) - True - True Outside Tutorial Path To Outpost (Outside Tutorial) - Outside Tutorial Outpost - 0x0A170: -158011 - 0x0A171 (Outpost Entry Panel) - True - Dots & Full Dots & Triangles +158011 - 0x0A171 (Outpost Entry Panel) - True - Full Dots & Triangles Door - 0x0A170 (Outpost Entry) - 0x0A171 Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: -158012 - 0x04CA4 (Outpost Exit Panel) - True - Dots & Full Dots & Shapers & Rotated Shapers +158012 - 0x04CA4 (Outpost Exit Panel) - True - Full Dots & Shapers & Rotated Shapers Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Arrows @@ -136,12 +136,12 @@ Door - 0x18269 (Upper) - 0x1C349 159000 - 0x0332B (Glass Factory Black Line Reflection EP) - True - True Symmetry Island Upper (Symmetry Island): -158065 - 0x00A52 (Laser Yellow 1) - True - Symmetry & Colored Dots -158066 - 0x00A57 (Laser Yellow 2) - 0x00A52 - Symmetry & Colored Dots -158067 - 0x00A5B (Laser Yellow 3) - 0x00A57 - Symmetry & Colored Dots -158068 - 0x00A61 (Laser Blue 1) - 0x00A52 - Symmetry & Colored Dots -158069 - 0x00A64 (Laser Blue 2) - 0x00A61 & 0x00A57 - Symmetry & Colored Dots -158070 - 0x00A68 (Laser Blue 3) - 0x00A64 & 0x00A5B - Symmetry & Colored Dots +158065 - 0x00A52 (Laser Yellow 1) - True - Colored Dots +158066 - 0x00A57 (Laser Yellow 2) - 0x00A52 - Colored Dots +158067 - 0x00A5B (Laser Yellow 3) - 0x00A57 - Colored Dots +158068 - 0x00A61 (Laser Blue 1) - 0x00A52 - Colored Dots +158069 - 0x00A64 (Laser Blue 2) - 0x00A61 & 0x00A57 - Colored Dots +158070 - 0x00A68 (Laser Blue 3) - 0x00A64 & 0x00A5B - Colored Dots 158700 - 0x0360D (Laser Panel) - 0x00A68 - True Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True @@ -157,7 +157,7 @@ Desert Obelisk (Desert) - Entry - True: 159709 - 0x00359 (Obelisk) - True - True Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: -158652 - 0x0CC7B (Vault Panel) - True - Dots & Full Dots & Stars & Stars + Same Colored Symbol & Eraser & Triangles & Shapers & Negative Shapers & Colored Squares +158652 - 0x0CC7B (Vault Panel) - True - Full Dots & Stars + Same Colored Symbol & Eraser & Triangles & Shapers & Negative Shapers & Colored Squares Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Arrows 158076 - 0x00698 (Surface 1) - True - True @@ -249,9 +249,9 @@ Quarry Obelisk (Quarry) - Entry - True: 159749 - 0x22073 (Obelisk) - True - True Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01: -158118 - 0x09E57 (Entry 1 Panel) - True - Squares & Black/White Squares & Triangles +158118 - 0x09E57 (Entry 1 Panel) - True - Black/White Squares & Triangles 158603 - 0x17CF0 (Discard) - True - Arrows -158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Triangles & Stars & Stars + Same Colored Symbol +158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Triangles & Stars + Same Colored Symbol Laser - 0x01539 (Laser) - 0x03612 Door - 0x09D6F (Entry 1) - 0x09E57 159404 - 0x28A4A (Shore EP) - True - True @@ -270,7 +270,7 @@ Door - 0x17C07 (Entry 2) - 0x17C09 Quarry (Quarry) - Quarry Stoneworks Ground Floor - 0x02010: 159802 - 0xFFD01 (Inside Reached Independently) - True - True -158121 - 0x01E5A (Stoneworks Entry Left Panel) - True - Squares & Black/White Squares & Stars & Stars + Same Colored Symbol +158121 - 0x01E5A (Stoneworks Entry Left Panel) - True - Black/White Squares & Stars + Same Colored Symbol 158122 - 0x01E59 (Stoneworks Entry Right Panel) - True - Triangles Door - 0x02010 (Stoneworks Entry) - 0x01E59 & 0x01E5A @@ -295,23 +295,23 @@ Quarry Stoneworks Lift (Quarry Stoneworks) - Quarry Stoneworks Middle Floor - 0x Quarry Stoneworks Upper Floor (Quarry Stoneworks) - Quarry Stoneworks Lift - 0x03675 - Quarry Stoneworks Ground Floor - 0x0368A: 158132 - 0x03676 (Upper Ramp Control) - True - Dots & Eraser 158133 - 0x03675 (Upper Lift Control) - True - Dots & Eraser -158134 - 0x00557 (Upper Row 1) - True - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol -158135 - 0x005F1 (Upper Row 2) - 0x00557 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol -158136 - 0x00620 (Upper Row 3) - 0x005F1 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol -158137 - 0x009F5 (Upper Row 4) - 0x00620 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol -158138 - 0x0146C (Upper Row 5) - 0x009F5 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol -158139 - 0x3C12D (Upper Row 6) - 0x0146C - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol -158140 - 0x03686 (Upper Row 7) - 0x3C12D - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol -158141 - 0x014E9 (Upper Row 8) - 0x03686 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol -158142 - 0x03677 (Stairs Panel) - True - Squares & Colored Squares & Eraser +158134 - 0x00557 (Upper Row 1) - True - Colored Squares & Eraser & Stars + Same Colored Symbol +158135 - 0x005F1 (Upper Row 2) - 0x00557 - Colored Squares & Eraser & Stars + Same Colored Symbol +158136 - 0x00620 (Upper Row 3) - 0x005F1 - Colored Squares & Eraser & Stars + Same Colored Symbol +158137 - 0x009F5 (Upper Row 4) - 0x00620 - Colored Squares & Eraser & Stars + Same Colored Symbol +158138 - 0x0146C (Upper Row 5) - 0x009F5 - Colored Squares & Eraser & Stars + Same Colored Symbol +158139 - 0x3C12D (Upper Row 6) - 0x0146C - Colored Squares & Eraser & Stars + Same Colored Symbol +158140 - 0x03686 (Upper Row 7) - 0x3C12D - Colored Squares & Eraser & Stars + Same Colored Symbol +158141 - 0x014E9 (Upper Row 8) - 0x03686 - Colored Squares & Eraser & Stars + Same Colored Symbol +158142 - 0x03677 (Stairs Panel) - True - Colored Squares & Eraser Door - 0x0368A (Stairs) - 0x03677 -158143 - 0x3C125 (Control Room Left) - 0x014E9 - Squares & Black/White Squares & Dots & Full Dots & Eraser -158144 - 0x0367C (Control Room Right) - 0x014E9 - Squares & Colored Squares & Triangles & Eraser & Stars & Stars + Same Colored Symbol +158143 - 0x3C125 (Control Room Left) - 0x014E9 - Black/White Squares & Full Dots & Eraser +158144 - 0x0367C (Control Room Right) - 0x014E9 - Colored Squares & Triangles & Eraser & Stars + Same Colored Symbol 159411 - 0x0069D (Ramp EP) - 0x03676 & 0x275FF - True 159413 - 0x00614 (Lift EP) - 0x275FF & 0x03675 - True Quarry Boathouse (Quarry Boathouse) - Quarry - True - Quarry Boathouse Upper Front - 0x03852 - Quarry Boathouse Behind Staircase - 0x2769B: -158146 - 0x034D4 (Intro Left) - True - Stars & Stars + Same Colored Symbol & Eraser +158146 - 0x034D4 (Intro Left) - True - Stars + Same Colored Symbol & Eraser 158147 - 0x021D5 (Intro Right) - True - Shapers & Eraser 158148 - 0x03852 (Ramp Height Control) - 0x034D4 & 0x021D5 - Rotated Shapers 158166 - 0x17CA6 (Boat Spawn) - True - Boat @@ -335,19 +335,19 @@ Quarry Boathouse Upper Middle (Quarry Boathouse) - Quarry Boathouse Upper Back - Quarry Boathouse Upper Back (Quarry Boathouse) - Quarry Boathouse Upper Middle - 0x3865F: 158155 - 0x38663 (Second Barrier Panel) - True - True Door - 0x3865F (Second Barrier) - 0x38663 -158156 - 0x021B5 (Back First Row 1) - True - Stars & Stars + Same Colored Symbol & Eraser -158157 - 0x021B6 (Back First Row 2) - 0x021B5 - Stars & Stars + Same Colored Symbol & Eraser -158158 - 0x021B7 (Back First Row 3) - 0x021B6 - Stars & Stars + Same Colored Symbol & Eraser -158159 - 0x021BB (Back First Row 4) - 0x021B7 - Stars & Stars + Same Colored Symbol & Eraser -158160 - 0x09DB5 (Back First Row 5) - 0x021BB - Stars & Stars + Same Colored Symbol & Eraser +158156 - 0x021B5 (Back First Row 1) - True - Stars + Same Colored Symbol & Eraser +158157 - 0x021B6 (Back First Row 2) - 0x021B5 - Stars + Same Colored Symbol & Eraser +158158 - 0x021B7 (Back First Row 3) - 0x021B6 - Stars + Same Colored Symbol & Eraser +158159 - 0x021BB (Back First Row 4) - 0x021B7 - Stars + Same Colored Symbol & Eraser +158160 - 0x09DB5 (Back First Row 5) - 0x021BB - Stars + Same Colored Symbol & Eraser 158161 - 0x09DB1 (Back First Row 6) - 0x09DB5 - Eraser & Shapers 158162 - 0x3C124 (Back First Row 7) - 0x09DB1 - Eraser & Shapers -158163 - 0x09DB3 (Back First Row 8) - 0x3C124 - Eraser & Shapers & Stars & Stars + Same Colored Symbol -158164 - 0x09DB4 (Back First Row 9) - 0x09DB3 - Eraser & Shapers & Stars & Stars + Same Colored Symbol +158163 - 0x09DB3 (Back First Row 8) - 0x3C124 - Eraser & Shapers & Stars + Same Colored Symbol +158164 - 0x09DB4 (Back First Row 9) - 0x09DB3 - Eraser & Shapers & Stars + Same Colored Symbol 158165 - 0x275FA (Hook Control) - True - Shapers & Eraser -158167 - 0x0A3CB (Back Second Row 1) - 0x09DB4 - Stars & Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol -158168 - 0x0A3CC (Back Second Row 2) - 0x0A3CB - Stars & Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol -158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol +158167 - 0x0A3CB (Back Second Row 1) - 0x09DB4 - Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol +158168 - 0x0A3CC (Back Second Row 2) - 0x0A3CB - Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol +158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol 159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True ==Shadows== @@ -400,7 +400,7 @@ Outside Keep (Keep) - Main Island - True: Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC: 158193 - 0x00139 (Hedge Maze 1) - True - True 158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True -158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Colored Squares & Triangles & Stars & Stars + Same Colored Symbol +158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Colored Squares & Triangles & Stars + Same Colored Symbol Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139 Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA @@ -421,7 +421,7 @@ Door - 0x01A0E (Hedge Maze 4 Exit) - 0x01A0F Keep 2nd Pressure Plate (Keep) - Keep 3rd Pressure Plate - True: 158199 - 0x0A3B9 (Reset Pressure Plates 2) - True - True -158200 - 0x01BE9 (Pressure Plates 2) - PP2 Weirdness - Stars & Stars + Same Colored Symbol & Squares & Black/White Squares & Shapers & Rotated Shapers +158200 - 0x01BE9 (Pressure Plates 2) - PP2 Weirdness - Stars + Same Colored Symbol & Black/White Squares & Shapers & Rotated Shapers Door - 0x01BEA (Pressure Plates 2 Exit) - 0x01BE9 Keep 3rd Pressure Plate (Keep) - Keep 4th Pressure Plate - 0x01CD5: @@ -431,7 +431,7 @@ Door - 0x01CD5 (Pressure Plates 3 Exit) - 0x01CD3 Keep 4th Pressure Plate (Keep) - Shadows - 0x09E3D - Keep Tower - 0x01D40: 158203 - 0x0A3AD (Reset Pressure Plates 4) - True - True -158204 - 0x01D3F (Pressure Plates 4) - 0x0A3AD - Shapers & Triangles & Stars & Stars + Same Colored Symbol +158204 - 0x01D3F (Pressure Plates 4) - 0x0A3AD - Shapers & Triangles & Stars + Same Colored Symbol Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F 158604 - 0x17D27 (Discard) - True - Arrows 158205 - 0x09E49 (Shadows Shortcut Panel) - True - True @@ -441,7 +441,7 @@ Keep Tower (Keep) - Keep - 0x04F8F: 158206 - 0x0361B (Tower Shortcut Panel) - True - True Door - 0x04F8F (Tower Shortcut) - 0x0361B 158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Rotated Shapers & Triangles & Stars & Stars + Same Colored Symbol & Colored Squares & Black/White Squares +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Rotated Shapers & Triangles & Stars + Same Colored Symbol & Colored Squares & Black/White Squares Laser - 0x014BB (Laser) - 0x0360E | 0x03317 159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True 159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True @@ -530,20 +530,20 @@ Town Obelisk (Town) - Entry - True: Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True: 158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat -158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Squares & Black/White Squares & Shapers & Triangles +158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Black/White Squares & Shapers & Triangles Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 158707 - 0x09F98 (Desert Laser Redirect Control) - True - True 158220 - 0x18590 (Transparent) - True - Symmetry 158221 - 0x28AE3 (Vines) - 0x18590 - True 158222 - 0x28938 (Apple Tree) - 0x28AE3 - True 158223 - 0x079DF (Triple Exit) - 0x28938 - True -158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Triangles & Dots & Full Dots -158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Triangles & Dots & Full Dots -158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Triangles & Dots & Full Dots -158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Triangles & Dots & Full Dots -158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Triangles & Dots & Full Dots +158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Triangles & Full Dots +158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Triangles & Full Dots +158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Triangles & Full Dots +158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Triangles & Full Dots +158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Triangles & Full Dots Door - 0x034F5 (Wooden Roof Stairs) - 0x28AC1 -158225 - 0x28998 (RGB House Entry Panel) - True - Stars & Rotated Shapers & Stars + Same Colored Symbol +158225 - 0x28998 (RGB House Entry Panel) - True - Rotated Shapers & Stars + Same Colored Symbol Door - 0x28A61 (RGB House Entry) - 0x28A0D 158226 - 0x28A0D (Church Entry Panel) - 0x28998 - Stars Door - 0x03BB0 (Church Entry) - 0x03C08 @@ -575,7 +575,7 @@ Town Red Rooftop (Town): 158224 - 0x28B39 (Tall Hexagonal) - 0x079DF - True Town Wooden Rooftop (Town): -158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Triangles & Dots & Full Dots & Eraser +158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Triangles & Full Dots & Eraser Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True @@ -587,8 +587,8 @@ Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B: Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3 Town RGB House Upstairs (Town RGB House Upstairs): -158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Squares & Colored Squares & Triangles -158245 - 0x03C0C (Left) - 0x334D8 - Squares & Colored Squares & Black/White Squares & Eraser +158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Colored Squares & Triangles +158245 - 0x03C0C (Left) - 0x334D8 - Colored Squares & Black/White Squares & Eraser 158246 - 0x03C08 (Right) - 0x334D8 & 0x03C0C - Symmetry & Dots & Colored Dots & Triangles Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799: @@ -620,7 +620,7 @@ Door - 0x1845B (Entry) - 0x17F5F Windmill Interior (Windmill) - Theater - 0x17F88: 158247 - 0x17D02 (Turn Control) - True - Dots -158248 - 0x17F89 (Theater Entry Panel) - True - Squares & Black/White Squares & Eraser & Triangles +158248 - 0x17F89 (Theater Entry Panel) - True - Black/White Squares & Eraser & Triangles Door - 0x17F88 (Theater Entry) - 0x17F89 Theater (Theater) - Town - 0x0A16D | 0x3CCDF: @@ -631,7 +631,7 @@ Theater (Theater) - Town - 0x0A16D | 0x3CCDF: 158660 - 0x03549 (Challenge Video) - 0x00815 & 0x0356B - True 158661 - 0x0354F (Shipwreck Video) - 0x00815 & 0x03535 - True 158662 - 0x03545 (Mountain Video) - 0x00815 & 0x03542 - True -158249 - 0x0A168 (Exit Left Panel) - True - Black/White Squares & Stars & Stars + Same Colored Symbol & Eraser +158249 - 0x0A168 (Exit Left Panel) - True - Black/White Squares & Stars + Same Colored Symbol & Eraser 158250 - 0x33AB2 (Exit Right Panel) - True - Eraser & Triangles & Shapers Door - 0x0A16D (Exit Left) - 0x0A168 Door - 0x3CCDF (Exit Right) - 0x33AB2 @@ -693,33 +693,33 @@ Jungle Vault (Jungle): ==Bunker== Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4: -158268 - 0x17C2E (Entry Panel) - True - Squares & Black/White Squares +158268 - 0x17C2E (Entry Panel) - True - Black/White Squares Door - 0x0C2A4 (Entry) - 0x17C2E Bunker (Bunker) - Bunker Glass Room - 0x17C79: -158269 - 0x09F7D (Intro Left 1) - True - Squares & Colored Squares -158270 - 0x09FDC (Intro Left 2) - 0x09F7D - Squares & Colored Squares & Black/White Squares -158271 - 0x09FF7 (Intro Left 3) - 0x09FDC - Squares & Colored Squares & Black/White Squares -158272 - 0x09F82 (Intro Left 4) - 0x09FF7 - Squares & Colored Squares & Black/White Squares -158273 - 0x09FF8 (Intro Left 5) - 0x09F82 - Squares & Colored Squares & Black/White Squares -158274 - 0x09D9F (Intro Back 1) - 0x09FF8 - Squares & Colored Squares & Black/White Squares -158275 - 0x09DA1 (Intro Back 2) - 0x09D9F - Squares & Colored Squares -158276 - 0x09DA2 (Intro Back 3) - 0x09DA1 - Squares & Colored Squares -158277 - 0x09DAF (Intro Back 4) - 0x09DA2 - Squares & Colored Squares +158269 - 0x09F7D (Intro Left 1) - True - Colored Squares +158270 - 0x09FDC (Intro Left 2) - 0x09F7D - Colored Squares & Black/White Squares +158271 - 0x09FF7 (Intro Left 3) - 0x09FDC - Colored Squares & Black/White Squares +158272 - 0x09F82 (Intro Left 4) - 0x09FF7 - Colored Squares & Black/White Squares +158273 - 0x09FF8 (Intro Left 5) - 0x09F82 - Colored Squares & Black/White Squares +158274 - 0x09D9F (Intro Back 1) - 0x09FF8 - Colored Squares & Black/White Squares +158275 - 0x09DA1 (Intro Back 2) - 0x09D9F - Colored Squares +158276 - 0x09DA2 (Intro Back 3) - 0x09DA1 - Colored Squares +158277 - 0x09DAF (Intro Back 4) - 0x09DA2 - Colored Squares 158278 - 0x0A099 (Tinted Glass Door Panel) - 0x09DAF - True Door - 0x17C79 (Tinted Glass Door) - 0x0A099 Bunker Glass Room (Bunker) - Bunker Ultraviolet Room - 0x0C2A3: -158279 - 0x0A010 (Glass Room 1) - 0x17C79 - Squares & Colored Squares -158280 - 0x0A01B (Glass Room 2) - 0x17C79 & 0x0A010 - Squares & Colored Squares & Black/White Squares -158281 - 0x0A01F (Glass Room 3) - 0x17C79 & 0x0A01B - Squares & Colored Squares & Black/White Squares +158279 - 0x0A010 (Glass Room 1) - 0x17C79 - Colored Squares +158280 - 0x0A01B (Glass Room 2) - 0x17C79 & 0x0A010 - Colored Squares & Black/White Squares +158281 - 0x0A01F (Glass Room 3) - 0x17C79 & 0x0A01B - Colored Squares & Black/White Squares Door - 0x0C2A3 (UV Room Entry) - 0x0A01F Bunker Ultraviolet Room (Bunker) - Bunker Elevator Section - 0x0A08D: 158282 - 0x34BC5 (Drop-Down Door Open) - True - True 158283 - 0x34BC6 (Drop-Down Door Close) - 0x34BC5 - True -158284 - 0x17E63 (UV Room 1) - 0x34BC5 - Squares & Colored Squares -158285 - 0x17E67 (UV Room 2) - 0x17E63 & 0x34BC6 - Squares & Colored Squares & Black/White Squares +158284 - 0x17E63 (UV Room 1) - 0x34BC5 - Colored Squares +158285 - 0x17E67 (UV Room 2) - 0x17E63 & 0x34BC6 - Colored Squares & Black/White Squares Door - 0x0A08D (Elevator Room Entry) - 0x17E67 Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay - Bunker Under Elevator - 0x0A079 | Bunker Green Room | Bunker Cyan Room | Bunker Laser Platform: @@ -797,31 +797,31 @@ Swamp Between Bridges Near (Swamp) - Swamp Between Bridges Far - 0x18507: Door - 0x18507 (Between Bridges Second Door) - 0x009A1 Swamp Between Bridges Far (Swamp) - Swamp Red Underwater - 0x183F2 - Swamp Rotating Bridge - TrueOneWay: -158319 - 0x00007 (Between Bridges Far Row 1) - 0x009A1 - Rotated Shapers & Dots & Full Dots -158320 - 0x00008 (Between Bridges Far Row 2) - 0x00007 - Rotated Shapers & Dots & Full Dots -158321 - 0x00009 (Between Bridges Far Row 3) - 0x00008 - Rotated Shapers & Shapers & Dots & Full Dots -158322 - 0x0000A (Between Bridges Far Row 4) - 0x00009 - Rotated Shapers & Shapers & Dots & Full Dots +158319 - 0x00007 (Between Bridges Far Row 1) - 0x009A1 - Rotated Shapers & Full Dots +158320 - 0x00008 (Between Bridges Far Row 2) - 0x00007 - Rotated Shapers & Full Dots +158321 - 0x00009 (Between Bridges Far Row 3) - 0x00008 - Rotated Shapers & Shapers & Full Dots +158322 - 0x0000A (Between Bridges Far Row 4) - 0x00009 - Rotated Shapers & Shapers & Full Dots Door - 0x183F2 (Red Water Pump) - 0x00596 Swamp Red Underwater (Swamp) - Swamp Maze - 0x305D5: -158323 - 0x00001 (Red Underwater 1) - True - Shapers & Negative Shapers & Dots & Full Dots -158324 - 0x014D2 (Red Underwater 2) - True - Shapers & Negative Shapers & Dots & Full Dots -158325 - 0x014D4 (Red Underwater 3) - True - Shapers & Negative Shapers & Dots & Full Dots -158326 - 0x014D1 (Red Underwater 4) - True - Shapers & Negative Shapers & Dots & Full Dots +158323 - 0x00001 (Red Underwater 1) - True - Shapers & Negative Shapers & Full Dots +158324 - 0x014D2 (Red Underwater 2) - True - Shapers & Negative Shapers & Full Dots +158325 - 0x014D4 (Red Underwater 3) - True - Shapers & Negative Shapers & Full Dots +158326 - 0x014D1 (Red Underwater 4) - True - Shapers & Negative Shapers & Full Dots Door - 0x305D5 (Red Underwater Exit) - 0x014D1 Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near Boat - 0x181F5 - Swamp Purple Area - 0x181F5: -158327 - 0x181F5 (Rotating Bridge) - True - Rotated Shapers & Shapers & Stars & Colored Squares & Triangles & Stars + Same Colored Symbol +158327 - 0x181F5 (Rotating Bridge) - True - Rotated Shapers & Shapers & Colored Squares & Triangles & Stars + Same Colored Symbol 159331 - 0x016B2 (Rotating Bridge CCW EP) - 0x181F5 - True 159334 - 0x036CE (Rotating Bridge CW EP) - 0x181F5 - True Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482 - Swamp Long Bridge - 0xFFD00 & 0xFFD02 - The Ocean - 0x09DB8: 159803 - 0xFFD02 (Beyond Rotating Bridge Reached Independently) - True - True 158328 - 0x09DB8 (Boat Spawn) - True - Boat -158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Shapers & Dots & Full Dots -158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers & Shapers & Dots & Full Dots -158331 - 0x00C2E (Beyond Rotating Bridge 3) - 0x00A1E - Shapers & Dots & Full Dots -158332 - 0x00E3A (Beyond Rotating Bridge 4) - 0x00C2E - Shapers & Dots & Full Dots +158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Shapers & Full Dots +158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers & Shapers & Full Dots +158331 - 0x00C2E (Beyond Rotating Bridge 3) - 0x00A1E - Shapers & Full Dots +158332 - 0x00E3A (Beyond Rotating Bridge 4) - 0x00C2E - Shapers & Full Dots Door - 0x18482 (Blue Water Pump) - 0x00E3A 159332 - 0x3365F (Boat EP) - 0x09DB8 - True 159333 - 0x03731 (Long Bridge Side EP) - 0x17E2B - True @@ -833,7 +833,7 @@ Swamp Purple Area (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Purple Un Door - 0x0A1D6 (Purple Water Pump) - 0x00E3A Swamp Purple Underwater (Swamp): -158333 - 0x009A6 (Purple Underwater) - True - Shapers & Triangles & Black/White Squares & Rotated Shapers & Dots & Full Dots +158333 - 0x009A6 (Purple Underwater) - True - Shapers & Triangles & Black/White Squares & Rotated Shapers & Full Dots 159330 - 0x03A9E (Purple Underwater Right EP) - True - True 159336 - 0x03A93 (Purple Underwater Left EP) - True - True @@ -851,8 +851,8 @@ Swamp Maze (Swamp) - Swamp Laser Area - 0x17C0A & 0x17E07: Swamp Laser Area (Swamp) - Outside Swamp - 0x2D880: 158711 - 0x03615 (Laser Panel) - True - True Laser - 0x00BF6 (Laser) - 0x03615 -158341 - 0x17C05 (Laser Shortcut Left Panel) - True - Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol -158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Stars & Stars + Same Colored Symbol +158341 - 0x17C05 (Laser Shortcut Left Panel) - True - Shapers & Negative Shapers & Stars + Same Colored Symbol +158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Stars + Same Colored Symbol Door - 0x2D880 (Laser Shortcut) - 0x17C02 ==Treehouse== @@ -873,91 +873,91 @@ Treehouse Beach (Treehouse Beach) - Main Island - True: Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95: 158343 - 0x17C95 (Boat Spawn) - True - Boat -158344 - 0x0288C (First Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles +158344 - 0x0288C (First Door Panel) - True - Stars + Same Colored Symbol & Triangles Door - 0x0C309 (First Door) - 0x0288C 159210 - 0x33721 (Buoy EP) - 0x17C95 - True Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: -158345 - 0x02886 (Second Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles +158345 - 0x02886 (Second Door Panel) - True - Stars + Same Colored Symbol & Triangles Door - 0x0C310 (Second Door) - 0x02886 Treehouse Yellow Bridge (Treehouse) - Treehouse After Yellow Bridge - 0x17DC4: -158346 - 0x17D72 (Yellow Bridge 1) - True - Stars & Stars + Same Colored Symbol & Triangles -158347 - 0x17D8F (Yellow Bridge 2) - 0x17D72 - Stars & Stars + Same Colored Symbol & Triangles -158348 - 0x17D74 (Yellow Bridge 3) - 0x17D8F - Stars & Stars + Same Colored Symbol & Triangles -158349 - 0x17DAC (Yellow Bridge 4) - 0x17D74 - Stars & Stars + Same Colored Symbol & Triangles -158350 - 0x17D9E (Yellow Bridge 5) - 0x17DAC - Stars & Stars + Same Colored Symbol & Triangles -158351 - 0x17DB9 (Yellow Bridge 6) - 0x17D9E - Stars & Stars + Same Colored Symbol & Triangles -158352 - 0x17D9C (Yellow Bridge 7) - 0x17DB9 - Stars & Stars + Same Colored Symbol & Triangles -158353 - 0x17DC2 (Yellow Bridge 8) - 0x17D9C - Stars & Stars + Same Colored Symbol & Triangles -158354 - 0x17DC4 (Yellow Bridge 9) - 0x17DC2 - Stars & Stars + Same Colored Symbol & Triangles +158346 - 0x17D72 (Yellow Bridge 1) - True - Stars + Same Colored Symbol & Triangles +158347 - 0x17D8F (Yellow Bridge 2) - 0x17D72 - Stars + Same Colored Symbol & Triangles +158348 - 0x17D74 (Yellow Bridge 3) - 0x17D8F - Stars + Same Colored Symbol & Triangles +158349 - 0x17DAC (Yellow Bridge 4) - 0x17D74 - Stars + Same Colored Symbol & Triangles +158350 - 0x17D9E (Yellow Bridge 5) - 0x17DAC - Stars + Same Colored Symbol & Triangles +158351 - 0x17DB9 (Yellow Bridge 6) - 0x17D9E - Stars + Same Colored Symbol & Triangles +158352 - 0x17D9C (Yellow Bridge 7) - 0x17DB9 - Stars + Same Colored Symbol & Triangles +158353 - 0x17DC2 (Yellow Bridge 8) - 0x17D9C - Stars + Same Colored Symbol & Triangles +158354 - 0x17DC4 (Yellow Bridge 9) - 0x17DC2 - Stars + Same Colored Symbol & Triangles Treehouse After Yellow Bridge (Treehouse) - Treehouse Junction - 0x0A181: -158355 - 0x0A182 (Third Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles & Colored Squares +158355 - 0x0A182 (Third Door Panel) - True - Stars + Same Colored Symbol & Triangles & Colored Squares Door - 0x0A181 (Third Door) - 0x0A182 Treehouse Junction (Treehouse) - Treehouse Right Orange Bridge - True - Treehouse First Purple Bridge - True - Treehouse Green Bridge - True: 158356 - 0x2700B (Laser House Door Timer Outside) - True - True Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x17D6C: -158357 - 0x17DC8 (First Purple Bridge 1) - True - Stars & Dots & Full Dots -158358 - 0x17DC7 (First Purple Bridge 2) - 0x17DC8 - Stars & Dots & Full Dots -158359 - 0x17CE4 (First Purple Bridge 3) - 0x17DC7 - Stars & Dots & Full Dots -158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots & Full Dots -158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots & Full Dots +158357 - 0x17DC8 (First Purple Bridge 1) - True - Stars & Full Dots +158358 - 0x17DC7 (First Purple Bridge 2) - 0x17DC8 - Stars & Full Dots +158359 - 0x17CE4 (First Purple Bridge 3) - 0x17DC7 - Stars & Full Dots +158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Full Dots +158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Full Dots Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2: -158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars & Stars + Same Colored Symbol & Triangles -158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars & Stars + Same Colored Symbol & Triangles -158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars & Stars + Same Colored Symbol & Triangles +158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars + Same Colored Symbol & Triangles +158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars + Same Colored Symbol & Triangles +158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars + Same Colored Symbol & Triangles 158394 - 0x17CE3 (Right Orange Bridge 4 & Directional) - 0x17D8C - Triangles -158395 - 0x17DCD (Right Orange Bridge 5) - 0x17CE3 - Stars & Stars + Same Colored Symbol & Triangles -158396 - 0x17DB2 (Right Orange Bridge 6) - 0x17DCD - Stars & Stars + Same Colored Symbol & Triangles -158397 - 0x17DCC (Right Orange Bridge 7) - 0x17DB2 - Stars & Stars + Same Colored Symbol & Triangles -158398 - 0x17DCA (Right Orange Bridge 8) - 0x17DCC - Stars & Stars + Same Colored Symbol & Triangles -158399 - 0x17D8E (Right Orange Bridge 9) - 0x17DCA - Stars & Stars + Same Colored Symbol & Triangles +158395 - 0x17DCD (Right Orange Bridge 5) - 0x17CE3 - Stars + Same Colored Symbol & Triangles +158396 - 0x17DB2 (Right Orange Bridge 6) - 0x17DCD - Stars + Same Colored Symbol & Triangles +158397 - 0x17DCC (Right Orange Bridge 7) - 0x17DB2 - Stars + Same Colored Symbol & Triangles +158398 - 0x17DCA (Right Orange Bridge 8) - 0x17DCC - Stars + Same Colored Symbol & Triangles +158399 - 0x17D8E (Right Orange Bridge 9) - 0x17DCA - Stars + Same Colored Symbol & Triangles 158400 - 0x17DB7 (Right Orange Bridge 10 & Directional) - 0x17D8E - Triangles -158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars & Stars + Same Colored Symbol & Triangles -158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars & Stars + Same Colored Symbol & Triangles +158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars + Same Colored Symbol & Triangles +158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars + Same Colored Symbol & Triangles Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D: 158404 - 0x037FF (Drawbridge Panel) - True - Stars Door - 0x0C32D (Drawbridge) - 0x037FF Treehouse Second Purple Bridge (Treehouse) - Treehouse Left Orange Bridge - 0x17DC6: -158362 - 0x17D9B (Second Purple Bridge 1) - True - Stars & Black/White Squares & Triangles & Stars + Same Colored Symbol -158363 - 0x17D99 (Second Purple Bridge 2) - 0x17D9B - Stars & Black/White Squares & Triangles & Stars + Same Colored Symbol -158364 - 0x17DAA (Second Purple Bridge 3) - 0x17D99 - Stars & Black/White Squares & Triangles & Stars + Same Colored Symbol -158365 - 0x17D97 (Second Purple Bridge 4) - 0x17DAA - Stars & Black/White Squares & Colored Squares & Triangles & Stars + Same Colored Symbol -158366 - 0x17BDF (Second Purple Bridge 5) - 0x17D97 - Stars & Colored Squares & Triangles & Stars + Same Colored Symbol -158367 - 0x17D91 (Second Purple Bridge 6) - 0x17BDF - Stars & Colored Squares & Triangles & Stars + Same Colored Symbol -158368 - 0x17DC6 (Second Purple Bridge 7) - 0x17D91 - Stars & Colored Squares & Triangles & Stars + Same Colored Symbol +158362 - 0x17D9B (Second Purple Bridge 1) - True - Black/White Squares & Triangles & Stars + Same Colored Symbol +158363 - 0x17D99 (Second Purple Bridge 2) - 0x17D9B - Black/White Squares & Triangles & Stars + Same Colored Symbol +158364 - 0x17DAA (Second Purple Bridge 3) - 0x17D99 - Black/White Squares & Triangles & Stars + Same Colored Symbol +158365 - 0x17D97 (Second Purple Bridge 4) - 0x17DAA - Black/White Squares & Colored Squares & Triangles & Stars + Same Colored Symbol +158366 - 0x17BDF (Second Purple Bridge 5) - 0x17D97 - Colored Squares & Triangles & Stars + Same Colored Symbol +158367 - 0x17D91 (Second Purple Bridge 6) - 0x17BDF - Colored Squares & Triangles & Stars + Same Colored Symbol +158368 - 0x17DC6 (Second Purple Bridge 7) - 0x17D91 - Colored Squares & Triangles & Stars + Same Colored Symbol Treehouse Left Orange Bridge (Treehouse) - Treehouse Laser Room Front Platform - 0x17DDE - Treehouse Laser Room Back Platform - 0x17DDB - Treehouse Burned House - 0x17DDB: -158376 - 0x17DB3 (Left Orange Bridge 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158377 - 0x17DB5 (Left Orange Bridge 2) - 0x17DB3 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158378 - 0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158379 - 0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158380 - 0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158381 - 0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158382 - 0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158383 - 0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158384 - 0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares -158385 - 0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares -158386 - 0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares -158387 - 0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares -158388 - 0x17DAE (Left Orange Bridge 13) - 0x17DEC & 0x03613 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Triangles -158389 - 0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Stars & Black/White Squares & Stars + Same Colored Symbol -158390 - 0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Stars & Black/White Squares & Stars + Same Colored Symbol +158376 - 0x17DB3 (Left Orange Bridge 1) - True - Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers +158377 - 0x17DB5 (Left Orange Bridge 2) - 0x17DB3 - Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers +158378 - 0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers +158379 - 0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers +158380 - 0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers +158381 - 0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers +158382 - 0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers +158383 - 0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers +158384 - 0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares +158385 - 0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares +158386 - 0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares +158387 - 0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares +158388 - 0x17DAE (Left Orange Bridge 13) - 0x17DEC & 0x03613 - Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Triangles +158389 - 0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Black/White Squares & Stars + Same Colored Symbol +158390 - 0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Black/White Squares & Stars + Same Colored Symbol Treehouse Green Bridge (Treehouse) - Treehouse Green Bridge Front House - 0x17E61 - Treehouse Green Bridge Left House - 0x17E61: -158369 - 0x17E3C (Green Bridge 1) - True - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158370 - 0x17E4D (Green Bridge 2) - 0x17E3C - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158371 - 0x17E4F (Green Bridge 3) - 0x17E4D - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158372 - 0x17E52 (Green Bridge 4 & Directional) - 0x17E4F - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol & Triangles -158375 - 0x17E61 (Green Bridge 7) - 0x17E5F - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol & Triangles +158369 - 0x17E3C (Green Bridge 1) - True - Shapers & Negative Shapers & Stars + Same Colored Symbol +158370 - 0x17E4D (Green Bridge 2) - 0x17E3C - Shapers & Negative Shapers & Stars + Same Colored Symbol +158371 - 0x17E4F (Green Bridge 3) - 0x17E4D - Shapers & Negative Shapers & Stars + Same Colored Symbol +158372 - 0x17E52 (Green Bridge 4 & Directional) - 0x17E4F - Shapers & Negative Shapers & Stars + Same Colored Symbol +158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Shapers & Negative Shapers & Stars + Same Colored Symbol +158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Shapers & Negative Shapers & Stars + Same Colored Symbol & Triangles +158375 - 0x17E61 (Green Bridge 7) - 0x17E5F - Shapers & Negative Shapers & Stars + Same Colored Symbol & Triangles Treehouse Green Bridge Front House (Treehouse): 158610 - 0x17FA9 (Green Bridge Discard) - True - Arrows @@ -993,7 +993,7 @@ Mountainside Obelisk (Mountainside) - Entry - True: Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085: 159550 - 0x28B91 (Thundercloud EP) - 0xFFD03 - True 158612 - 0x17C42 (Discard) - True - Arrows -158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Squares & Triangles & Stars & Stars + Same Colored Symbol +158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Squares & Triangles & Stars + Same Colored Symbol Door - 0x00085 (Vault Door) - 0x002A6 159301 - 0x335AE (Cloud Cycle EP) - True - True 159325 - 0x33505 (Bush EP) - True - True @@ -1005,7 +1005,7 @@ Mountainside Vault (Mountainside): Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34: 158405 - 0x0042D (River Shape) - True - True 158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True -158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol & Triangles +158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Black/White Squares & Stars + Same Colored Symbol & Triangles 158800 - 0xFFF00 (Box Long) - 11 Lasers + Redirect & 0x17C34 - True 159300 - 0x001A3 (River Shape EP) - True - True 159320 - 0x3370E (Arch Black EP) - True - True @@ -1018,18 +1018,18 @@ Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39: 158408 - 0x09E39 (Light Bridge Controller) - True - Eraser & Triangles Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay - Mountain Floor 1 Trash Pillar - TrueOneWay - Mountain Floor 1 Back Section - TrueOneWay: -158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots & Stars & Stars + Same Colored Symbol +158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots & Stars + Same Colored Symbol 158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Triangles -158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Stars & Stars + Same Colored Symbol -158412 - 0x09E69 (Right Row 4) - 0x09E72 - Stars & Black/White Squares & Stars + Same Colored Symbol & Rotated Shapers -158413 - 0x09E7B (Right Row 5) - 0x09E69 - Stars & Black/White Squares & Stars + Same Colored Symbol & Eraser & Dots & Triangles & Shapers +158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Stars + Same Colored Symbol +158412 - 0x09E69 (Right Row 4) - 0x09E72 - Black/White Squares & Stars + Same Colored Symbol & Rotated Shapers +158413 - 0x09E7B (Right Row 5) - 0x09E69 - Black/White Squares & Stars + Same Colored Symbol & Eraser & Dots & Triangles & Shapers 158414 - 0x09E73 (Left Row 1) - True - Dots & Black/White Squares & Triangles 158415 - 0x09E75 (Left Row 2) - 0x09E73 - Dots & Black/White Squares & Shapers & Rotated Shapers -158416 - 0x09E78 (Left Row 3) - 0x09E75 - Stars & Triangles & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158417 - 0x09E79 (Left Row 4) - 0x09E78 - Stars & Colored Squares & Stars + Same Colored Symbol & Triangles & Eraser -158418 - 0x09E6C (Left Row 5) - 0x09E79 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158419 - 0x09E6F (Left Row 6) - 0x09E6C - Symmetry & Stars & Colored Squares & Black/White Squares & Stars + Same Colored Symbol & Symmetry & Eraser -158420 - 0x09E6B (Left Row 7) - 0x09E6F - Symmetry & Dots & Full Dots & Triangles +158416 - 0x09E78 (Left Row 3) - 0x09E75 - Triangles & Stars + Same Colored Symbol & Shapers & Rotated Shapers +158417 - 0x09E79 (Left Row 4) - 0x09E78 - Colored Squares & Stars + Same Colored Symbol & Triangles & Eraser +158418 - 0x09E6C (Left Row 5) - 0x09E79 - Shapers & Negative Shapers & Stars + Same Colored Symbol +158419 - 0x09E6F (Left Row 6) - 0x09E6C - Symmetry & Colored Squares & Black/White Squares & Stars + Same Colored Symbol & Symmetry & Eraser +158420 - 0x09E6B (Left Row 7) - 0x09E6F - Symmetry & Full Dots & Triangles 158424 - 0x09EAD (Trash Pillar 1) - True - Rotated Shapers & Stars 158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Rotated Shapers & Triangles @@ -1037,18 +1037,18 @@ Mountain Floor 1 Trash Pillar (Mountain Floor 1): Mountain Floor 1 Back Section (Mountain Floor 1): 158421 - 0x33AF5 (Back Row 1) - True - Symmetry & Black/White Squares & Triangles -158422 - 0x33AF7 (Back Row 2) - 0x33AF5 - Symmetry & Stars & Triangles & Stars + Same Colored Symbol -158423 - 0x09F6E (Back Row 3) - 0x33AF7 - Symmetry & Stars & Shapers & Stars + Same Colored Symbol +158422 - 0x33AF7 (Back Row 2) - 0x33AF5 - Symmetry & Triangles & Stars + Same Colored Symbol +158423 - 0x09F6E (Back Row 3) - 0x33AF7 - Symmetry & Shapers & Stars + Same Colored Symbol Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 Above The Abyss - True - Mountain Pink Bridge EP - TrueOneWay: -158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol -158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Triangles & Stars + Same Colored Symbol -158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol +158426 - 0x09FD3 (Near Row 1) - True - Colored Squares & Stars + Same Colored Symbol +158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Triangles & Stars + Same Colored Symbol +158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Shapers & Negative Shapers & Stars + Same Colored Symbol 158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Stars -158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser +158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Stars + Same Colored Symbol & Rotated Shapers & Eraser Door - 0x09FFB (Staircase Near) - 0x09FD8 Mountain Floor 2 Above The Abyss (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD & 0x09ED8 & 0x09E86: @@ -1059,8 +1059,8 @@ Mountain Floor 2 Light Bridge Room Near (Mountain Floor 2): Mountain Floor 2 Beyond Bridge (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Far - 0x09E07 - Mountain Pink Bridge EP - TrueOneWay - Mountain Floor 2 - 0x09ED8: 158432 - 0x09FCC (Far Row 1) - True - Triangles -158433 - 0x09FCE (Far Row 2) - 0x09FCC - Black/White Squares & Stars & Stars + Same Colored Symbol -158434 - 0x09FCF (Far Row 3) - 0x09FCE - Stars & Triangles & Stars + Same Colored Symbol +158433 - 0x09FCE (Far Row 2) - 0x09FCC - Black/White Squares & Stars + Same Colored Symbol +158434 - 0x09FCF (Far Row 3) - 0x09FCE - Triangles & Stars + Same Colored Symbol 158435 - 0x09FD0 (Far Row 4) - 0x09FCF - Rotated Shapers & Negative Shapers 158436 - 0x09FD1 (Far Row 5) - 0x09FD0 - Dots 158437 - 0x09FD2 (Far Row 6) - 0x09FD1 - Rotated Shapers @@ -1088,19 +1088,19 @@ Door - 0x09F89 (Exit) - 0x09FDA Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141: 158614 - 0x17FA2 (Discard) - 0xFFF00 - Arrows 158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars -158446 - 0x01987 (Pillars Room Entry Right) - True - Squares & Colored Squares & Dots +158446 - 0x01987 (Pillars Room Entry Right) - True - Colored Squares & Dots Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987 Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: -158522 - 0x0383A (Right Pillar 1) - True - Stars & Eraser & Triangles & Stars + Same Colored Symbol -158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Dots & Full Dots & Triangles & Symmetry -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol & Symmetry -158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Eraser & Symmetry & Stars & Stars + Same Colored Symbol & Negative Shapers & Shapers -158526 - 0x0383D (Left Pillar 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol +158522 - 0x0383A (Right Pillar 1) - True - Eraser & Triangles & Stars + Same Colored Symbol +158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Full Dots & Triangles & Symmetry +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Shapers & Negative Shapers & Stars + Same Colored Symbol & Symmetry +158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Eraser & Symmetry & Stars + Same Colored Symbol & Negative Shapers & Shapers +158526 - 0x0383D (Left Pillar 1) - True - Black/White Squares & Stars + Same Colored Symbol 158527 - 0x0383F (Left Pillar 2) - 0x0383D - Triangles & Symmetry 158528 - 0x03859 (Left Pillar 3) - 0x0383F - Symmetry & Shapers & Black/White Squares -158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles & Colored Dots +158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars + Same Colored Symbol & Triangles & Colored Dots Elevator (Mountain Bottom Floor): 158530 - 0x3D9A6 (Elevator Door Close Left) - True - True @@ -1124,9 +1124,9 @@ Door - 0x2D77D (Caves Entry) - 0x00FF8 Caves Entry Door (Caves): Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5 - Caves Entry Door - TrueOneWay: -158451 - 0x335AB (Elevator Inside Control) - True - Dots & Squares & Black/White Squares -158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Squares & Black/White Squares -158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Squares & Black/White Squares & Dots +158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares +158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares +158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots 158454 - 0x00190 (Blue Tunnel Right First 1) - True - Arrows 158455 - 0x00558 (Blue Tunnel Right First 2) - 0x00190 - Arrows 158456 - 0x00567 (Blue Tunnel Right First 3) - 0x00558 - Arrows @@ -1145,41 +1145,41 @@ Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x01 158469 - 0x009A4 (Blue Tunnel Left Third 1) - True - Arrows & Stars 158470 - 0x018A0 (Blue Tunnel Right Third 1) - True - Arrows & Symmetry 158471 - 0x00A72 (Blue Tunnel Left Fourth 1) - True - Arrows & Shapers & Negative Shapers -158472 - 0x32962 (First Floor Left) - True - Dots & Full Dots & Rotated Shapers -158473 - 0x32966 (First Floor Grounded) - True - Stars & Triangles & Rotated Shapers & Black/White Squares & Stars + Same Colored Symbol +158472 - 0x32962 (First Floor Left) - True - Full Dots & Rotated Shapers +158473 - 0x32966 (First Floor Grounded) - True - Triangles & Rotated Shapers & Black/White Squares & Stars + Same Colored Symbol 158474 - 0x01A31 (First Floor Middle) - True - Stars -158475 - 0x00B71 (First Floor Right) - True - Dots & Full Dots & Eraser & Stars & Stars + Same Colored Symbol & Colored Squares & Shapers & Negative Shapers +158475 - 0x00B71 (First Floor Right) - True - Full Dots & Eraser & Stars + Same Colored Symbol & Colored Squares & Shapers & Negative Shapers 158478 - 0x288EA (First Wooden Beam) - True - Stars 158479 - 0x288FC (Second Wooden Beam) - True - Shapers & Eraser 158480 - 0x289E7 (Third Wooden Beam) - True - Eraser & Triangles -158481 - 0x288AA (Fourth Wooden Beam) - True - Dots & Full Dots & Negative Shapers & Shapers -158482 - 0x17FB9 (Left Upstairs Single) - True - Dots & Full Dots & Arrows & Black/White Squares -158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Dots & Full Dots & Arrows -158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Dots & Full Dots & Arrows -158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Dots & Full Dots & Arrows -158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Dots & Full Dots & Arrows -158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Dots & Full Dots & Arrows +158481 - 0x288AA (Fourth Wooden Beam) - True - Full Dots & Negative Shapers & Shapers +158482 - 0x17FB9 (Left Upstairs Single) - True - Full Dots & Arrows & Black/White Squares +158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Full Dots & Arrows +158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Full Dots & Arrows +158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Full Dots & Arrows +158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Full Dots & Arrows +158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Full Dots & Arrows 158488 - 0x0008F (Right Upstairs Left Row 1) - True - Dots & Black/White Squares & Colored Squares -158489 - 0x0006B (Right Upstairs Left Row 2) - 0x0008F - Stars & Black/White Squares & Colored Squares & Stars + Same Colored Symbol -158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Stars & Black/White Squares & Colored Squares & Stars + Same Colored Symbol & Triangles -158491 - 0x0008C (Right Upstairs Left Row 4) - 0x0008B - Stars & Stars + Same Colored Symbol & Shapers & Rotated Shapers -158492 - 0x0008A (Right Upstairs Left Row 5) - 0x0008C - Stars & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Eraser & Triangles +158489 - 0x0006B (Right Upstairs Left Row 2) - 0x0008F - Black/White Squares & Colored Squares & Stars + Same Colored Symbol +158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Black/White Squares & Colored Squares & Stars + Same Colored Symbol & Triangles +158491 - 0x0008C (Right Upstairs Left Row 4) - 0x0008B - Stars + Same Colored Symbol & Shapers & Rotated Shapers +158492 - 0x0008A (Right Upstairs Left Row 5) - 0x0008C - Stars + Same Colored Symbol & Shapers & Rotated Shapers & Eraser & Triangles 158493 - 0x00089 (Right Upstairs Left Row 6) - 0x0008A - Shapers & Negative Shapers & Dots 158494 - 0x0006A (Right Upstairs Left Row 7) - 0x00089 - Stars & Dots -158495 - 0x0006C (Right Upstairs Left Row 8) - 0x0006A - Dots & Stars & Stars + Same Colored Symbol & Eraser +158495 - 0x0006C (Right Upstairs Left Row 8) - 0x0006A - Dots & Stars + Same Colored Symbol & Eraser 158496 - 0x00027 (Right Upstairs Right Row 1) - True - Colored Squares & Black/White Squares & Eraser 158497 - 0x00028 (Right Upstairs Right Row 2) - 0x00027 - Shapers & Symmetry 158498 - 0x00029 (Right Upstairs Right Row 3) - 0x00028 - Symmetry & Triangles & Eraser 158476 - 0x09DD5 (Lone Pillar) - True - Arrows Door - 0x019A5 (Pillar Door) - 0x09DD5 -158449 - 0x021D7 (Mountain Shortcut Panel) - True - Stars & Stars + Same Colored Symbol & Triangles & Eraser +158449 - 0x021D7 (Mountain Shortcut Panel) - True - Stars + Same Colored Symbol & Triangles & Eraser Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7 158450 - 0x17CF2 (Swamp Shortcut Panel) - True - Arrows Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True Caves Path to Challenge (Caves) - Challenge - 0x0A19A: -158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Arrows & Stars + Same Colored Symbol +158477 - 0x0A16E (Challenge Entry Panel) - True - Arrows & Stars + Same Colored Symbol Door - 0x0A19A (Challenge Entry) - 0x0A16E ==Challenge== diff --git a/worlds/witness/data/WitnessLogicVanilla.txt b/worlds/witness/data/WitnessLogicVanilla.txt index a967a12e28..f3fa51bb9d 100644 --- a/worlds/witness/data/WitnessLogicVanilla.txt +++ b/worlds/witness/data/WitnessLogicVanilla.txt @@ -52,11 +52,11 @@ Outside Tutorial Vault (Outside Tutorial): 158651 - 0x03481 (Vault Box) - True - True Outside Tutorial Path To Outpost (Outside Tutorial) - Outside Tutorial Outpost - 0x0A170: -158011 - 0x0A171 (Outpost Entry Panel) - True - Dots & Full Dots +158011 - 0x0A171 (Outpost Entry Panel) - True - Full Dots Door - 0x0A170 (Outpost Entry) - 0x0A171 Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: -158012 - 0x04CA4 (Outpost Exit Panel) - True - Dots & Full Dots +158012 - 0x04CA4 (Outpost Exit Panel) - True - Full Dots Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Triangles @@ -136,12 +136,12 @@ Door - 0x18269 (Upper) - 0x1C349 159000 - 0x0332B (Glass Factory Black Line Reflection EP) - True - True Symmetry Island Upper (Symmetry Island): -158065 - 0x00A52 (Laser Yellow 1) - True - Symmetry & Colored Dots -158066 - 0x00A57 (Laser Yellow 2) - 0x00A52 - Symmetry & Colored Dots -158067 - 0x00A5B (Laser Yellow 3) - 0x00A57 - Symmetry & Colored Dots -158068 - 0x00A61 (Laser Blue 1) - 0x00A52 - Symmetry & Colored Dots -158069 - 0x00A64 (Laser Blue 2) - 0x00A61 & 0x00A57 - Symmetry & Colored Dots -158070 - 0x00A68 (Laser Blue 3) - 0x00A64 & 0x00A5B - Symmetry & Colored Dots +158065 - 0x00A52 (Laser Yellow 1) - True - Colored Dots +158066 - 0x00A57 (Laser Yellow 2) - 0x00A52 - Colored Dots +158067 - 0x00A5B (Laser Yellow 3) - 0x00A57 - Colored Dots +158068 - 0x00A61 (Laser Blue 1) - 0x00A52 - Colored Dots +158069 - 0x00A64 (Laser Blue 2) - 0x00A61 & 0x00A57 - Colored Dots +158070 - 0x00A68 (Laser Blue 3) - 0x00A64 & 0x00A5B - Colored Dots 158700 - 0x0360D (Laser Panel) - 0x00A68 - True Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True @@ -157,7 +157,7 @@ Desert Obelisk (Desert) - Entry - True: 159709 - 0x00359 (Obelisk) - True - True Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: -158652 - 0x0CC7B (Vault Panel) - True - Dots & Shapers & Rotated Shapers & Negative Shapers & Full Dots +158652 - 0x0CC7B (Vault Panel) - True - Shapers & Rotated Shapers & Negative Shapers & Full Dots Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Triangles 158076 - 0x00698 (Surface 1) - True - True @@ -285,7 +285,7 @@ Quarry Stoneworks Middle Floor (Quarry Stoneworks) - Quarry Stoneworks Lift - Tr 158125 - 0x00E0C (Lower Row 1) - True - Dots & Eraser 158126 - 0x01489 (Lower Row 2) - 0x00E0C - Dots & Eraser 158127 - 0x0148A (Lower Row 3) - 0x01489 - Dots & Eraser -158128 - 0x014D9 (Lower Row 4) - 0x0148A - Dots & Full Dots & Eraser +158128 - 0x014D9 (Lower Row 4) - 0x0148A - Full Dots & Eraser 158129 - 0x014E7 (Lower Row 5) - 0x014D9 - Dots 158130 - 0x014E8 (Lower Row 6) - 0x014E7 - Dots & Eraser @@ -336,12 +336,12 @@ Quarry Boathouse Upper Back (Quarry Boathouse) - Quarry Boathouse Upper Middle - 158155 - 0x38663 (Second Barrier Panel) - True - True Door - 0x3865F (Second Barrier) - 0x38663 158156 - 0x021B5 (Back First Row 1) - True - Stars & Eraser -158157 - 0x021B6 (Back First Row 2) - 0x021B5 - Stars & Stars + Same Colored Symbol & Eraser +158157 - 0x021B6 (Back First Row 2) - 0x021B5 - Stars + Same Colored Symbol & Eraser 158158 - 0x021B7 (Back First Row 3) - 0x021B6 - Stars & Eraser -158159 - 0x021BB (Back First Row 4) - 0x021B7 - Stars & Stars + Same Colored Symbol & Eraser -158160 - 0x09DB5 (Back First Row 5) - 0x021BB - Stars & Stars + Same Colored Symbol & Eraser -158161 - 0x09DB1 (Back First Row 6) - 0x09DB5 - Stars & Stars + Same Colored Symbol & Eraser -158162 - 0x3C124 (Back First Row 7) - 0x09DB1 - Stars & Stars + Same Colored Symbol & Eraser +158159 - 0x021BB (Back First Row 4) - 0x021B7 - Stars + Same Colored Symbol & Eraser +158160 - 0x09DB5 (Back First Row 5) - 0x021BB - Stars + Same Colored Symbol & Eraser +158161 - 0x09DB1 (Back First Row 6) - 0x09DB5 - Stars + Same Colored Symbol & Eraser +158162 - 0x3C124 (Back First Row 7) - 0x09DB1 - Stars + Same Colored Symbol & Eraser 158163 - 0x09DB3 (Back First Row 8) - 0x3C124 - Stars & Eraser & Shapers 158164 - 0x09DB4 (Back First Row 9) - 0x09DB3 - Stars & Eraser & Shapers 158165 - 0x275FA (Hook Control) - True - Shapers & Eraser @@ -537,11 +537,11 @@ Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 158221 - 0x28AE3 (Vines) - 0x18590 - True 158222 - 0x28938 (Apple Tree) - 0x28AE3 - True 158223 - 0x079DF (Triple Exit) - 0x28938 - True -158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Rotated Shapers & Dots & Full Dots -158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Shapers & Rotated Shapers & Dots & Full Dots -158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Shapers & Rotated Shapers & Dots & Full Dots -158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Rotated Shapers & Dots & Full Dots -158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Rotated Shapers & Dots & Full Dots +158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Rotated Shapers & Full Dots +158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Shapers & Rotated Shapers & Full Dots +158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Shapers & Rotated Shapers & Full Dots +158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Rotated Shapers & Full Dots +158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Rotated Shapers & Full Dots Door - 0x034F5 (Wooden Roof Stairs) - 0x28AC1 158225 - 0x28998 (RGB House Entry Panel) - True - Stars & Rotated Shapers Door - 0x28A61 (RGB House Entry) - 0x28998 @@ -575,7 +575,7 @@ Town Red Rooftop (Town): 158224 - 0x28B39 (Tall Hexagonal) - 0x079DF - True Town Wooden Rooftop (Town): -158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Shapers & Dots & Eraser & Full Dots +158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Shapers & Eraser & Full Dots Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True @@ -934,28 +934,28 @@ Treehouse Second Purple Bridge (Treehouse) - Treehouse Left Orange Bridge - 0x17 158368 - 0x17DC6 (Second Purple Bridge 7) - 0x17D91 - Stars & Colored Squares Treehouse Left Orange Bridge (Treehouse) - Treehouse Laser Room Front Platform - 0x17DDB - Treehouse Laser Room Back Platform - 0x17DDB - Treehouse Burned House - 0x17DDB: -158376 - 0x17DB3 (Left Orange Bridge 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol +158376 - 0x17DB3 (Left Orange Bridge 1) - True - Black/White Squares & Stars + Same Colored Symbol 158377 - 0x17DB5 (Left Orange Bridge 2) - 0x17DB3 - Stars & Black/White Squares -158378 - 0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Stars & Black/White Squares & Stars + Same Colored Symbol -158379 - 0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Stars & Black/White Squares & Stars + Same Colored Symbol -158380 - 0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Stars & Black/White Squares & Stars + Same Colored Symbol -158381 - 0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Stars & Black/White Squares & Colored Squares & Stars + Same Colored Symbol -158382 - 0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Stars & Black/White Squares & Colored Squares & Stars + Same Colored Symbol -158383 - 0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Stars & Colored Squares & Stars + Same Colored Symbol -158384 - 0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Stars & Colored Squares & Stars + Same Colored Symbol -158385 - 0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Stars & Colored Squares & Stars + Same Colored Symbol -158386 - 0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Stars & Colored Squares & Stars + Same Colored Symbol -158387 - 0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Stars & Black/White Squares & Stars + Same Colored Symbol -158388 - 0x17DAE (Left Orange Bridge 13) - 0x17DEC - Stars & Black/White Squares & Stars + Same Colored Symbol -158389 - 0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Stars & Black/White Squares & Stars + Same Colored Symbol -158390 - 0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Stars & Black/White Squares & Stars + Same Colored Symbol +158378 - 0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Black/White Squares & Stars + Same Colored Symbol +158379 - 0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Black/White Squares & Stars + Same Colored Symbol +158380 - 0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Black/White Squares & Stars + Same Colored Symbol +158381 - 0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Black/White Squares & Colored Squares & Stars + Same Colored Symbol +158382 - 0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Black/White Squares & Colored Squares & Stars + Same Colored Symbol +158383 - 0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Colored Squares & Stars + Same Colored Symbol +158384 - 0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Colored Squares & Stars + Same Colored Symbol +158385 - 0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Colored Squares & Stars + Same Colored Symbol +158386 - 0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Colored Squares & Stars + Same Colored Symbol +158387 - 0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Black/White Squares & Stars + Same Colored Symbol +158388 - 0x17DAE (Left Orange Bridge 13) - 0x17DEC - Black/White Squares & Stars + Same Colored Symbol +158389 - 0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Black/White Squares & Stars + Same Colored Symbol +158390 - 0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Black/White Squares & Stars + Same Colored Symbol Treehouse Green Bridge (Treehouse) - Treehouse Green Bridge Front House - 0x17E61 - Treehouse Green Bridge Left House - 0x17E61: 158369 - 0x17E3C (Green Bridge 1) - True - Stars & Shapers 158370 - 0x17E4D (Green Bridge 2) - 0x17E3C - Stars & Shapers 158371 - 0x17E4F (Green Bridge 3) - 0x17E4D - Stars & Shapers & Rotated Shapers 158372 - 0x17E52 (Green Bridge 4 & Directional) - 0x17E4F - Stars & Rotated Shapers -158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Stars & Shapers & Stars + Same Colored Symbol +158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Shapers & Stars + Same Colored Symbol 158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Stars & Negative Shapers & Rotated Shapers 158375 - 0x17E61 (Green Bridge 7) - 0x17E5F - Stars & Shapers & Rotated Shapers @@ -1046,8 +1046,8 @@ Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 Above The Abyss - True - Mountain Pink Bridge EP - TrueOneWay: 158426 - 0x09FD3 (Near Row 1) - True - Colored Squares 158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Colored Squares & Dots -158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol -158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers +158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Colored Squares & Stars + Same Colored Symbol +158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Colored Squares & Stars + Same Colored Symbol & Shapers 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares Door - 0x09FFB (Staircase Near) - 0x09FD8 @@ -1095,9 +1095,9 @@ Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: 158522 - 0x0383A (Right Pillar 1) - True - Stars 158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Full Dots 158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry -158526 - 0x0383D (Left Pillar 1) - True - Dots & Full Dots +158526 - 0x0383D (Left Pillar 1) - True - Full Dots 158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares 158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers 158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry @@ -1148,28 +1148,28 @@ Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x01 158472 - 0x32962 (First Floor Left) - True - Rotated Shapers & Shapers 158473 - 0x32966 (First Floor Grounded) - True - Stars & Black/White Squares 158474 - 0x01A31 (First Floor Middle) - True - Colored Squares -158475 - 0x00B71 (First Floor Right) - True - Colored Squares & Stars & Stars + Same Colored Symbol & Eraser +158475 - 0x00B71 (First Floor Right) - True - Colored Squares & Stars + Same Colored Symbol & Eraser 158478 - 0x288EA (First Wooden Beam) - True - Rotated Shapers 158479 - 0x288FC (Second Wooden Beam) - True - Black/White Squares & Shapers & Rotated Shapers 158480 - 0x289E7 (Third Wooden Beam) - True - Black/White Squares 158481 - 0x288AA (Fourth Wooden Beam) - True - Black/White Squares & Shapers 158482 - 0x17FB9 (Left Upstairs Single) - True - Dots -158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Dots & Full Dots -158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Dots & Full Dots -158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Dots & Full Dots -158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Dots & Full Dots -158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Dots & Full Dots -158488 - 0x0008F (Right Upstairs Left Row 1) - True - Dots & Invisible Dots -158489 - 0x0006B (Right Upstairs Left Row 2) - 0x0008F - Dots & Invisible Dots -158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Dots & Invisible Dots -158491 - 0x0008C (Right Upstairs Left Row 4) - 0x0008B - Dots & Invisible Dots -158492 - 0x0008A (Right Upstairs Left Row 5) - 0x0008C - Dots & Invisible Dots -158493 - 0x00089 (Right Upstairs Left Row 6) - 0x0008A - Dots & Invisible Dots -158494 - 0x0006A (Right Upstairs Left Row 7) - 0x00089 - Dots & Invisible Dots -158495 - 0x0006C (Right Upstairs Left Row 8) - 0x0006A - Dots & Invisible Dots -158496 - 0x00027 (Right Upstairs Right Row 1) - True - Dots & Invisible Dots & Symmetry -158497 - 0x00028 (Right Upstairs Right Row 2) - 0x00027 - Dots & Invisible Dots & Symmetry -158498 - 0x00029 (Right Upstairs Right Row 3) - 0x00028 - Dots & Invisible Dots & Symmetry +158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Full Dots +158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Full Dots +158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Full Dots +158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Full Dots +158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Full Dots +158488 - 0x0008F (Right Upstairs Left Row 1) - True - Dots +158489 - 0x0006B (Right Upstairs Left Row 2) - 0x0008F - Dots +158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Dots +158491 - 0x0008C (Right Upstairs Left Row 4) - 0x0008B - Dots +158492 - 0x0008A (Right Upstairs Left Row 5) - 0x0008C - Dots +158493 - 0x00089 (Right Upstairs Left Row 6) - 0x0008A - Dots +158494 - 0x0006A (Right Upstairs Left Row 7) - 0x00089 - Dots +158495 - 0x0006C (Right Upstairs Left Row 8) - 0x0006A - Dots +158496 - 0x00027 (Right Upstairs Right Row 1) - True - Dots & Symmetry +158497 - 0x00028 (Right Upstairs Right Row 2) - 0x00027 - Dots & Symmetry +158498 - 0x00029 (Right Upstairs Right Row 3) - 0x00028 - Dots & Symmetry 158476 - 0x09DD5 (Lone Pillar) - True - Triangles Door - 0x019A5 (Pillar Door) - 0x09DD5 158449 - 0x021D7 (Mountain Shortcut Panel) - True - Stars @@ -1179,7 +1179,7 @@ Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True Caves Path to Challenge (Caves) - Challenge - 0x0A19A: -158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Shapers & Stars + Same Colored Symbol +158477 - 0x0A16E (Challenge Entry Panel) - True - Shapers & Stars + Same Colored Symbol Door - 0x0A19A (Challenge Entry) - 0x0A16E ==Challenge== diff --git a/worlds/witness/data/WitnessLogicVariety.txt b/worlds/witness/data/WitnessLogicVariety.txt index bc9a40f566..1014558a66 100644 --- a/worlds/witness/data/WitnessLogicVariety.txt +++ b/worlds/witness/data/WitnessLogicVariety.txt @@ -28,11 +28,11 @@ Tutorial (Tutorial) - Outside Tutorial - True: Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0: 158650 - 0x033D4 (Vault Panel) - True - Dots & Black/White Squares & Colored Squares & Symmetry Door - 0x033D0 (Vault Door) - 0x033D4 -158013 - 0x0005D (Shed Row 1) - True - Dots & Full Dots & Black/White Squares & Colored Squares -158014 - 0x0005E (Shed Row 2) - 0x0005D - Dots & Full Dots & Stars -158015 - 0x0005F (Shed Row 3) - 0x0005E - Dots & Full Dots & Shapers & Negative Shapers -158016 - 0x00060 (Shed Row 4) - 0x0005F - Dots & Full Dots & Black/White Squares & Stars & Stars + Same Colored Symbol & Eraser -158017 - 0x00061 (Shed Row 5) - 0x00060 - Dots & Full Dots & Triangles +158013 - 0x0005D (Shed Row 1) - True - Full Dots & Black/White Squares & Colored Squares +158014 - 0x0005E (Shed Row 2) - 0x0005D - Full Dots & Stars +158015 - 0x0005F (Shed Row 3) - 0x0005E - Full Dots & Shapers & Negative Shapers +158016 - 0x00060 (Shed Row 4) - 0x0005F - Full Dots & Black/White Squares & Stars + Same Colored Symbol & Eraser +158017 - 0x00061 (Shed Row 5) - 0x00060 - Full Dots & Triangles 158018 - 0x018AF (Tree Row 1) - True - Arrows 158019 - 0x0001B (Tree Row 2) - 0x018AF - Arrows 158020 - 0x012C9 (Tree Row 3) - 0x0001B - Arrows @@ -52,11 +52,11 @@ Outside Tutorial Vault (Outside Tutorial): 158651 - 0x03481 (Vault Box) - True - True Outside Tutorial Path To Outpost (Outside Tutorial) - Outside Tutorial Outpost - 0x0A170: -158011 - 0x0A171 (Outpost Entry Panel) - True - Dots & Full Dots & Triangles & Black/White Squares +158011 - 0x0A171 (Outpost Entry Panel) - True - Full Dots & Triangles & Black/White Squares Door - 0x0A170 (Outpost Entry) - 0x0A171 Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3: -158012 - 0x04CA4 (Outpost Exit Panel) - True - Dots & Full Dots & Triangles & Black/White Squares +158012 - 0x04CA4 (Outpost Exit Panel) - True - Full Dots & Triangles & Black/White Squares Door - 0x04CA3 (Outpost Exit) - 0x04CA4 158600 - 0x17CFB (Discard) - True - Arrows & Triangles @@ -108,11 +108,11 @@ Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Door - 0x17F3E (Lower) - 0x000B0 Symmetry Island Lower (Symmetry Island) - Symmetry Island Upper - 0x18269: -158041 - 0x00022 (Right 1) - True - Symmetry & Dots & Full Dots & Triangles -158042 - 0x00023 (Right 2) - 0x00022 - Symmetry & Dots & Full Dots & Triangles -158043 - 0x00024 (Right 3) - 0x00023 - Symmetry & Dots & Full Dots & Triangles -158044 - 0x00025 (Right 4) - 0x00024 - Symmetry & Dots & Full Dots & Triangles -158045 - 0x00026 (Right 5) - 0x00025 - Symmetry & Dots & Full Dots & Triangles +158041 - 0x00022 (Right 1) - True - Symmetry & Full Dots & Triangles +158042 - 0x00023 (Right 2) - 0x00022 - Symmetry & Full Dots & Triangles +158043 - 0x00024 (Right 3) - 0x00023 - Symmetry & Full Dots & Triangles +158044 - 0x00025 (Right 4) - 0x00024 - Symmetry & Full Dots & Triangles +158045 - 0x00026 (Right 5) - 0x00025 - Symmetry & Full Dots & Triangles 158046 - 0x0007C (Back 1) - 0x00026 - Symmetry & Dots & Colored Dots 158047 - 0x0007E (Back 2) - 0x0007C - Symmetry & Dots & Colored Dots 158048 - 0x00075 (Back 3) - 0x0007E - Symmetry & Dots & Colored Dots @@ -122,8 +122,8 @@ Symmetry Island Lower (Symmetry Island) - Symmetry Island Upper - 0x18269: 158052 - 0x00065 (Left 1) - 0x00079 - Symmetry & Dots & Colored Dots 158053 - 0x0006D (Left 2) - 0x00065 - Symmetry & Colored Squares 158054 - 0x00072 (Left 3) - 0x0006D - Symmetry & Stars -158055 - 0x0006F (Left 4) - 0x00072 - Symmetry & Stars & Stars + Same Colored Symbol & Colored Squares -158056 - 0x00070 (Left 5) - 0x0006F - Symmetry & Stars & Stars + Same Colored Symbol & Colored Squares +158055 - 0x0006F (Left 4) - 0x00072 - Symmetry & Stars + Same Colored Symbol & Colored Squares +158056 - 0x00070 (Left 5) - 0x0006F - Symmetry & Stars + Same Colored Symbol & Colored Squares 158057 - 0x00071 (Left 6) - 0x00070 - Symmetry & Colored Dots & Eraser 158058 - 0x00076 (Left 7) - 0x00071 - Symmetry & Dots & Eraser 158059 - 0x009B8 (Scenery Outlines 1) - True - Symmetry @@ -136,12 +136,12 @@ Door - 0x18269 (Upper) - 0x1C349 159000 - 0x0332B (Glass Factory Black Line Reflection EP) - True - True Symmetry Island Upper (Symmetry Island): -158065 - 0x00A52 (Laser Yellow 1) - True - Symmetry & Colored Dots -158066 - 0x00A57 (Laser Yellow 2) - 0x00A52 - Symmetry & Colored Dots -158067 - 0x00A5B (Laser Yellow 3) - 0x00A57 - Symmetry & Colored Dots -158068 - 0x00A61 (Laser Blue 1) - 0x00A52 - Symmetry & Colored Dots -158069 - 0x00A64 (Laser Blue 2) - 0x00A61 & 0x00A57 - Symmetry & Colored Dots -158070 - 0x00A68 (Laser Blue 3) - 0x00A64 & 0x00A5B - Symmetry & Colored Dots +158065 - 0x00A52 (Laser Yellow 1) - True - Colored Dots +158066 - 0x00A57 (Laser Yellow 2) - 0x00A52 - Colored Dots +158067 - 0x00A5B (Laser Yellow 3) - 0x00A57 - Colored Dots +158068 - 0x00A61 (Laser Blue 1) - 0x00A52 - Colored Dots +158069 - 0x00A64 (Laser Blue 2) - 0x00A61 & 0x00A57 - Colored Dots +158070 - 0x00A68 (Laser Blue 3) - 0x00A64 & 0x00A5B - Colored Dots 158700 - 0x0360D (Laser Panel) - 0x00A68 - True Laser - 0x00509 (Laser) - 0x0360D 159001 - 0x03367 (Glass Factory Black Line EP) - True - True @@ -157,7 +157,7 @@ Desert Obelisk (Desert) - Entry - True: 159709 - 0x00359 (Obelisk) - True - True Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444: -158652 - 0x0CC7B (Vault Panel) - True - Dots & Full Dots & Rotated Shapers & Negative Shapers & Stars & Stars + Same Colored Symbol +158652 - 0x0CC7B (Vault Panel) - True - Full Dots & Rotated Shapers & Negative Shapers & Stars + Same Colored Symbol Door - 0x03444 (Vault Door) - 0x0CC7B 158602 - 0x17CE7 (Discard) - True - Arrows & Triangles 158076 - 0x00698 (Surface 1) - True - True @@ -270,7 +270,7 @@ Door - 0x17C07 (Entry 2) - 0x17C09 Quarry (Quarry) - Quarry Stoneworks Ground Floor - 0x02010: 159802 - 0xFFD01 (Inside Reached Independently) - True - True -158121 - 0x01E5A (Stoneworks Entry Left Panel) - True - Stars & Stars + Same Colored Symbol & Eraser +158121 - 0x01E5A (Stoneworks Entry Left Panel) - True - Stars + Same Colored Symbol & Eraser 158122 - 0x01E59 (Stoneworks Entry Right Panel) - True - Triangles Door - 0x02010 (Stoneworks Entry) - 0x01E59 & 0x01E5A @@ -299,14 +299,14 @@ Quarry Stoneworks Upper Floor (Quarry Stoneworks) - Quarry Stoneworks Lift - 0x0 158135 - 0x005F1 (Upper Row 2) - 0x00557 - Colored Squares & Eraser 158136 - 0x00620 (Upper Row 3) - 0x005F1 - Colored Squares & Eraser 158137 - 0x009F5 (Upper Row 4) - 0x00620 - Colored Squares & Eraser -158138 - 0x0146C (Upper Row 5) - 0x009F5 - Stars & Stars + Same Colored Symbol & Eraser -158139 - 0x3C12D (Upper Row 6) - 0x0146C - Stars & Stars + Same Colored Symbol & Eraser -158140 - 0x03686 (Upper Row 7) - 0x3C12D - Stars & Stars + Same Colored Symbol & Eraser -158141 - 0x014E9 (Upper Row 8) - 0x03686 - Stars & Stars + Same Colored Symbol & Eraser +158138 - 0x0146C (Upper Row 5) - 0x009F5 - Stars + Same Colored Symbol & Eraser +158139 - 0x3C12D (Upper Row 6) - 0x0146C - Stars + Same Colored Symbol & Eraser +158140 - 0x03686 (Upper Row 7) - 0x3C12D - Stars + Same Colored Symbol & Eraser +158141 - 0x014E9 (Upper Row 8) - 0x03686 - Stars + Same Colored Symbol & Eraser 158142 - 0x03677 (Stairs Panel) - True - Colored Squares & Eraser Door - 0x0368A (Stairs) - 0x03677 158143 - 0x3C125 (Control Room Left) - 0x014E9 - Black/White Squares & Dots & Eraser & Symmetry -158144 - 0x0367C (Control Room Right) - 0x014E9 - Colored Squares & Dots & Eraser & Stars & Stars + Same Colored Symbol +158144 - 0x0367C (Control Room Right) - 0x014E9 - Colored Squares & Dots & Eraser & Stars + Same Colored Symbol 159411 - 0x0069D (Ramp EP) - 0x03676 & 0x275FF - True 159413 - 0x00614 (Lift EP) - 0x275FF & 0x03675 - True @@ -340,13 +340,13 @@ Door - 0x3865F (Second Barrier) - 0x38663 158158 - 0x021B7 (Back First Row 3) - 0x021B6 - Shapers & Negative Shapers & Eraser 158159 - 0x021BB (Back First Row 4) - 0x021B7 - Shapers & Negative Shapers & Eraser 158160 - 0x09DB5 (Back First Row 5) - 0x021BB - Shapers & Negative Shapers & Eraser -158161 - 0x09DB1 (Back First Row 6) - 0x09DB5 - Stars & Stars + Same Colored Symbol & Eraser & Colored Squares -158162 - 0x3C124 (Back First Row 7) - 0x09DB1 - Stars & Stars + Same Colored Symbol & Eraser & Colored Squares -158163 - 0x09DB3 (Back First Row 8) - 0x3C124 - Stars & Stars + Same Colored Symbol & Eraser & Colored Squares & Triangles -158164 - 0x09DB4 (Back First Row 9) - 0x09DB3 - Stars & Stars + Same Colored Symbol & Eraser & Colored Squares & Triangles +158161 - 0x09DB1 (Back First Row 6) - 0x09DB5 - Stars + Same Colored Symbol & Eraser & Colored Squares +158162 - 0x3C124 (Back First Row 7) - 0x09DB1 - Stars + Same Colored Symbol & Eraser & Colored Squares +158163 - 0x09DB3 (Back First Row 8) - 0x3C124 - Stars + Same Colored Symbol & Eraser & Colored Squares & Triangles +158164 - 0x09DB4 (Back First Row 9) - 0x09DB3 - Stars + Same Colored Symbol & Eraser & Colored Squares & Triangles 158165 - 0x275FA (Hook Control) - True - Shapers & Eraser 158167 - 0x0A3CB (Back Second Row 1) - 0x09DB4 - Black/White Squares & Colored Squares & Eraser & Shapers -158168 - 0x0A3CC (Back Second Row 2) - 0x0A3CB - Stars & Stars + Same Colored Symbol & Eraser & Shapers +158168 - 0x0A3CC (Back Second Row 2) - 0x0A3CB - Stars + Same Colored Symbol & Eraser & Shapers 158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Triangles & Eraser & Shapers 159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True @@ -421,7 +421,7 @@ Door - 0x01A0E (Hedge Maze 4 Exit) - 0x01A0F Keep 2nd Pressure Plate (Keep) - Keep 3rd Pressure Plate - True: 158199 - 0x0A3B9 (Reset Pressure Plates 2) - True - True -158200 - 0x01BE9 (Pressure Plates 2) - 0x0A3B9 - Stars & Stars + Same Colored Symbol & Triangles +158200 - 0x01BE9 (Pressure Plates 2) - 0x0A3B9 - Stars + Same Colored Symbol & Triangles Door - 0x01BEA (Pressure Plates 2 Exit) - 0x01BE9 Keep 3rd Pressure Plate (Keep) - Keep 4th Pressure Plate - 0x01CD5: @@ -441,7 +441,7 @@ Keep Tower (Keep) - Keep - 0x04F8F: 158206 - 0x0361B (Tower Shortcut Panel) - True - True Door - 0x04F8F (Tower Shortcut) - 0x0361B 158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True -158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol +158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Colored Squares & Stars + Same Colored Symbol Laser - 0x014BB (Laser) - 0x0360E | 0x03317 159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True 159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True @@ -537,11 +537,11 @@ Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8 158221 - 0x28AE3 (Vines) - 0x18590 - True 158222 - 0x28938 (Apple Tree) - 0x28AE3 - True 158223 - 0x079DF (Triple Exit) - 0x28938 - True -158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Rotated Shapers & Dots & Full Dots & Colored Squares -158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Rotated Shapers & Dots & Full Dots & Stars -158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Shapers & Negative Shapers & Dots & Full Dots -158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Rotated Shapers & Dots & Full Dots -158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Rotated Shapers & Dots & Full Dots & Triangles +158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Rotated Shapers & Full Dots & Colored Squares +158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Rotated Shapers & Full Dots & Stars +158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Shapers & Negative Shapers & Full Dots +158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Rotated Shapers & Full Dots +158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Rotated Shapers & Full Dots & Triangles Door - 0x034F5 (Wooden Roof Stairs) - 0x28AC1 158225 - 0x28998 (RGB House Entry Panel) - True - Stars & Rotated Shapers Door - 0x28A61 (RGB House Entry) - 0x28998 @@ -575,7 +575,7 @@ Town Red Rooftop (Town): 158224 - 0x28B39 (Tall Hexagonal) - 0x079DF - True Town Wooden Rooftop (Town): -158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Rotated Shapers & Dots & Eraser & Full Dots +158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Rotated Shapers & Eraser & Full Dots Town Church (Town): 158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True @@ -631,8 +631,8 @@ Theater (Theater) - Town - 0x0A16D | 0x3CCDF: 158660 - 0x03549 (Challenge Video) - 0x00815 & 0x0356B - True 158661 - 0x0354F (Shipwreck Video) - 0x00815 & 0x03535 - True 158662 - 0x03545 (Mountain Video) - 0x00815 & 0x03542 - True -158249 - 0x0A168 (Exit Left Panel) - True - Black/White Squares & Stars & Stars + Same Colored Symbol & Shapers -158250 - 0x33AB2 (Exit Right Panel) - True - Black/White Squares & Stars & Stars + Same Colored Symbol & Shapers +158249 - 0x0A168 (Exit Left Panel) - True - Black/White Squares & Stars + Same Colored Symbol & Shapers +158250 - 0x33AB2 (Exit Right Panel) - True - Black/White Squares & Stars + Same Colored Symbol & Shapers Door - 0x0A16D (Exit Left) - 0x0A168 Door - 0x3CCDF (Exit Right) - 0x33AB2 158608 - 0x17CF7 (Discard) - True - Arrows & Triangles @@ -851,8 +851,8 @@ Swamp Maze (Swamp) - Swamp Laser Area - 0x17C0A & 0x17E07: Swamp Laser Area (Swamp) - Outside Swamp - 0x2D880: 158711 - 0x03615 (Laser Panel) - True - True Laser - 0x00BF6 (Laser) - 0x03615 -158341 - 0x17C05 (Laser Shortcut Left Panel) - True - Shapers & Colored Squares & Stars & Stars + Same Colored Symbol -158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Colored Squares & Stars & Stars + Same Colored Symbol +158341 - 0x17C05 (Laser Shortcut Left Panel) - True - Shapers & Colored Squares & Stars + Same Colored Symbol +158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Colored Squares & Stars + Same Colored Symbol Door - 0x2D880 (Laser Shortcut) - 0x17C02 ==Treehouse== @@ -878,7 +878,7 @@ Door - 0x0C309 (First Door) - 0x0288C 159210 - 0x33721 (Buoy EP) - 0x17C95 - True Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310: -158345 - 0x02886 (Second Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles +158345 - 0x02886 (Second Door Panel) - True - Stars + Same Colored Symbol & Triangles Door - 0x0C310 (Second Door) - 0x02886 Treehouse Yellow Bridge (Treehouse) - Treehouse After Yellow Bridge - 0x17DC4: @@ -907,57 +907,57 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1 158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots & Triangles Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2: -158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars & Stars + Same Colored Symbol & Colored Squares -158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars & Stars + Same Colored Symbol & Colored Squares -158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars & Stars + Same Colored Symbol & Colored Squares -158394 - 0x17CE3 (Right Orange Bridge 4 & Directional) - 0x17D8C - Stars & Stars + Same Colored Symbol & Colored Squares -158395 - 0x17DCD (Right Orange Bridge 5) - 0x17CE3 - Stars & Stars + Same Colored Symbol & Colored Squares -158396 - 0x17DB2 (Right Orange Bridge 6) - 0x17DCD - Stars & Stars + Same Colored Symbol & Colored Squares -158397 - 0x17DCC (Right Orange Bridge 7) - 0x17DB2 - Stars & Stars + Same Colored Symbol & Colored Squares -158398 - 0x17DCA (Right Orange Bridge 8) - 0x17DCC - Stars & Stars + Same Colored Symbol & Colored Squares -158399 - 0x17D8E (Right Orange Bridge 9) - 0x17DCA - Stars & Stars + Same Colored Symbol & Colored Squares -158400 - 0x17DB7 (Right Orange Bridge 10 & Directional) - 0x17D8E - Stars & Stars + Same Colored Symbol & Colored Squares -158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars & Stars + Same Colored Symbol & Colored Squares -158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars & Stars + Same Colored Symbol & Colored Squares +158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars + Same Colored Symbol & Colored Squares +158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars + Same Colored Symbol & Colored Squares +158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars + Same Colored Symbol & Colored Squares +158394 - 0x17CE3 (Right Orange Bridge 4 & Directional) - 0x17D8C - Stars + Same Colored Symbol & Colored Squares +158395 - 0x17DCD (Right Orange Bridge 5) - 0x17CE3 - Stars + Same Colored Symbol & Colored Squares +158396 - 0x17DB2 (Right Orange Bridge 6) - 0x17DCD - Stars + Same Colored Symbol & Colored Squares +158397 - 0x17DCC (Right Orange Bridge 7) - 0x17DB2 - Stars + Same Colored Symbol & Colored Squares +158398 - 0x17DCA (Right Orange Bridge 8) - 0x17DCC - Stars + Same Colored Symbol & Colored Squares +158399 - 0x17D8E (Right Orange Bridge 9) - 0x17DCA - Stars + Same Colored Symbol & Colored Squares +158400 - 0x17DB7 (Right Orange Bridge 10 & Directional) - 0x17D8E - Stars + Same Colored Symbol & Colored Squares +158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars + Same Colored Symbol & Colored Squares +158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars + Same Colored Symbol & Colored Squares Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D: 158404 - 0x037FF (Drawbridge Panel) - True - Stars Door - 0x0C32D (Drawbridge) - 0x037FF Treehouse Second Purple Bridge (Treehouse) - Treehouse Left Orange Bridge - 0x17DC6: -158362 - 0x17D9B (Second Purple Bridge 1) - True - Stars & Stars + Same Colored Symbol & Black/White Squares & Colored Squares -158363 - 0x17D99 (Second Purple Bridge 2) - 0x17D9B - Stars & Stars + Same Colored Symbol & Black/White Squares & Colored Squares -158364 - 0x17DAA (Second Purple Bridge 3) - 0x17D99 - Stars & Stars + Same Colored Symbol & Black/White Squares & Colored Squares -158365 - 0x17D97 (Second Purple Bridge 4) - 0x17DAA - Stars & Stars + Same Colored Symbol & Black/White Squares & Colored Squares -158366 - 0x17BDF (Second Purple Bridge 5) - 0x17D97 - Stars & Stars + Same Colored Symbol & Colored Squares -158367 - 0x17D91 (Second Purple Bridge 6) - 0x17BDF - Stars & Stars + Same Colored Symbol & Black/White Squares & Colored Squares -158368 - 0x17DC6 (Second Purple Bridge 7) - 0x17D91 - Stars & Stars + Same Colored Symbol & Colored Squares +158362 - 0x17D9B (Second Purple Bridge 1) - True - Stars + Same Colored Symbol & Black/White Squares & Colored Squares +158363 - 0x17D99 (Second Purple Bridge 2) - 0x17D9B - Stars + Same Colored Symbol & Black/White Squares & Colored Squares +158364 - 0x17DAA (Second Purple Bridge 3) - 0x17D99 - Stars + Same Colored Symbol & Black/White Squares & Colored Squares +158365 - 0x17D97 (Second Purple Bridge 4) - 0x17DAA - Stars + Same Colored Symbol & Black/White Squares & Colored Squares +158366 - 0x17BDF (Second Purple Bridge 5) - 0x17D97 - Stars + Same Colored Symbol & Colored Squares +158367 - 0x17D91 (Second Purple Bridge 6) - 0x17BDF - Stars + Same Colored Symbol & Black/White Squares & Colored Squares +158368 - 0x17DC6 (Second Purple Bridge 7) - 0x17D91 - Stars + Same Colored Symbol & Colored Squares Treehouse Left Orange Bridge (Treehouse) - Treehouse Laser Room Front Platform - 0x17DDB - Treehouse Laser Room Back Platform - 0x17DDB - Treehouse Burned House - 0x17DDB: -158376 - 0x17DB3 (Left Orange Bridge 1) - True - Stars & Stars + Same Colored Symbol & Triangles -158377 - 0x17DB5 (Left Orange Bridge 2) - 0x17DB3 - Stars & Stars + Same Colored Symbol & Triangles -158378 - 0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Stars & Stars + Same Colored Symbol & Triangles -158379 - 0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Stars & Stars + Same Colored Symbol & Triangles -158380 - 0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers -158381 - 0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers -158382 - 0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers -158383 - 0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers -158384 - 0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Stars & Black/White Squares & Stars + Same Colored Symbol -158385 - 0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Stars & Stars + Same Colored Symbol & Shapers -158386 - 0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Stars & Stars + Same Colored Symbol & Shapers -158387 - 0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers -158388 - 0x17DAE (Left Orange Bridge 13) - 0x17DEC - Stars & Stars + Same Colored Symbol & Shapers & Triangles -158389 - 0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers -158390 - 0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Stars & Stars + Same Colored Symbol & Shapers & Triangles +158376 - 0x17DB3 (Left Orange Bridge 1) - True - Stars + Same Colored Symbol & Triangles +158377 - 0x17DB5 (Left Orange Bridge 2) - 0x17DB3 - Stars + Same Colored Symbol & Triangles +158378 - 0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Stars + Same Colored Symbol & Triangles +158379 - 0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Stars + Same Colored Symbol & Triangles +158380 - 0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Black/White Squares & Stars + Same Colored Symbol & Shapers +158381 - 0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Black/White Squares & Stars + Same Colored Symbol & Shapers +158382 - 0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Black/White Squares & Stars + Same Colored Symbol & Shapers +158383 - 0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Black/White Squares & Stars + Same Colored Symbol & Shapers +158384 - 0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Black/White Squares & Stars + Same Colored Symbol +158385 - 0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Stars + Same Colored Symbol & Shapers +158386 - 0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Stars + Same Colored Symbol & Shapers +158387 - 0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Black/White Squares & Stars + Same Colored Symbol & Shapers +158388 - 0x17DAE (Left Orange Bridge 13) - 0x17DEC - Stars + Same Colored Symbol & Shapers & Triangles +158389 - 0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Black/White Squares & Stars + Same Colored Symbol & Shapers +158390 - 0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Stars + Same Colored Symbol & Shapers & Triangles Treehouse Green Bridge (Treehouse) - Treehouse Green Bridge Front House - 0x17E61 - Treehouse Green Bridge Left House - 0x17E61: -158369 - 0x17E3C (Green Bridge 1) - True - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158370 - 0x17E4D (Green Bridge 2) - 0x17E3C - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158371 - 0x17E4F (Green Bridge 3) - 0x17E4D - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158372 - 0x17E52 (Green Bridge 4 & Directional) - 0x17E4F - Stars & Rotated Shapers & Negative Shapers & Stars + Same Colored Symbol -158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol -158375 - 0x17E61 (Green Bridge 7) - 0x17E5F - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol +158369 - 0x17E3C (Green Bridge 1) - True - Shapers & Negative Shapers & Stars + Same Colored Symbol +158370 - 0x17E4D (Green Bridge 2) - 0x17E3C - Shapers & Negative Shapers & Stars + Same Colored Symbol +158371 - 0x17E4F (Green Bridge 3) - 0x17E4D - Shapers & Negative Shapers & Stars + Same Colored Symbol +158372 - 0x17E52 (Green Bridge 4 & Directional) - 0x17E4F - Rotated Shapers & Negative Shapers & Stars + Same Colored Symbol +158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Shapers & Negative Shapers & Stars + Same Colored Symbol +158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Shapers & Negative Shapers & Stars + Same Colored Symbol +158375 - 0x17E61 (Green Bridge 7) - 0x17E5F - Shapers & Negative Shapers & Stars + Same Colored Symbol Treehouse Green Bridge Front House (Treehouse): 158610 - 0x17FA9 (Green Bridge Discard) - True - Arrows & Triangles @@ -1019,17 +1019,17 @@ Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39: Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay - Mountain Floor 1 Trash Pillar - TrueOneWay - Mountain Floor 1 Back Section - TrueOneWay: 158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots -158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots & Stars & Stars + Same Colored Symbol -158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Stars & Stars + Same Colored Symbol -158412 - 0x09E69 (Right Row 4) - 0x09E72 - Black/White Squares & Eraser & Stars & Stars + Same Colored Symbol -158413 - 0x09E7B (Right Row 5) - 0x09E69 - Dots & Full Dots & Triangles +158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots & Stars + Same Colored Symbol +158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Stars + Same Colored Symbol +158412 - 0x09E69 (Right Row 4) - 0x09E72 - Black/White Squares & Eraser & Stars + Same Colored Symbol +158413 - 0x09E7B (Right Row 5) - 0x09E69 - Full Dots & Triangles 158414 - 0x09E73 (Left Row 1) - True - Dots & Black/White Squares 158415 - 0x09E75 (Left Row 2) - 0x09E73 - Arrows & Black/White Squares 158416 - 0x09E78 (Left Row 3) - 0x09E75 - Arrows & Stars 158417 - 0x09E79 (Left Row 4) - 0x09E78 - Arrows & Shapers & Rotated Shapers -158418 - 0x09E6C (Left Row 5) - 0x09E79 - Arrows & Black/White Squares & Stars & Stars + Same Colored Symbol -158419 - 0x09E6F (Left Row 6) - 0x09E6C - Arrows & Dots & Full Dots -158420 - 0x09E6B (Left Row 7) - 0x09E6F - Arrows & Dots & Full Dots +158418 - 0x09E6C (Left Row 5) - 0x09E79 - Arrows & Black/White Squares & Stars + Same Colored Symbol +158419 - 0x09E6F (Left Row 6) - 0x09E6C - Arrows & Full Dots +158420 - 0x09E6B (Left Row 7) - 0x09E6F - Arrows & Full Dots 158424 - 0x09EAD (Trash Pillar 1) - True - Triangles & Arrows 158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Triangles & Arrows @@ -1044,10 +1044,10 @@ Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54: Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 Above The Abyss - True - Mountain Pink Bridge EP - TrueOneWay: -158426 - 0x09FD3 (Near Row 1) - True - Stars & Stars + Same Colored Symbol & Colored Squares -158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Stars + Same Colored Symbol & Triangles -158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Stars + Same Colored Symbol & Colored Squares & Eraser -158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Stars & Stars + Same Colored Symbol & Shapers & Eraser +158426 - 0x09FD3 (Near Row 1) - True - Stars + Same Colored Symbol & Colored Squares +158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars + Same Colored Symbol & Triangles +158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars + Same Colored Symbol & Colored Squares & Eraser +158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Stars + Same Colored Symbol & Shapers & Eraser 158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Symmetry & Triangles Door - 0x09FFB (Staircase Near) - 0x09FD8 @@ -1055,19 +1055,19 @@ Mountain Floor 2 Above The Abyss (Mountain Floor 2) - Mountain Floor 2 Elevator Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86 Mountain Floor 2 Light Bridge Room Near (Mountain Floor 2): -158431 - 0x09E86 (Light Bridge Controller Near) - True - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser +158431 - 0x09E86 (Light Bridge Controller Near) - True - Stars + Same Colored Symbol & Rotated Shapers & Eraser Mountain Floor 2 Beyond Bridge (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Far - 0x09E07 - Mountain Pink Bridge EP - TrueOneWay - Mountain Floor 2 - 0x09ED8: 158432 - 0x09FCC (Far Row 1) - True - Black/White Squares 158433 - 0x09FCE (Far Row 2) - 0x09FCC - Triangles 158434 - 0x09FCF (Far Row 3) - 0x09FCE - Stars -158435 - 0x09FD0 (Far Row 4) - 0x09FCF - Stars & Stars + Same Colored Symbol & Colored Squares +158435 - 0x09FD0 (Far Row 4) - 0x09FCF - Stars + Same Colored Symbol & Colored Squares 158436 - 0x09FD1 (Far Row 5) - 0x09FD0 - Dots 158437 - 0x09FD2 (Far Row 6) - 0x09FD1 - Shapers Door - 0x09E07 (Staircase Far) - 0x09FD2 Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2): -158438 - 0x09ED8 (Light Bridge Controller Far) - True - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser +158438 - 0x09ED8 (Light Bridge Controller Far) - True - Stars + Same Colored Symbol & Rotated Shapers & Eraser Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay: 158613 - 0x17F93 (Elevator Discard) - True - Arrows & Triangles @@ -1095,10 +1095,10 @@ Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1 Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961: 158522 - 0x0383A (Right Pillar 1) - True - Stars 158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots -158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots & Triangles +158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Full Dots & Triangles 158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry & Triangles 158526 - 0x0383D (Left Pillar 1) - True - Triangles -158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares & Stars & Stars + Same Colored Symbol +158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares & Stars + Same Colored Symbol 158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers 158529 - 0x339BB (Left Pillar 4) - 0x03859 - Triangles & Symmetry @@ -1127,16 +1127,16 @@ Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x01 158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares 158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares 158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots -158454 - 0x00190 (Blue Tunnel Right First 1) - True - Dots & Full Dots & Triangles & Arrows -158455 - 0x00558 (Blue Tunnel Right First 2) - 0x00190 - Dots & Full Dots & Triangles & Arrows -158456 - 0x00567 (Blue Tunnel Right First 3) - 0x00558 - Dots & Full Dots & Triangles & Arrows -158457 - 0x006FE (Blue Tunnel Right First 4) - 0x00567 - Dots & Full Dots & Triangles & Arrows +158454 - 0x00190 (Blue Tunnel Right First 1) - True - Full Dots & Triangles & Arrows +158455 - 0x00558 (Blue Tunnel Right First 2) - 0x00190 - Full Dots & Triangles & Arrows +158456 - 0x00567 (Blue Tunnel Right First 3) - 0x00558 - Full Dots & Triangles & Arrows +158457 - 0x006FE (Blue Tunnel Right First 4) - 0x00567 - Full Dots & Triangles & Arrows 158458 - 0x01A0D (Blue Tunnel Left First 1) - True - Symmetry & Triangles & Arrows 158459 - 0x008B8 (Blue Tunnel Left Second 1) - True - Triangles & Colored Squares & Arrows 158460 - 0x00973 (Blue Tunnel Left Second 2) - 0x008B8 - Triangles & Colored Squares & Arrows -158461 - 0x0097B (Blue Tunnel Left Second 3) - 0x00973 - Triangles & Colored Squares & Arrows & Stars & Stars + Same Colored Symbol -158462 - 0x0097D (Blue Tunnel Left Second 4) - 0x0097B - Triangles & Colored Squares & Arrows & Stars & Stars + Same Colored Symbol -158463 - 0x0097E (Blue Tunnel Left Second 5) - 0x0097D - Triangles & Colored Squares & Arrows & Stars & Stars + Same Colored Symbol +158461 - 0x0097B (Blue Tunnel Left Second 3) - 0x00973 - Triangles & Colored Squares & Arrows & Stars + Same Colored Symbol +158462 - 0x0097D (Blue Tunnel Left Second 4) - 0x0097B - Triangles & Colored Squares & Arrows & Stars + Same Colored Symbol +158463 - 0x0097E (Blue Tunnel Left Second 5) - 0x0097D - Triangles & Colored Squares & Arrows & Stars + Same Colored Symbol 158464 - 0x00994 (Blue Tunnel Right Second 1) - True - Rotated Shapers & Triangles 158465 - 0x334D5 (Blue Tunnel Right Second 2) - 0x00994 - Rotated Shapers & Triangles 158466 - 0x00995 (Blue Tunnel Right Second 3) - 0x334D5 - Rotated Shapers & Triangles @@ -1146,30 +1146,30 @@ Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x01 158470 - 0x018A0 (Blue Tunnel Right Third 1) - True - Shapers & Symmetry & Eraser 158471 - 0x00A72 (Blue Tunnel Left Fourth 1) - True - Shapers & Negative Shapers & Triangles 158472 - 0x32962 (First Floor Left) - True - Rotated Shapers & Dots -158473 - 0x32966 (First Floor Grounded) - True - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Triangles +158473 - 0x32966 (First Floor Grounded) - True - Black/White Squares & Stars + Same Colored Symbol & Shapers & Triangles 158474 - 0x01A31 (First Floor Middle) - True - Colored Squares -158475 - 0x00B71 (First Floor Right) - True - Colored Squares & Stars & Stars + Same Colored Symbol & Eraser & Shapers & Negative Shapers & Dots +158475 - 0x00B71 (First Floor Right) - True - Colored Squares & Stars + Same Colored Symbol & Eraser & Shapers & Negative Shapers & Dots 158478 - 0x288EA (First Wooden Beam) - True - Colored Squares & Black/White Squares & Eraser -158479 - 0x288FC (Second Wooden Beam) - True - Black/White Squares & Stars & Stars + Same Colored Symbol & Eraser -158480 - 0x289E7 (Third Wooden Beam) - True - Black/White Squares & Stars & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Eraser +158479 - 0x288FC (Second Wooden Beam) - True - Black/White Squares & Stars + Same Colored Symbol & Eraser +158480 - 0x289E7 (Third Wooden Beam) - True - Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Eraser 158481 - 0x288AA (Fourth Wooden Beam) - True - Stars & Shapers & Eraser -158482 - 0x17FB9 (Left Upstairs Single) - True - Stars & Dots & Full Dots -158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Dots & Full Dots & Black/White Squares -158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Dots & Full Dots & Stars -158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Dots & Full Dots & Shapers -158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Dots & Full Dots & Triangles -158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Dots & Full Dots & Triangles & Eraser +158482 - 0x17FB9 (Left Upstairs Single) - True - Stars & Full Dots +158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Full Dots & Black/White Squares +158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Full Dots & Stars +158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Full Dots & Shapers +158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Full Dots & Triangles +158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Full Dots & Triangles & Eraser 158488 - 0x0008F (Right Upstairs Left Row 1) - True - Dots 158489 - 0x0006B (Right Upstairs Left Row 2) - 0x0008F - Black/White Squares & Colored Squares -158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol -158491 - 0x0008C (Right Upstairs Left Row 4) - 0x0008B - Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Shapers -158492 - 0x0008A (Right Upstairs Left Row 5) - 0x0008C - Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol -158493 - 0x00089 (Right Upstairs Left Row 6) - 0x0008A - Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Rotated Shapers -158494 - 0x0006A (Right Upstairs Left Row 7) - 0x00089 - Stars & Stars + Same Colored Symbol & Shapers & Negative Shapers +158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Black/White Squares & Colored Squares & Stars + Same Colored Symbol +158491 - 0x0008C (Right Upstairs Left Row 4) - 0x0008B - Black/White Squares & Colored Squares & Stars + Same Colored Symbol & Shapers +158492 - 0x0008A (Right Upstairs Left Row 5) - 0x0008C - Black/White Squares & Colored Squares & Stars + Same Colored Symbol +158493 - 0x00089 (Right Upstairs Left Row 6) - 0x0008A - Black/White Squares & Colored Squares & Stars + Same Colored Symbol & Rotated Shapers +158494 - 0x0006A (Right Upstairs Left Row 7) - 0x00089 - Stars + Same Colored Symbol & Shapers & Negative Shapers 158495 - 0x0006C (Right Upstairs Left Row 8) - 0x0006A - Dots & Shapers & Negative Shapers & Eraser 158496 - 0x00027 (Right Upstairs Right Row 1) - True - Black/White Squares & Colored Squares & Eraser & Symmetry 158497 - 0x00028 (Right Upstairs Right Row 2) - 0x00027 - Black/White Squares & Colored Squares & Eraser & Symmetry -158498 - 0x00029 (Right Upstairs Right Row 3) - 0x00028 - Stars & Stars + Same Colored Symbol & Eraser & Symmetry +158498 - 0x00029 (Right Upstairs Right Row 3) - 0x00028 - Stars + Same Colored Symbol & Eraser & Symmetry 158476 - 0x09DD5 (Lone Pillar) - True - Triangles & Dots Door - 0x019A5 (Pillar Door) - 0x09DD5 158449 - 0x021D7 (Mountain Shortcut Panel) - True - Triangles @@ -1179,7 +1179,7 @@ Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2 159341 - 0x3397C (Skylight EP) - True - True Caves Path to Challenge (Caves) - Challenge - 0x0A19A: -158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Shapers & Stars + Same Colored Symbol & Triangles +158477 - 0x0A16E (Challenge Entry Panel) - True - Shapers & Stars + Same Colored Symbol & Triangles Door - 0x0A19A (Challenge Entry) - 0x0A16E ==Challenge== From 6ad042b3498d293e8dff5c8dde1539a1e70ca073 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 24 Apr 2025 21:56:52 +0200 Subject: [PATCH 41/46] Core: Add Region.add_event (#2965) * region.add_event function * Make it return the location bc why not * Actually item bc that seems more useful * Update BaseClasses.py Co-authored-by: Aaron Wagener * Update BaseClasses.py Co-authored-by: Aaron Wagener * add all the requested features from code review * oop * roughly sort args in order of importance (imo) * Fix typing --------- Co-authored-by: Aaron Wagener --- BaseClasses.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/BaseClasses.py b/BaseClasses.py index bf115e3f9c..e59e96e17d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1198,6 +1198,48 @@ class Region: for location, address in locations.items(): self.locations.append(location_type(self.player, location, address, self)) + def add_event( + self, + location_name: str, + item_name: str | None = None, + rule: Callable[[CollectionState], bool] | None = None, + location_type: type[Location] | None = None, + item_type: type[Item] | None = None, + show_in_spoiler: bool = True, + ) -> Item: + """ + Adds an event location/item pair to the region. + + :param location_name: Name for the event location. + :param item_name: Name for the event item. If not provided, defaults to location_name. + :param rule: Callable to determine access for this event location within its region. + :param location_type: Location class to create the event location with. Defaults to BaseClasses.Location. + :param item_type: Item class to create the event item with. Defaults to BaseClasses.Item. + :param show_in_spoiler: Will be passed along to the created event Location's show_in_spoiler attribute. + :return: The created Event Item + """ + if location_type is None: + location_type = Location + + if item_name is None: + item_name = location_name + + if item_type is None: + item_type = Item + + event_location = location_type(self.player, location_name, None, self) + event_location.show_in_spoiler = show_in_spoiler + if rule is not None: + event_location.access_rule = rule + + event_item = item_type(item_name, ItemClassification.progression, None, self.player) + + event_location.place_locked_item(event_item) + + self.locations.append(event_location) + + return event_item + def connect(self, connecting_region: Region, name: Optional[str] = None, rule: Optional[Callable[[CollectionState], bool]] = None) -> Entrance: """ From 05c1751d293ea27fa4ee7c6f4797be772d111ae2 Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Thu, 24 Apr 2025 22:06:41 +0200 Subject: [PATCH 42/46] Core: Add "OptionCounter", use it for generic "StartInventory" and Witness "TrapWeights" (#3756) * CounterOption * bring back the negative exception for ItemDict * Backwards compatibility * ruff on witness * fix in calls * move the contains * comment * comment * Add option min and max values for CounterOption * Use min 0 for TrapWeights * This is safe now * ruff * This fits on one line again now * OptionCounter * Update Options.py * Couple more typing things * Update Options.py * Make StartInventory work again, also make LocationCounter theoretically work * Docs * more forceful wording * forced line break * Fix unit test (that wasn't breaking?) * Add trapweights to witness option presets to 'prove' that the unit test passes * Make it so you can order stuff * Update macros.html --- Options.py | 47 ++++++++++++++++--- Utils.py | 3 ++ WebHostLib/options.py | 4 +- .../templates/playerOptions/macros.html | 13 ++++- .../playerOptions/playerOptions.html | 12 +++-- .../templates/weightedOptions/macros.html | 13 ++++- .../weightedOptions/weightedOptions.html | 6 ++- docs/options api.md | 9 +++- test/webhost/test_option_presets.py | 4 +- worlds/witness/options.py | 28 ++++++----- worlds/witness/presets.py | 6 +++ 11 files changed, 111 insertions(+), 34 deletions(-) diff --git a/Options.py b/Options.py index 95b9b468c6..6a6bbe5e77 100644 --- a/Options.py +++ b/Options.py @@ -1,6 +1,7 @@ from __future__ import annotations import abc +import collections import functools import logging import math @@ -866,15 +867,49 @@ class OptionDict(Option[typing.Dict[str, typing.Any]], VerifyKeys, typing.Mappin def __len__(self) -> int: return self.value.__len__() + # __getitem__ fallback fails for Counters, so we define this explicitly + def __contains__(self, item) -> bool: + return item in self.value -class ItemDict(OptionDict): + +class OptionCounter(OptionDict): + min: int | None = None + max: int | None = None + + def __init__(self, value: dict[str, int]) -> None: + super(OptionCounter, self).__init__(collections.Counter(value)) + + def verify(self, world: type[World], player_name: str, plando_options: PlandoOptions) -> None: + super(OptionCounter, self).verify(world, player_name, plando_options) + + range_errors = [] + + if self.max is not None: + range_errors += [ + f"\"{key}: {value}\" is higher than maximum allowed value {self.max}." + for key, value in self.value.items() if value > self.max + ] + + if self.min is not None: + range_errors += [ + f"\"{key}: {value}\" is lower than minimum allowed value {self.min}." + for key, value in self.value.items() if value < self.min + ] + + if range_errors: + range_errors = [f"For option {getattr(self, 'display_name', self)}:"] + range_errors + raise OptionError("\n".join(range_errors)) + + +class ItemDict(OptionCounter): verify_item_name = True - def __init__(self, value: typing.Dict[str, int]): - if any(item_count is None for item_count in value.values()): - raise Exception("Items must have counts associated with them. Please provide positive integer values in the format \"item\": count .") - if any(item_count < 1 for item_count in value.values()): - raise Exception("Cannot have non-positive item counts.") + min = 0 + + def __init__(self, value: dict[str, int]) -> None: + # Backwards compatibility: Cull 0s to make "in" checks behave the same as when this wasn't a OptionCounter + value = {item_name: amount for item_name, amount in value.items() if amount != 0} + super(ItemDict, self).__init__(value) diff --git a/Utils.py b/Utils.py index e4e94a45f6..46a0d106ef 100644 --- a/Utils.py +++ b/Utils.py @@ -429,6 +429,9 @@ class RestrictedUnpickler(pickle.Unpickler): def find_class(self, module: str, name: str) -> type: if module == "builtins" and name in safe_builtins: return getattr(builtins, name) + # used by OptionCounter + if module == "collections" and name == "Counter": + return collections.Counter # used by MultiServer -> savegame/multidata if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint", "SlotType", "NetworkSlot", "HintStatus"}: diff --git a/WebHostLib/options.py b/WebHostLib/options.py index 711762ee5f..38489cee3c 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -108,7 +108,7 @@ def option_presets(game: str) -> Response: f"Expected {option.special_range_names.keys()} or {option.range_start}-{option.range_end}." presets[preset_name][preset_option_name] = option.value - elif isinstance(option, (Options.Range, Options.OptionSet, Options.OptionList, Options.ItemDict)): + elif isinstance(option, (Options.Range, Options.OptionSet, Options.OptionList, Options.OptionCounter)): presets[preset_name][preset_option_name] = option.value elif isinstance(preset_option, str): # Ensure the option value is valid for Choice and Toggle options @@ -222,7 +222,7 @@ def generate_yaml(game: str): for key, val in options.copy().items(): key_parts = key.rsplit("||", 2) - # Detect and build ItemDict options from their name pattern + # Detect and build OptionCounter options from their name pattern if key_parts[-1] == "qty": if key_parts[0] not in options: options[key_parts[0]] = {} diff --git a/WebHostLib/templates/playerOptions/macros.html b/WebHostLib/templates/playerOptions/macros.html index 972f03175d..bbb3c75d12 100644 --- a/WebHostLib/templates/playerOptions/macros.html +++ b/WebHostLib/templates/playerOptions/macros.html @@ -111,10 +111,19 @@ {% endmacro %} -{% macro ItemDict(option_name, option) %} +{% macro OptionCounter(option_name, option) %} + {% set relevant_keys = option.valid_keys %} + {% if not relevant_keys %} + {% if option.verify_item_name %} + {% set relevant_keys = world.item_names %} + {% elif option.verify_location_name %} + {% set relevant_keys = world.location_names %} + {% endif %} + {% endif %} + {{ OptionTitle(option_name, option) }}
- {% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} + {% for item_name in (relevant_keys if relevant_keys is ordered else relevant_keys|sort) %}
diff --git a/WebHostLib/templates/playerOptions/playerOptions.html b/WebHostLib/templates/playerOptions/playerOptions.html index 7e2f0ee11c..5e82342126 100644 --- a/WebHostLib/templates/playerOptions/playerOptions.html +++ b/WebHostLib/templates/playerOptions/playerOptions.html @@ -93,8 +93,10 @@ {% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.ItemDict) and option.verify_item_name %} - {{ inputs.ItemDict(option_name, option) }} + {% elif issubclass(option, Options.OptionCounter) and ( + option.valid_keys or option.verify_item_name or option.verify_location_name + ) %} + {{ inputs.OptionCounter(option_name, option) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} {{ inputs.OptionList(option_name, option) }} @@ -133,8 +135,10 @@ {% elif issubclass(option, Options.FreeText) %} {{ inputs.FreeText(option_name, option) }} - {% elif issubclass(option, Options.ItemDict) and option.verify_item_name %} - {{ inputs.ItemDict(option_name, option) }} + {% elif issubclass(option, Options.OptionCounter) and ( + option.valid_keys or option.verify_item_name or option.verify_location_name + ) %} + {{ inputs.OptionCounter(option_name, option) }} {% elif issubclass(option, Options.OptionList) and option.valid_keys %} {{ inputs.OptionList(option_name, option) }} diff --git a/WebHostLib/templates/weightedOptions/macros.html b/WebHostLib/templates/weightedOptions/macros.html index d18d0f0b89..89ba0a0e6e 100644 --- a/WebHostLib/templates/weightedOptions/macros.html +++ b/WebHostLib/templates/weightedOptions/macros.html @@ -113,9 +113,18 @@ {{ TextChoice(option_name, option) }} {% endmacro %} -{% macro ItemDict(option_name, option, world) %} +{% macro OptionCounter(option_name, option, world) %} + {% set relevant_keys = option.valid_keys %} + {% if not relevant_keys %} + {% if option.verify_item_name %} + {% set relevant_keys = world.item_names %} + {% elif option.verify_location_name %} + {% set relevant_keys = world.location_names %} + {% endif %} + {% endif %} +
- {% for item_name in (option.valid_keys|sort if (option.valid_keys|length > 0) else world.item_names|sort) %} + {% for item_name in (relevant_keys if relevant_keys is ordered else relevant_keys|sort) %}
= 0) - for trap_name, item_definition in static_witness_logic.ALL_ITEMS.items() - if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP - }) - default = { - trap_name: item_definition.weight - for trap_name, item_definition in static_witness_logic.ALL_ITEMS.items() - if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP - } + valid_keys = _default_trap_weights.keys() + + min = 0 + + default = _default_trap_weights class PuzzleSkipAmount(Range): diff --git a/worlds/witness/presets.py b/worlds/witness/presets.py index 687d74f771..81dd28d68d 100644 --- a/worlds/witness/presets.py +++ b/worlds/witness/presets.py @@ -40,6 +40,8 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "trap_percentage": TrapPercentage.default, "puzzle_skip_amount": PuzzleSkipAmount.default, + "trap_weights": TrapWeights.default, + "hint_amount": HintAmount.default, "area_hint_percentage": AreaHintPercentage.default, "laser_hints": LaserHints.default, @@ -79,6 +81,8 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "trap_percentage": TrapPercentage.default, "puzzle_skip_amount": 15, + "trap_weights": TrapWeights.default, + "hint_amount": HintAmount.default, "area_hint_percentage": AreaHintPercentage.default, "laser_hints": LaserHints.default, @@ -118,6 +122,8 @@ witness_option_presets: Dict[str, Dict[str, Any]] = { "trap_percentage": TrapPercentage.default, "puzzle_skip_amount": 15, + "trap_weights": TrapWeights.default, + "hint_amount": HintAmount.default, "area_hint_percentage": AreaHintPercentage.default, "laser_hints": LaserHints.default, From d4110d3b2a0186b8498b140bc24667641f0def8b Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Thu, 24 Apr 2025 23:10:58 +0200 Subject: [PATCH 43/46] LttP: make progression health optional (#4918) --- worlds/alttp/ItemPool.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 0bcc189f9c..57ad01b9e4 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -490,12 +490,16 @@ def generate_itempool(world): # Otherwise, logic has some branches where having 4 hearts is one possible requirement (of several alternatives) # rather than making all hearts/heart pieces progression items (which slows down generation considerably) # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode) - if world.options.item_pool in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0): - next(item for item in items if item.name == 'Boss Heart Container').classification = ItemClassification.progression - elif world.options.item_pool in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4): + try: + next(item for item in items if item.name == 'Boss Heart Container').classification \ + |= ItemClassification.progression + except StopIteration: adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart') for i in range(4): - next(adv_heart_pieces).classification = ItemClassification.progression + try: + next(adv_heart_pieces).classification |= ItemClassification.progression + except StopIteration: + break # logically health tanking is an option, so rules should still resolve to something beatable world.required_medallions = (world.options.misery_mire_medallion.current_key.title(), world.options.turtle_rock_medallion.current_key.title()) From fc04192c992af1ec7288a774332107293cbbe06b Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Thu, 24 Apr 2025 17:14:42 -0400 Subject: [PATCH 44/46] Lingo: Use OptionCounter for trap_weights (#4920) --- worlds/lingo/options.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/worlds/lingo/options.py b/worlds/lingo/options.py index f9d04f68fc..faf5316e90 100644 --- a/worlds/lingo/options.py +++ b/worlds/lingo/options.py @@ -2,7 +2,7 @@ from dataclasses import dataclass from schema import And, Schema -from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions, StartInventoryPool, OptionDict, \ +from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions, StartInventoryPool, OptionCounter, \ OptionGroup from .items import TRAP_ITEMS @@ -222,13 +222,14 @@ class TrapPercentage(Range): default = 20 -class TrapWeights(OptionDict): +class TrapWeights(OptionCounter): """Specify the distribution of traps that should be placed into the pool. If you don't want a specific type of trap, set the weight to zero. """ display_name = "Trap Weights" - schema = Schema({trap_name: And(int, lambda n: n >= 0) for trap_name in TRAP_ITEMS}) + valid_keys = TRAP_ITEMS + min = 0 default = {trap_name: 1 for trap_name in TRAP_ITEMS} From abb6d7fbdb71ac8d29a867067a4a1abc15a24d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Bolduc?= <16137441+Jouramie@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:36:25 -0400 Subject: [PATCH 45/46] Stardew Valley: Replace all add_rule by set_rule #4909 --- worlds/stardew_valley/rules.py | 376 ++++++++++++++++----------------- 1 file changed, 188 insertions(+), 188 deletions(-) diff --git a/worlds/stardew_valley/rules.py b/worlds/stardew_valley/rules.py index 4b1ff2ad56..4257f856c8 100644 --- a/worlds/stardew_valley/rules.py +++ b/worlds/stardew_valley/rules.py @@ -3,7 +3,7 @@ import logging from typing import List, Dict, Set from BaseClasses import MultiWorld, CollectionState -from worlds.generic import Rules as MultiWorldRules +from worlds.generic.Rules import set_rule from . import locations from .bundles.bundle_room import BundleRoom from .content import StardewContent @@ -98,28 +98,28 @@ def set_rules(world): def set_isolated_locations_rules(logic: StardewLogic, multiworld, player): - MultiWorldRules.add_rule(multiworld.get_location("Old Master Cannoli", player), - logic.has(Fruit.sweet_gem_berry)) - MultiWorldRules.add_rule(multiworld.get_location("Galaxy Sword Shrine", player), - logic.has("Prismatic Shard")) - MultiWorldRules.add_rule(multiworld.get_location("Krobus Stardrop", player), - logic.money.can_spend(20000)) - MultiWorldRules.add_rule(multiworld.get_location("Demetrius's Breakthrough", player), - logic.money.can_have_earned_total(25000)) - MultiWorldRules.add_rule(multiworld.get_location("Pot Of Gold", player), - logic.season.has(Season.spring)) + set_rule(multiworld.get_location("Old Master Cannoli", player), + logic.has(Fruit.sweet_gem_berry)) + set_rule(multiworld.get_location("Galaxy Sword Shrine", player), + logic.has("Prismatic Shard")) + set_rule(multiworld.get_location("Krobus Stardrop", player), + logic.money.can_spend(20000)) + set_rule(multiworld.get_location("Demetrius's Breakthrough", player), + logic.money.can_have_earned_total(25000)) + set_rule(multiworld.get_location("Pot Of Gold", player), + logic.season.has(Season.spring)) def set_tool_rules(logic: StardewLogic, multiworld, player, content: StardewContent): if not content.features.tool_progression.is_progressive: return - MultiWorldRules.add_rule(multiworld.get_location("Purchase Fiberglass Rod", player), - (logic.skill.has_level(Skill.fishing, 2) & logic.money.can_spend(1800))) - MultiWorldRules.add_rule(multiworld.get_location("Purchase Iridium Rod", player), - (logic.skill.has_level(Skill.fishing, 6) & logic.money.can_spend(7500))) + set_rule(multiworld.get_location("Purchase Fiberglass Rod", player), + (logic.skill.has_level(Skill.fishing, 2) & logic.money.can_spend(1800))) + set_rule(multiworld.get_location("Purchase Iridium Rod", player), + (logic.skill.has_level(Skill.fishing, 6) & logic.money.can_spend(7500))) - MultiWorldRules.add_rule(multiworld.get_location("Copper Pan Cutscene", player), logic.received("Glittering Boulder Removed")) + set_rule(multiworld.get_location("Copper Pan Cutscene", player), logic.received("Glittering Boulder Removed")) materials = [None, "Copper", "Iron", "Gold", "Iridium"] tool = [Tool.hoe, Tool.pickaxe, Tool.axe, Tool.watering_can, Tool.trash_can, Tool.pan] @@ -127,7 +127,7 @@ def set_tool_rules(logic: StardewLogic, multiworld, player, content: StardewCont if previous is None: continue tool_upgrade_location = multiworld.get_location(f"{material} {tool} Upgrade", player) - MultiWorldRules.set_rule(tool_upgrade_location, logic.tool.has_tool(tool, previous)) + set_rule(tool_upgrade_location, logic.tool.has_tool(tool, previous)) def set_building_rules(logic: StardewLogic, multiworld, player, content: StardewContent): @@ -141,8 +141,8 @@ def set_building_rules(logic: StardewLogic, multiworld, player, content: Stardew location_name = building_progression.to_location_name(building.name) - MultiWorldRules.set_rule(multiworld.get_location(location_name, player), - logic.building.can_build(building.name)) + set_rule(multiworld.get_location(location_name, player), + logic.building.can_build(building.name)) def set_bundle_rules(bundle_rooms: List[BundleRoom], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -160,11 +160,11 @@ def set_bundle_rules(bundle_rooms: List[BundleRoom], logic: StardewLogic, multiw previous_bundle_name = f"Raccoon Request {num - 1}" bundle_rules = bundle_rules & logic.region.can_reach_location(previous_bundle_name) room_rules.append(bundle_rules) - MultiWorldRules.set_rule(location, bundle_rules) + set_rule(location, bundle_rules) if bundle_room.name == CCRoom.abandoned_joja_mart or bundle_room.name == CCRoom.raccoon_requests: continue room_location = f"Complete {bundle_room.name}" - MultiWorldRules.add_rule(multiworld.get_location(room_location, player), And(*room_rules)) + set_rule(multiworld.get_location(room_location, player), And(*room_rules)) def set_skills_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, content: StardewContent): @@ -176,12 +176,12 @@ def set_skills_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, c for level, level_name in skill_progression.get_randomized_level_names_by_level(skill): rule = logic.skill.can_earn_level(skill.name, level) location = multiworld.get_location(level_name, player) - MultiWorldRules.set_rule(location, rule) + set_rule(location, rule) if skill_progression.is_mastery_randomized(skill): rule = logic.skill.can_earn_mastery(skill.name) location = multiworld.get_location(skill.mastery_name, player) - MultiWorldRules.set_rule(location, rule) + set_rule(location, rule) def set_entrance_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -339,20 +339,20 @@ def set_ginger_island_rules(logic: StardewLogic, multiworld, player, world_optio set_boat_repair_rules(logic, multiworld, player) set_island_parrot_rules(logic, multiworld, player) - MultiWorldRules.add_rule(multiworld.get_location("Open Professor Snail Cave", player), - logic.has(Bomb.cherry_bomb)) - MultiWorldRules.add_rule(multiworld.get_location("Complete Island Field Office", player), - logic.walnut.can_complete_field_office()) + set_rule(multiworld.get_location("Open Professor Snail Cave", player), + logic.has(Bomb.cherry_bomb)) + set_rule(multiworld.get_location("Complete Island Field Office", player), + logic.walnut.can_complete_field_office()) set_walnut_rules(logic, multiworld, player, world_options) def set_boat_repair_rules(logic: StardewLogic, multiworld, player): - MultiWorldRules.add_rule(multiworld.get_location("Repair Boat Hull", player), - logic.has(Material.hardwood)) - MultiWorldRules.add_rule(multiworld.get_location("Repair Boat Anchor", player), - logic.has(MetalBar.iridium)) - MultiWorldRules.add_rule(multiworld.get_location("Repair Ticket Machine", player), - logic.has(ArtisanGood.battery_pack)) + set_rule(multiworld.get_location("Repair Boat Hull", player), + logic.has(Material.hardwood)) + set_rule(multiworld.get_location("Repair Boat Anchor", player), + logic.has(MetalBar.iridium)) + set_rule(multiworld.get_location("Repair Ticket Machine", player), + logic.has(ArtisanGood.battery_pack)) def set_island_entrances_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -403,29 +403,29 @@ def set_island_parrot_rules(logic: StardewLogic, multiworld, player): has_5_walnut = logic.walnut.has_walnut(15) has_10_walnut = logic.walnut.has_walnut(40) has_20_walnut = logic.walnut.has_walnut(60) - MultiWorldRules.add_rule(multiworld.get_location("Leo's Parrot", player), - has_walnut) - MultiWorldRules.add_rule(multiworld.get_location("Island West Turtle", player), - has_10_walnut & logic.received("Island North Turtle")) - MultiWorldRules.add_rule(multiworld.get_location("Island Farmhouse", player), - has_20_walnut) - MultiWorldRules.add_rule(multiworld.get_location("Island Mailbox", player), - has_5_walnut & logic.received("Island Farmhouse")) - MultiWorldRules.add_rule(multiworld.get_location(Transportation.farm_obelisk, player), - has_20_walnut & logic.received("Island Mailbox")) - MultiWorldRules.add_rule(multiworld.get_location("Dig Site Bridge", player), - has_10_walnut & logic.received("Island West Turtle")) - MultiWorldRules.add_rule(multiworld.get_location("Island Trader", player), - has_10_walnut & logic.received("Island Farmhouse")) - MultiWorldRules.add_rule(multiworld.get_location("Volcano Bridge", player), - has_5_walnut & logic.received("Island West Turtle") & - logic.region.can_reach(Region.volcano_floor_10)) - MultiWorldRules.add_rule(multiworld.get_location("Volcano Exit Shortcut", player), - has_5_walnut & logic.received("Island West Turtle")) - MultiWorldRules.add_rule(multiworld.get_location("Island Resort", player), - has_20_walnut & logic.received("Island Farmhouse")) - MultiWorldRules.add_rule(multiworld.get_location(Transportation.parrot_express, player), - has_10_walnut) + set_rule(multiworld.get_location("Leo's Parrot", player), + has_walnut) + set_rule(multiworld.get_location("Island West Turtle", player), + has_10_walnut & logic.received("Island North Turtle")) + set_rule(multiworld.get_location("Island Farmhouse", player), + has_20_walnut) + set_rule(multiworld.get_location("Island Mailbox", player), + has_5_walnut & logic.received("Island Farmhouse")) + set_rule(multiworld.get_location(Transportation.farm_obelisk, player), + has_20_walnut & logic.received("Island Mailbox")) + set_rule(multiworld.get_location("Dig Site Bridge", player), + has_10_walnut & logic.received("Island West Turtle")) + set_rule(multiworld.get_location("Island Trader", player), + has_10_walnut & logic.received("Island Farmhouse")) + set_rule(multiworld.get_location("Volcano Bridge", player), + has_5_walnut & logic.received("Island West Turtle") & + logic.region.can_reach(Region.volcano_floor_10)) + set_rule(multiworld.get_location("Volcano Exit Shortcut", player), + has_5_walnut & logic.received("Island West Turtle")) + set_rule(multiworld.get_location("Island Resort", player), + has_20_walnut & logic.received("Island Farmhouse")) + set_rule(multiworld.get_location(Transportation.parrot_express, player), + has_10_walnut) def set_walnut_rules(logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -442,27 +442,27 @@ def set_walnut_puzzle_rules(logic: StardewLogic, multiworld, player, world_optio if WalnutsanityOptionName.puzzles not in world_options.walnutsanity: return - MultiWorldRules.add_rule(multiworld.get_location("Open Golden Coconut", player), logic.has(Geode.golden_coconut)) - MultiWorldRules.add_rule(multiworld.get_location("Banana Altar", player), logic.has(Fruit.banana)) - MultiWorldRules.add_rule(multiworld.get_location("Leo's Tree", player), logic.tool.has_tool(Tool.axe)) - MultiWorldRules.add_rule(multiworld.get_location("Gem Birds Shrine", player), logic.has(Mineral.amethyst) & logic.has(Mineral.aquamarine) & - logic.has(Mineral.emerald) & logic.has(Mineral.ruby) & logic.has(Mineral.topaz) & - logic.region.can_reach_all((Region.island_north, Region.island_west, Region.island_east, Region.island_south))) - MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Melon", player), logic.has(Fruit.melon) & logic.region.can_reach(Region.island_west)) - MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Wheat", player), logic.has(Vegetable.wheat) & - logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Gourmand Frog Melon")) - MultiWorldRules.add_rule(multiworld.get_location("Gourmand Frog Garlic", player), logic.has(Vegetable.garlic) & - logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Gourmand Frog Wheat")) - MultiWorldRules.add_rule(multiworld.get_location("Whack A Mole", player), logic.tool.has_tool(Tool.watering_can, ToolMaterial.iridium)) - MultiWorldRules.add_rule(multiworld.get_location("Complete Large Animal Collection", player), logic.walnut.can_complete_large_animal_collection()) - MultiWorldRules.add_rule(multiworld.get_location("Complete Snake Collection", player), logic.walnut.can_complete_snake_collection()) - MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Frog Collection", player), logic.walnut.can_complete_frog_collection()) - MultiWorldRules.add_rule(multiworld.get_location("Complete Mummified Bat Collection", player), logic.walnut.can_complete_bat_collection()) - MultiWorldRules.add_rule(multiworld.get_location("Purple Flowers Island Survey", player), logic.walnut.can_start_field_office) - MultiWorldRules.add_rule(multiworld.get_location("Purple Starfish Island Survey", player), logic.walnut.can_start_field_office) - MultiWorldRules.add_rule(multiworld.get_location("Protruding Tree Walnut", player), logic.combat.has_slingshot) - MultiWorldRules.add_rule(multiworld.get_location("Starfish Tide Pool", player), logic.tool.has_fishing_rod(1)) - MultiWorldRules.add_rule(multiworld.get_location("Mermaid Song", player), logic.has(Furniture.flute_block)) + set_rule(multiworld.get_location("Open Golden Coconut", player), logic.has(Geode.golden_coconut)) + set_rule(multiworld.get_location("Banana Altar", player), logic.has(Fruit.banana)) + set_rule(multiworld.get_location("Leo's Tree", player), logic.tool.has_tool(Tool.axe)) + set_rule(multiworld.get_location("Gem Birds Shrine", player), logic.has(Mineral.amethyst) & logic.has(Mineral.aquamarine) & + logic.has(Mineral.emerald) & logic.has(Mineral.ruby) & logic.has(Mineral.topaz) & + logic.region.can_reach_all((Region.island_north, Region.island_west, Region.island_east, Region.island_south))) + set_rule(multiworld.get_location("Gourmand Frog Melon", player), logic.has(Fruit.melon) & logic.region.can_reach(Region.island_west)) + set_rule(multiworld.get_location("Gourmand Frog Wheat", player), logic.has(Vegetable.wheat) & + logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Gourmand Frog Melon")) + set_rule(multiworld.get_location("Gourmand Frog Garlic", player), logic.has(Vegetable.garlic) & + logic.region.can_reach(Region.island_west) & logic.region.can_reach_location("Gourmand Frog Wheat")) + set_rule(multiworld.get_location("Whack A Mole", player), logic.tool.has_tool(Tool.watering_can, ToolMaterial.iridium)) + set_rule(multiworld.get_location("Complete Large Animal Collection", player), logic.walnut.can_complete_large_animal_collection()) + set_rule(multiworld.get_location("Complete Snake Collection", player), logic.walnut.can_complete_snake_collection()) + set_rule(multiworld.get_location("Complete Mummified Frog Collection", player), logic.walnut.can_complete_frog_collection()) + set_rule(multiworld.get_location("Complete Mummified Bat Collection", player), logic.walnut.can_complete_bat_collection()) + set_rule(multiworld.get_location("Purple Flowers Island Survey", player), logic.walnut.can_start_field_office) + set_rule(multiworld.get_location("Purple Starfish Island Survey", player), logic.walnut.can_start_field_office) + set_rule(multiworld.get_location("Protruding Tree Walnut", player), logic.combat.has_slingshot) + set_rule(multiworld.get_location("Starfish Tide Pool", player), logic.tool.has_fishing_rod(1)) + set_rule(multiworld.get_location("Mermaid Song", player), logic.has(Furniture.flute_block)) def set_walnut_bushes_rules(logic, multiworld, player, world_options): @@ -482,20 +482,20 @@ def set_walnut_dig_spot_rules(logic, multiworld, player, world_options): rule = rule & logic.has(Forageable.journal_scrap) if "Starfish Diamond" in dig_spot_walnut.name: rule = rule & logic.tool.has_tool(Tool.pickaxe, ToolMaterial.iron) - MultiWorldRules.set_rule(multiworld.get_location(dig_spot_walnut.name, player), rule) + set_rule(multiworld.get_location(dig_spot_walnut.name, player), rule) def set_walnut_repeatable_rules(logic, multiworld, player, world_options): if WalnutsanityOptionName.repeatables not in world_options.walnutsanity: return for i in range(1, 6): - MultiWorldRules.set_rule(multiworld.get_location(f"Fishing Walnut {i}", player), logic.tool.has_fishing_rod(1)) - MultiWorldRules.set_rule(multiworld.get_location(f"Harvesting Walnut {i}", player), logic.skill.can_get_farming_xp) - MultiWorldRules.set_rule(multiworld.get_location(f"Mussel Node Walnut {i}", player), logic.tool.has_tool(Tool.pickaxe)) - MultiWorldRules.set_rule(multiworld.get_location(f"Volcano Rocks Walnut {i}", player), logic.tool.has_tool(Tool.pickaxe)) - MultiWorldRules.set_rule(multiworld.get_location(f"Volcano Monsters Walnut {i}", player), logic.combat.has_galaxy_weapon) - MultiWorldRules.set_rule(multiworld.get_location(f"Volcano Crates Walnut {i}", player), logic.combat.has_any_weapon) - MultiWorldRules.set_rule(multiworld.get_location(f"Tiger Slime Walnut", player), logic.monster.can_kill(Monster.tiger_slime)) + set_rule(multiworld.get_location(f"Fishing Walnut {i}", player), logic.tool.has_fishing_rod(1)) + set_rule(multiworld.get_location(f"Harvesting Walnut {i}", player), logic.skill.can_get_farming_xp) + set_rule(multiworld.get_location(f"Mussel Node Walnut {i}", player), logic.tool.has_tool(Tool.pickaxe)) + set_rule(multiworld.get_location(f"Volcano Rocks Walnut {i}", player), logic.tool.has_tool(Tool.pickaxe)) + set_rule(multiworld.get_location(f"Volcano Monsters Walnut {i}", player), logic.combat.has_galaxy_weapon) + set_rule(multiworld.get_location(f"Volcano Crates Walnut {i}", player), logic.combat.has_any_weapon) + set_rule(multiworld.get_location(f"Tiger Slime Walnut", player), logic.monster.can_kill(Monster.tiger_slime)) def set_cropsanity_rules(logic: StardewLogic, multiworld, player, world_content: StardewContent): @@ -505,7 +505,7 @@ def set_cropsanity_rules(logic: StardewLogic, multiworld, player, world_content: for item in world_content.find_tagged_items(ItemTag.CROPSANITY): location = world_content.features.cropsanity.to_location_name(item.name) harvest_sources = (source for source in item.sources if isinstance(source, (HarvestFruitTreeSource, HarvestCropSource))) - MultiWorldRules.set_rule(multiworld.get_location(location, player), logic.source.has_access_to_any(harvest_sources)) + set_rule(multiworld.get_location(location, player), logic.source.has_access_to_any(harvest_sources)) def set_story_quests_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -513,8 +513,8 @@ def set_story_quests_rules(all_location_names: Set[str], logic: StardewLogic, mu return for quest in locations.locations_by_tag[LocationTags.STORY_QUEST]: if quest.name in all_location_names and (quest.mod_name is None or quest.mod_name in world_options.mods): - MultiWorldRules.set_rule(multiworld.get_location(quest.name, player), - logic.registry.quest_rules[quest.name]) + set_rule(multiworld.get_location(quest.name, player), + logic.registry.quest_rules[quest.name]) def set_special_order_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, @@ -524,7 +524,7 @@ def set_special_order_rules(all_location_names: Set[str], logic: StardewLogic, m for board_order in locations.locations_by_tag[LocationTags.SPECIAL_ORDER_BOARD]: if board_order.name in all_location_names: order_rule = board_rule & logic.registry.special_order_rules[board_order.name] - MultiWorldRules.set_rule(multiworld.get_location(board_order.name, player), order_rule) + set_rule(multiworld.get_location(board_order.name, player), order_rule) if world_options.exclude_ginger_island == ExcludeGingerIsland.option_true: return @@ -533,7 +533,7 @@ def set_special_order_rules(all_location_names: Set[str], logic: StardewLogic, m for qi_order in locations.locations_by_tag[LocationTags.SPECIAL_ORDER_QI]: if qi_order.name in all_location_names: order_rule = qi_rule & logic.registry.special_order_rules[qi_order.name] - MultiWorldRules.set_rule(multiworld.get_location(qi_order.name, player), order_rule) + set_rule(multiworld.get_location(qi_order.name, player), order_rule) help_wanted_prefix = "Help Wanted:" @@ -565,22 +565,22 @@ def set_help_wanted_quests_rules(logic: StardewLogic, multiworld, player, world_ def set_help_wanted_delivery_rule(multiworld, player, month_rule, quest_number): location_name = f"{help_wanted_prefix} {item_delivery} {quest_number}" - MultiWorldRules.set_rule(multiworld.get_location(location_name, player), month_rule) + set_rule(multiworld.get_location(location_name, player), month_rule) def set_help_wanted_gathering_rule(multiworld, player, month_rule, quest_number): location_name = f"{help_wanted_prefix} {gathering} {quest_number}" - MultiWorldRules.set_rule(multiworld.get_location(location_name, player), month_rule) + set_rule(multiworld.get_location(location_name, player), month_rule) def set_help_wanted_fishing_rule(multiworld, player, month_rule, quest_number): location_name = f"{help_wanted_prefix} {fishing} {quest_number}" - MultiWorldRules.set_rule(multiworld.get_location(location_name, player), month_rule) + set_rule(multiworld.get_location(location_name, player), month_rule) def set_help_wanted_slay_monsters_rule(multiworld, player, month_rule, quest_number): location_name = f"{help_wanted_prefix} {slay_monsters} {quest_number}" - MultiWorldRules.set_rule(multiworld.get_location(location_name, player), month_rule) + set_rule(multiworld.get_location(location_name, player), month_rule) def set_fishsanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld: MultiWorld, player: int): @@ -588,8 +588,8 @@ def set_fishsanity_rules(all_location_names: Set[str], logic: StardewLogic, mult for fish_location in locations.locations_by_tag[LocationTags.FISHSANITY]: if fish_location.name in all_location_names: fish_name = fish_location.name[len(fish_prefix):] - MultiWorldRules.set_rule(multiworld.get_location(fish_location.name, player), - logic.has(fish_name)) + set_rule(multiworld.get_location(fish_location.name, player), + logic.has(fish_name)) def set_museumsanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld: MultiWorld, player: int, @@ -612,8 +612,8 @@ def set_museum_individual_donations_rules(all_location_names, logic: StardewLogi donation_name = museum_location.name[len(museum_prefix):] required_detectors = counter * 3 // number_donations rule = logic.museum.can_find_museum_item(all_museum_items_by_name[donation_name]) & logic.received(Wallet.metal_detector, required_detectors) - MultiWorldRules.set_rule(multiworld.get_location(museum_location.name, player), - rule) + set_rule(multiworld.get_location(museum_location.name, player), + rule) counter += 1 @@ -643,7 +643,7 @@ def set_museum_milestone_rule(logic: StardewLogic, multiworld: MultiWorld, museu rule = logic.museum.can_find_museum_item(Artifact.ancient_seed) & logic.received(metal_detector, 2) if rule is None: return - MultiWorldRules.set_rule(multiworld.get_location(museum_milestone.name, player), rule) + set_rule(multiworld.get_location(museum_milestone.name, player), rule) def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, accepted_items, donation_func): @@ -656,14 +656,14 @@ def get_museum_item_count_rule(logic: StardewLogic, suffix, milestone_name, acce def set_backpack_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): if world_options.backpack_progression != BackpackProgression.option_vanilla: - MultiWorldRules.set_rule(multiworld.get_location("Large Pack", player), - logic.money.can_spend(2000)) - MultiWorldRules.set_rule(multiworld.get_location("Deluxe Pack", player), - (logic.money.can_spend(10000) & logic.received("Progressive Backpack"))) + set_rule(multiworld.get_location("Large Pack", player), + logic.money.can_spend(2000)) + set_rule(multiworld.get_location("Deluxe Pack", player), + (logic.money.can_spend(10000) & logic.received("Progressive Backpack"))) if ModNames.big_backpack in world_options.mods: - MultiWorldRules.set_rule(multiworld.get_location("Premium Pack", player), - (logic.money.can_spend(150000) & - logic.received("Progressive Backpack", 2))) + set_rule(multiworld.get_location("Premium Pack", player), + (logic.money.can_spend(150000) & + logic.received("Progressive Backpack", 2))) def set_festival_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player): @@ -672,8 +672,8 @@ def set_festival_rules(all_location_names: Set[str], logic: StardewLogic, multiw festival_locations.extend(locations.locations_by_tag[LocationTags.FESTIVAL_HARD]) for festival in festival_locations: if festival.name in all_location_names: - MultiWorldRules.set_rule(multiworld.get_location(festival.name, player), - logic.registry.festival_rules[festival.name]) + set_rule(multiworld.get_location(festival.name, player), + logic.registry.festival_rules[festival.name]) monster_eradication_prefix = "Monster Eradication: " @@ -705,7 +705,7 @@ def set_monstersanity_monster_rules(all_location_names: Set[str], logic: Stardew rule = logic.monster.can_kill_many(logic.monster.all_monsters_by_name[monster_name]) else: rule = logic.monster.can_kill(logic.monster.all_monsters_by_name[monster_name]) - MultiWorldRules.set_rule(location, rule) + set_rule(location, rule) def set_monstersanity_progressive_category_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player): @@ -732,7 +732,7 @@ def set_monstersanity_progressive_category_rule(all_location_names: Set[str], lo rule = logic.monster.can_kill_any(logic.monster.all_monsters_by_category[monster_category], goal_index + 1) else: rule = logic.monster.can_kill_any(logic.monster.all_monsters_by_category[monster_category], goal_index * 2) - MultiWorldRules.set_rule(location, rule) + set_rule(location, rule) def get_monster_eradication_number(location_name, monster_category) -> int: @@ -753,7 +753,7 @@ def set_monstersanity_category_rules(all_location_names: Set[str], logic: Starde rule = logic.monster.can_kill_any(logic.monster.all_monsters_by_category[monster_category]) else: rule = logic.monster.can_kill_any(logic.monster.all_monsters_by_category[monster_category], MAX_MONTHS) - MultiWorldRules.set_rule(location, rule) + set_rule(location, rule) def set_shipsanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -766,7 +766,7 @@ def set_shipsanity_rules(all_location_names: Set[str], logic: StardewLogic, mult if location.name not in all_location_names: continue item_to_ship = location.name[len(shipsanity_prefix):] - MultiWorldRules.set_rule(multiworld.get_location(location.name, player), logic.shipping.can_ship(item_to_ship)) + set_rule(multiworld.get_location(location.name, player), logic.shipping.can_ship(item_to_ship)) def set_cooksanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -781,7 +781,7 @@ def set_cooksanity_rules(all_location_names: Set[str], logic: StardewLogic, mult recipe_name = location.name[len(cooksanity_prefix):] recipe = all_cooking_recipes_by_name[recipe_name] cook_rule = logic.cooking.can_cook(recipe) - MultiWorldRules.set_rule(multiworld.get_location(location.name, player), cook_rule) + set_rule(multiworld.get_location(location.name, player), cook_rule) def set_chefsanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -796,7 +796,7 @@ def set_chefsanity_rules(all_location_names: Set[str], logic: StardewLogic, mult recipe_name = location.name[:-len(chefsanity_suffix)] recipe = all_cooking_recipes_by_name[recipe_name] learn_rule = logic.cooking.can_learn_recipe(recipe.source) - MultiWorldRules.set_rule(multiworld.get_location(location.name, player), learn_rule) + set_rule(multiworld.get_location(location.name, player), learn_rule) def set_craftsanity_rules(all_location_names: Set[str], logic: StardewLogic, multiworld, player, world_options: StardewValleyOptions): @@ -817,7 +817,7 @@ def set_craftsanity_rules(all_location_names: Set[str], logic: StardewLogic, mul recipe_name = location.name[len(craft_prefix):] recipe = all_crafting_recipes_by_name[recipe_name] craft_rule = logic.crafting.can_craft(recipe) - MultiWorldRules.set_rule(multiworld.get_location(location.name, player), craft_rule) + set_rule(multiworld.get_location(location.name, player), craft_rule) def set_booksanity_rules(logic: StardewLogic, multiworld, player, content: StardewContent): @@ -827,12 +827,12 @@ def set_booksanity_rules(logic: StardewLogic, multiworld, player, content: Stard for book in content.find_tagged_items(ItemTag.BOOK): if booksanity.is_included(book): - MultiWorldRules.set_rule(multiworld.get_location(booksanity.to_location_name(book.name), player), logic.has(book.name)) + set_rule(multiworld.get_location(booksanity.to_location_name(book.name), player), logic.has(book.name)) for i, book in enumerate(booksanity.get_randomized_lost_books()): if i <= 0: continue - MultiWorldRules.set_rule(multiworld.get_location(booksanity.to_location_name(book), player), logic.received(booksanity.progressive_lost_book, i)) + set_rule(multiworld.get_location(booksanity.to_location_name(book), player), logic.received(booksanity.progressive_lost_book, i)) def set_traveling_merchant_day_rules(logic: StardewLogic, multiworld: MultiWorld, player: int): @@ -856,102 +856,102 @@ def set_arcade_machine_rules(logic: StardewLogic, multiworld: MultiWorld, player set_entrance_rule(multiworld, player, Entrance.play_journey_of_the_prairie_king, logic.has("JotPK Small Buff")) set_entrance_rule(multiworld, player, Entrance.reach_jotpk_world_2, logic.has("JotPK Medium Buff")) set_entrance_rule(multiworld, player, Entrance.reach_jotpk_world_3, logic.has("JotPK Big Buff")) - MultiWorldRules.add_rule(multiworld.get_location("Journey of the Prairie King Victory", player), - logic.has("JotPK Max Buff")) + set_rule(multiworld.get_location("Journey of the Prairie King Victory", player), + logic.has("JotPK Max Buff")) def set_friendsanity_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, content: StardewContent): if not content.features.friendsanity.is_enabled: return - MultiWorldRules.add_rule(multiworld.get_location("Spouse Stardrop", player), - logic.relationship.has_hearts_with_any_bachelor(13)) - MultiWorldRules.add_rule(multiworld.get_location("Have a Baby", player), - logic.relationship.can_reproduce(1)) - MultiWorldRules.add_rule(multiworld.get_location("Have Another Baby", player), - logic.relationship.can_reproduce(2)) + set_rule(multiworld.get_location("Spouse Stardrop", player), + logic.relationship.has_hearts_with_any_bachelor(13)) + set_rule(multiworld.get_location("Have a Baby", player), + logic.relationship.can_reproduce(1)) + set_rule(multiworld.get_location("Have Another Baby", player), + logic.relationship.can_reproduce(2)) for villager in content.villagers.values(): for heart in content.features.friendsanity.get_randomized_hearts(villager): rule = logic.relationship.can_earn_relationship(villager.name, heart) location_name = friendsanity.to_location_name(villager.name, heart) - MultiWorldRules.set_rule(multiworld.get_location(location_name, player), rule) + set_rule(multiworld.get_location(location_name, player), rule) for heart in content.features.friendsanity.get_pet_randomized_hearts(): rule = logic.pet.can_befriend_pet(heart) location_name = friendsanity.to_location_name(NPC.pet, heart) - MultiWorldRules.set_rule(multiworld.get_location(location_name, player), rule) + set_rule(multiworld.get_location(location_name, player), rule) def set_deepwoods_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): if ModNames.deepwoods in world_options.mods: - MultiWorldRules.add_rule(multiworld.get_location("Breaking Up Deep Woods Gingerbread House", player), - logic.tool.has_tool(Tool.axe, "Gold")) - MultiWorldRules.add_rule(multiworld.get_location("Chop Down a Deep Woods Iridium Tree", player), - logic.tool.has_tool(Tool.axe, "Iridium")) + set_rule(multiworld.get_location("Breaking Up Deep Woods Gingerbread House", player), + logic.tool.has_tool(Tool.axe, "Gold")) + set_rule(multiworld.get_location("Chop Down a Deep Woods Iridium Tree", player), + logic.tool.has_tool(Tool.axe, "Iridium")) set_entrance_rule(multiworld, player, DeepWoodsEntrance.use_woods_obelisk, logic.received("Woods Obelisk")) for depth in range(10, 100 + 10, 10): set_entrance_rule(multiworld, player, move_to_woods_depth(depth), logic.mod.deepwoods.can_chop_to_depth(depth)) - MultiWorldRules.add_rule(multiworld.get_location("The Sword in the Stone", player), - logic.mod.deepwoods.can_pull_sword() & logic.mod.deepwoods.can_chop_to_depth(100)) + set_rule(multiworld.get_location("The Sword in the Stone", player), + logic.mod.deepwoods.can_pull_sword() & logic.mod.deepwoods.can_chop_to_depth(100)) def set_magic_spell_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): if ModNames.magic not in world_options.mods: return - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Clear Debris", player), - (logic.tool.has_tool("Axe", "Basic") | logic.tool.has_tool("Pickaxe", "Basic"))) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Till", player), - logic.tool.has_tool("Hoe", "Basic")) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Water", player), - logic.tool.has_tool("Watering Can", "Basic")) - MultiWorldRules.add_rule(multiworld.get_location("Analyze All Toil School Locations", player), - (logic.tool.has_tool("Watering Can", "Basic") & logic.tool.has_tool("Hoe", "Basic") - & (logic.tool.has_tool("Axe", "Basic") | logic.tool.has_tool("Pickaxe", "Basic")))) + set_rule(multiworld.get_location("Analyze: Clear Debris", player), + (logic.tool.has_tool("Axe", "Basic") | logic.tool.has_tool("Pickaxe", "Basic"))) + set_rule(multiworld.get_location("Analyze: Till", player), + logic.tool.has_tool("Hoe", "Basic")) + set_rule(multiworld.get_location("Analyze: Water", player), + logic.tool.has_tool("Watering Can", "Basic")) + set_rule(multiworld.get_location("Analyze All Toil School Locations", player), + (logic.tool.has_tool("Watering Can", "Basic") & logic.tool.has_tool("Hoe", "Basic") + & (logic.tool.has_tool("Axe", "Basic") | logic.tool.has_tool("Pickaxe", "Basic")))) # Do I *want* to add boots into logic when you get them even in vanilla without effort? idk - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Evac", player), - logic.ability.can_mine_perfectly()) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Haste", player), - logic.has("Coffee")) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Heal", player), - logic.has("Life Elixir")) - MultiWorldRules.add_rule(multiworld.get_location("Analyze All Life School Locations", player), - (logic.has("Coffee") & logic.has("Life Elixir") - & logic.ability.can_mine_perfectly())) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Descend", player), - logic.region.can_reach(Region.mines)) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Fireball", player), - logic.has("Fire Quartz")) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Frostbolt", player), - logic.region.can_reach(Region.mines_floor_60) & logic.skill.can_fish(difficulty=85)) - MultiWorldRules.add_rule(multiworld.get_location("Analyze All Elemental School Locations", player), - logic.has("Fire Quartz") & logic.region.can_reach(Region.mines_floor_60) & logic.skill.can_fish(difficulty=85)) - # MultiWorldRules.add_rule(multiworld.get_location("Analyze: Lantern", player),) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Tendrils", player), - logic.region.can_reach(Region.farm)) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Shockwave", player), - logic.has("Earth Crystal")) - MultiWorldRules.add_rule(multiworld.get_location("Analyze All Nature School Locations", player), - (logic.has("Earth Crystal") & logic.region.can_reach("Farm"))), - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Meteor", player), - (logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12))), - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Lucksteal", player), - logic.region.can_reach(Region.witch_hut)) - MultiWorldRules.add_rule(multiworld.get_location("Analyze: Bloodmana", player), - logic.region.can_reach(Region.mines_floor_100)) - MultiWorldRules.add_rule(multiworld.get_location("Analyze All Eldritch School Locations", player), - (logic.region.can_reach(Region.witch_hut) & - logic.region.can_reach(Region.mines_floor_100) & - logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12))) - MultiWorldRules.add_rule(multiworld.get_location("Analyze Every Magic School Location", player), - (logic.tool.has_tool("Watering Can", "Basic") & logic.tool.has_tool("Hoe", "Basic") - & (logic.tool.has_tool("Axe", "Basic") | logic.tool.has_tool("Pickaxe", "Basic")) & - logic.has("Coffee") & logic.has("Life Elixir") - & logic.ability.can_mine_perfectly() & logic.has("Earth Crystal") & - logic.has("Fire Quartz") & logic.skill.can_fish(difficulty=85) & - logic.region.can_reach(Region.witch_hut) & - logic.region.can_reach(Region.mines_floor_100) & - logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12))) + set_rule(multiworld.get_location("Analyze: Evac", player), + logic.ability.can_mine_perfectly()) + set_rule(multiworld.get_location("Analyze: Haste", player), + logic.has("Coffee")) + set_rule(multiworld.get_location("Analyze: Heal", player), + logic.has("Life Elixir")) + set_rule(multiworld.get_location("Analyze All Life School Locations", player), + (logic.has("Coffee") & logic.has("Life Elixir") + & logic.ability.can_mine_perfectly())) + set_rule(multiworld.get_location("Analyze: Descend", player), + logic.region.can_reach(Region.mines)) + set_rule(multiworld.get_location("Analyze: Fireball", player), + logic.has("Fire Quartz")) + set_rule(multiworld.get_location("Analyze: Frostbolt", player), + logic.region.can_reach(Region.mines_floor_60) & logic.skill.can_fish(difficulty=85)) + set_rule(multiworld.get_location("Analyze All Elemental School Locations", player), + logic.has("Fire Quartz") & logic.region.can_reach(Region.mines_floor_60) & logic.skill.can_fish(difficulty=85)) + # set_rule(multiworld.get_location("Analyze: Lantern", player),) + set_rule(multiworld.get_location("Analyze: Tendrils", player), + logic.region.can_reach(Region.farm)) + set_rule(multiworld.get_location("Analyze: Shockwave", player), + logic.has("Earth Crystal")) + set_rule(multiworld.get_location("Analyze All Nature School Locations", player), + (logic.has("Earth Crystal") & logic.region.can_reach("Farm"))), + set_rule(multiworld.get_location("Analyze: Meteor", player), + (logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12))), + set_rule(multiworld.get_location("Analyze: Lucksteal", player), + logic.region.can_reach(Region.witch_hut)) + set_rule(multiworld.get_location("Analyze: Bloodmana", player), + logic.region.can_reach(Region.mines_floor_100)) + set_rule(multiworld.get_location("Analyze All Eldritch School Locations", player), + (logic.region.can_reach(Region.witch_hut) & + logic.region.can_reach(Region.mines_floor_100) & + logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12))) + set_rule(multiworld.get_location("Analyze Every Magic School Location", player), + (logic.tool.has_tool("Watering Can", "Basic") & logic.tool.has_tool("Hoe", "Basic") + & (logic.tool.has_tool("Axe", "Basic") | logic.tool.has_tool("Pickaxe", "Basic")) & + logic.has("Coffee") & logic.has("Life Elixir") + & logic.ability.can_mine_perfectly() & logic.has("Earth Crystal") & + logic.has("Fire Quartz") & logic.skill.can_fish(difficulty=85) & + logic.region.can_reach(Region.witch_hut) & + logic.region.can_reach(Region.mines_floor_100) & + logic.region.can_reach(Region.farm) & logic.time.has_lived_months(12))) def set_sve_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, world_options: StardewValleyOptions): @@ -980,8 +980,8 @@ def set_sve_rules(logic: StardewLogic, multiworld: MultiWorld, player: int, worl set_entrance_rule(multiworld, player, SVEEntrance.to_aurora_basement, logic.mod.quest.has_completed_aurora_vineyard_bundle()) logic.mod.sve.initialize_rules() for location in logic.registry.sve_location_rules: - MultiWorldRules.set_rule(multiworld.get_location(location, player), - logic.registry.sve_location_rules[location]) + set_rule(multiworld.get_location(location, player), + logic.registry.sve_location_rules[location]) set_sve_ginger_island_rules(logic, multiworld, player, world_options) set_boarding_house_rules(logic, multiworld, player, world_options) @@ -1010,7 +1010,7 @@ def set_entrance_rule(multiworld, player, entrance: str, rule: StardewRule): logger.debug(f"Registering indirect condition for {region} -> {entrance}") multiworld.register_indirect_condition(multiworld.get_region(region, player), multiworld.get_entrance(entrance, player)) - MultiWorldRules.set_rule(multiworld.get_entrance(entrance, player), rule) + set_rule(multiworld.get_entrance(entrance, player), rule) except KeyError as ex: logger.error(f"""Failed to evaluate indirect connection in: {explain(rule, CollectionState(multiworld))}""") raise ex From 8755d5cbc099f79bb347da07cf37d7c5e983ddc4 Mon Sep 17 00:00:00 2001 From: Nicholas Brochu Date: Thu, 24 Apr 2025 19:42:42 -0400 Subject: [PATCH 46/46] Remove Game: Zork Grand Inquisitor (#4884) * remove zork grand inquisitor * add apworld to inno setup installdelete --- README.md | 1 - docs/CODEOWNERS | 4 - worlds/zork_grand_inquisitor/LICENSE | 21 - worlds/zork_grand_inquisitor/__init__.py | 17 - worlds/zork_grand_inquisitor/client.py | 188 -- worlds/zork_grand_inquisitor/data/__init__.py | 0 .../data/entrance_rule_data.py | 419 --- .../zork_grand_inquisitor/data/item_data.py | 792 ----- .../data/location_data.py | 1535 --------- ...missable_location_grant_conditions_data.py | 200 -- .../zork_grand_inquisitor/data/region_data.py | 183 -- worlds/zork_grand_inquisitor/data_funcs.py | 247 -- .../docs/en_Zork Grand Inquisitor.md | 102 - worlds/zork_grand_inquisitor/docs/setup_en.md | 42 - worlds/zork_grand_inquisitor/enums.py | 350 -- .../zork_grand_inquisitor/game_controller.py | 1388 -------- .../game_state_manager.py | 370 --- worlds/zork_grand_inquisitor/options.py | 61 - worlds/zork_grand_inquisitor/requirements.txt | 1 - worlds/zork_grand_inquisitor/test/__init__.py | 5 - .../zork_grand_inquisitor/test/test_access.py | 2927 ----------------- .../test/test_data_funcs.py | 132 - .../test/test_locations.py | 49 - worlds/zork_grand_inquisitor/world.py | 205 -- 24 files changed, 9239 deletions(-) delete mode 100644 worlds/zork_grand_inquisitor/LICENSE delete mode 100644 worlds/zork_grand_inquisitor/__init__.py delete mode 100644 worlds/zork_grand_inquisitor/client.py delete mode 100644 worlds/zork_grand_inquisitor/data/__init__.py delete mode 100644 worlds/zork_grand_inquisitor/data/entrance_rule_data.py delete mode 100644 worlds/zork_grand_inquisitor/data/item_data.py delete mode 100644 worlds/zork_grand_inquisitor/data/location_data.py delete mode 100644 worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py delete mode 100644 worlds/zork_grand_inquisitor/data/region_data.py delete mode 100644 worlds/zork_grand_inquisitor/data_funcs.py delete mode 100644 worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md delete mode 100644 worlds/zork_grand_inquisitor/docs/setup_en.md delete mode 100644 worlds/zork_grand_inquisitor/enums.py delete mode 100644 worlds/zork_grand_inquisitor/game_controller.py delete mode 100644 worlds/zork_grand_inquisitor/game_state_manager.py delete mode 100644 worlds/zork_grand_inquisitor/options.py delete mode 100644 worlds/zork_grand_inquisitor/requirements.txt delete mode 100644 worlds/zork_grand_inquisitor/test/__init__.py delete mode 100644 worlds/zork_grand_inquisitor/test/test_access.py delete mode 100644 worlds/zork_grand_inquisitor/test/test_data_funcs.py delete mode 100644 worlds/zork_grand_inquisitor/test/test_locations.py delete mode 100644 worlds/zork_grand_inquisitor/world.py diff --git a/README.md b/README.md index 5e14ef5de3..83fdeea611 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,6 @@ Currently, the following games are supported: * TUNIC * Kirby's Dream Land 3 * Celeste 64 -* Zork Grand Inquisitor * Castlevania 64 * A Short Hike * Yoshi's Island diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 29de6bbfb6..88b5060dcc 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -232,10 +232,6 @@ # Zillion /worlds/zillion/ @beauxq -# Zork Grand Inquisitor -/worlds/zork_grand_inquisitor/ @nbrochu - - ## Active Unmaintained Worlds # The following worlds in this repo are currently unmaintained, but currently still work in core. If any update breaks diff --git a/worlds/zork_grand_inquisitor/LICENSE b/worlds/zork_grand_inquisitor/LICENSE deleted file mode 100644 index a94ca6bf91..0000000000 --- a/worlds/zork_grand_inquisitor/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Serpent.AI - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/worlds/zork_grand_inquisitor/__init__.py b/worlds/zork_grand_inquisitor/__init__.py deleted file mode 100644 index 791f41dd00..0000000000 --- a/worlds/zork_grand_inquisitor/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -import worlds.LauncherComponents as LauncherComponents - -from .world import ZorkGrandInquisitorWorld - - -def launch_client() -> None: - from .client import main - LauncherComponents.launch(main, name="ZorkGrandInquisitorClient") - - -LauncherComponents.components.append( - LauncherComponents.Component( - "Zork Grand Inquisitor Client", - func=launch_client, - component_type=LauncherComponents.Type.CLIENT - ) -) diff --git a/worlds/zork_grand_inquisitor/client.py b/worlds/zork_grand_inquisitor/client.py deleted file mode 100644 index 8b8d7d3ebf..0000000000 --- a/worlds/zork_grand_inquisitor/client.py +++ /dev/null @@ -1,188 +0,0 @@ -import asyncio - -import CommonClient -import NetUtils -import Utils - -from typing import Any, Dict, List, Optional, Set, Tuple - -from .data_funcs import item_names_to_id, location_names_to_id, id_to_items, id_to_locations, id_to_goals -from .enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorLocations -from .game_controller import GameController - - -class ZorkGrandInquisitorCommandProcessor(CommonClient.ClientCommandProcessor): - def _cmd_zork(self) -> None: - """Attach to an open Zork Grand Inquisitor process.""" - result: bool = self.ctx.game_controller.open_process_handle() - - if result: - self.ctx.process_attached_at_least_once = True - self.output("Successfully attached to Zork Grand Inquisitor process.") - else: - self.output("Failed to attach to Zork Grand Inquisitor process.") - - def _cmd_brog(self) -> None: - """List received Brog items.""" - self.ctx.game_controller.list_received_brog_items() - - def _cmd_griff(self) -> None: - """List received Griff items.""" - self.ctx.game_controller.list_received_griff_items() - - def _cmd_lucy(self) -> None: - """List received Lucy items.""" - self.ctx.game_controller.list_received_lucy_items() - - def _cmd_hotspots(self) -> None: - """List received Hotspots.""" - self.ctx.game_controller.list_received_hotspots() - - -class ZorkGrandInquisitorContext(CommonClient.CommonContext): - tags: Set[str] = {"AP"} - game: str = "Zork Grand Inquisitor" - command_processor: CommonClient.ClientCommandProcessor = ZorkGrandInquisitorCommandProcessor - items_handling: int = 0b111 - want_slot_data: bool = True - - item_name_to_id: Dict[str, int] = item_names_to_id() - location_name_to_id: Dict[str, int] = location_names_to_id() - - id_to_items: Dict[int, ZorkGrandInquisitorItems] = id_to_items() - id_to_locations: Dict[int, ZorkGrandInquisitorLocations] = id_to_locations() - - game_controller: GameController - - controller_task: Optional[asyncio.Task] - - process_attached_at_least_once: bool - can_display_process_message: bool - - def __init__(self, server_address: Optional[str], password: Optional[str]) -> None: - super().__init__(server_address, password) - - self.game_controller = GameController(logger=CommonClient.logger) - - self.controller_task = None - - self.process_attached_at_least_once = False - self.can_display_process_message = True - - def run_gui(self) -> None: - from kvui import GameManager - - class TextManager(GameManager): - logging_pairs: List[Tuple[str, str]] = [("Client", "Archipelago")] - base_title: str = "Archipelago Zork Grand Inquisitor Client" - - self.ui = TextManager(self) - self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI") - - async def server_auth(self, password_requested: bool = False): - if password_requested and not self.password: - await super().server_auth(password_requested) - - await self.get_username() - await self.send_connect() - - def on_package(self, cmd: str, _args: Any) -> None: - if cmd == "Connected": - self.game = self.slot_info[self.slot].game - - # Options - self.game_controller.option_goal = id_to_goals()[_args["slot_data"]["goal"]] - self.game_controller.option_deathsanity = _args["slot_data"]["deathsanity"] == 1 - - self.game_controller.option_grant_missable_location_checks = ( - _args["slot_data"]["grant_missable_location_checks"] == 1 - ) - - async def controller(self): - while not self.exit_event.is_set(): - await asyncio.sleep(0.1) - - # Enqueue Received Item Delta - network_item: NetUtils.NetworkItem - for network_item in self.items_received: - item: ZorkGrandInquisitorItems = self.id_to_items[network_item.item] - - if item not in self.game_controller.received_items: - if item not in self.game_controller.received_items_queue: - self.game_controller.received_items_queue.append(item) - - # Game Controller Update - if self.game_controller.is_process_running(): - self.game_controller.update() - self.can_display_process_message = True - else: - process_message: str - - if self.process_attached_at_least_once: - process_message = ( - "Lost connection to Zork Grand Inquisitor process. Please restart the game and use the /zork " - "command to reattach." - ) - else: - process_message = ( - "Please use the /zork command to attach to a running Zork Grand Inquisitor process." - ) - - if self.can_display_process_message: - CommonClient.logger.info(process_message) - self.can_display_process_message = False - - # Send Checked Locations - checked_location_ids: List[int] = list() - - while len(self.game_controller.completed_locations_queue) > 0: - location: ZorkGrandInquisitorLocations = self.game_controller.completed_locations_queue.popleft() - location_id: int = self.location_name_to_id[location.value] - - checked_location_ids.append(location_id) - - await self.send_msgs([ - { - "cmd": "LocationChecks", - "locations": checked_location_ids - } - ]) - - # Check for Goal Completion - if self.game_controller.goal_completed: - await self.send_msgs([ - { - "cmd": "StatusUpdate", - "status": CommonClient.ClientStatus.CLIENT_GOAL - } - ]) - - -def main() -> None: - Utils.init_logging("ZorkGrandInquisitorClient", exception_logger="Client") - - async def _main(): - ctx: ZorkGrandInquisitorContext = ZorkGrandInquisitorContext(None, None) - - ctx.server_task = asyncio.create_task(CommonClient.server_loop(ctx), name="server loop") - ctx.controller_task = asyncio.create_task(ctx.controller(), name="ZorkGrandInquisitorController") - - if CommonClient.gui_enabled: - ctx.run_gui() - - ctx.run_cli() - - await ctx.exit_event.wait() - await ctx.shutdown() - - import colorama - - colorama.just_fix_windows_console() - - asyncio.run(_main()) - - colorama.deinit() - - -if __name__ == "__main__": - main() diff --git a/worlds/zork_grand_inquisitor/data/__init__.py b/worlds/zork_grand_inquisitor/data/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/worlds/zork_grand_inquisitor/data/entrance_rule_data.py b/worlds/zork_grand_inquisitor/data/entrance_rule_data.py deleted file mode 100644 index f48be5eb6b..0000000000 --- a/worlds/zork_grand_inquisitor/data/entrance_rule_data.py +++ /dev/null @@ -1,419 +0,0 @@ -from typing import Dict, Tuple, Union - -from ..enums import ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems, ZorkGrandInquisitorRegions - - -entrance_rule_data: Dict[ - Tuple[ - ZorkGrandInquisitorRegions, - ZorkGrandInquisitorRegions, - ], - Union[ - Tuple[ - Tuple[ - Union[ - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorRegions, - ], - ..., - ], - ..., - ], - None, - ], -] = { - (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.DM_LAIR): ( - ( - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE, - ), - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, - ), - ), - (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH): ( - ( - ZorkGrandInquisitorItems.SPELL_REZROV, - ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR, - ), - ), - (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, - ), - ), - (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.HADES_SHORE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, - ), - ), - (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.PORT_FOOZLE): None, - (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, - ), - ), - (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): ( - ( - ZorkGrandInquisitorItems.SUBWAY_TOKEN, - ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT, - ), - ), - (ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, - ), - ), - (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.CROSSROADS): None, - (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): ( - ( - ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR, - ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD, - ), - ), - (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, - ), - ), - (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.HADES_SHORE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, - ), - ), - (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, - ), - ), - (ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, - ), - ), - (ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.DM_LAIR): None, - (ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WALKING_CASTLE): ( - ( - ZorkGrandInquisitorItems.HOTSPOT_BLINDS, - ZorkGrandInquisitorEvents.KNOWS_OBIDIL, - ), - ), - (ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WHITE_HOUSE): ( - ( - ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR, - ZorkGrandInquisitorItems.SPELL_NARWILE, - ZorkGrandInquisitorEvents.KNOWS_YASTARD, - ), - ), - (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON): ( - ( - ZorkGrandInquisitorItems.TOTEM_GRIFF, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW, - ), - ), - (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, ZorkGrandInquisitorRegions.HADES_BEYOND_GATES): None, - (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO): None, - (ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.ENDGAME): ( - ( - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, - ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, - ZorkGrandInquisitorRegions.WHITE_HOUSE, - ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG, - ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, - ZorkGrandInquisitorItems.BROGS_PLANK, - ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, - ), - ), - (ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.CROSSROADS): None, - (ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY): ( - ( - ZorkGrandInquisitorItems.SPELL_IGRAM, - ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, - ), - ), - (ZorkGrandInquisitorRegions.GUE_TECH, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( - (ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR,), - ), - (ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, ZorkGrandInquisitorRegions.GUE_TECH): None, - (ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( - ( - ZorkGrandInquisitorItems.STUDENT_ID, - ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE, - ), - ), - (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.CROSSROADS): ( - (ZorkGrandInquisitorItems.MAP,), - ), - (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.DM_LAIR): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, - ), - ), - (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.GUE_TECH): None, - (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.HADES_SHORE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, - ), - ), - (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, - ), - ), - (ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, - ), - ), - (ZorkGrandInquisitorRegions.HADES, ZorkGrandInquisitorRegions.HADES_BEYOND_GATES): ( - ( - ZorkGrandInquisitorEvents.KNOWS_SNAVIG, - ZorkGrandInquisitorItems.TOTEM_BROG, # Visually hiding this totem is tied to owning it; no choice - ), - ), - (ZorkGrandInquisitorRegions.HADES, ZorkGrandInquisitorRegions.HADES_SHORE): ( - (ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,), - ), - (ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO): ( - ( - ZorkGrandInquisitorItems.SPELL_NARWILE, - ZorkGrandInquisitorEvents.KNOWS_YASTARD, - ), - ), - (ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, ZorkGrandInquisitorRegions.HADES): None, - (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.CROSSROADS): ( - (ZorkGrandInquisitorItems.MAP,), - ), - (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.DM_LAIR): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, - ), - ), - (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, - ), - ), - (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.HADES): ( - ( - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER, - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS, - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, - ), - ), - (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB, - ), - ), - (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None, - (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): ( - (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM,), - ), - (ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( - (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY,), - ), - (ZorkGrandInquisitorRegions.MENU, ZorkGrandInquisitorRegions.PORT_FOOZLE): None, - (ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.HADES_SHORE): ( - ( - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, - ), - ), - (ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT): ( - ( - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, - ), - ), - (ZorkGrandInquisitorRegions.MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): None, - (ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, ZorkGrandInquisitorRegions.MONASTERY): None, - (ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST): ( - ( - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER, - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT, - ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER, - ZorkGrandInquisitorItems.SPELL_NARWILE, - ZorkGrandInquisitorEvents.KNOWS_YASTARD, - ), - ), - (ZorkGrandInquisitorRegions.PORT_FOOZLE, ZorkGrandInquisitorRegions.CROSSROADS): ( - ( - ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE, - ZorkGrandInquisitorItems.ROPE, - ZorkGrandInquisitorItems.HOTSPOT_WELL, - ), - ), - (ZorkGrandInquisitorRegions.PORT_FOOZLE, ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP): ( - ( - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, - ), - ), - (ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, ZorkGrandInquisitorRegions.PORT_FOOZLE): None, - (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT): None, - (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN): ( - ( - ZorkGrandInquisitorItems.TOTEM_LUCY, - ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR, - ), - ), - (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, ZorkGrandInquisitorRegions.ENDGAME): ( - ( - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, - ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, - ZorkGrandInquisitorRegions.WHITE_HOUSE, - ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG, - ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, - ZorkGrandInquisitorItems.BROGS_PLANK, - ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, - ), - ), - (ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST): None, - (ZorkGrandInquisitorRegions.SPELL_LAB, ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE): None, - (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.CROSSROADS): ( - (ZorkGrandInquisitorItems.MAP,), - ), - (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.DM_LAIR): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR, - ), - ), - (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH, - ), - ), - (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY): None, - (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.HADES_SHORE): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES, - ), - ), - (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.SPELL_LAB): ( - ( - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE, - ZorkGrandInquisitorEvents.DAM_DESTROYED, - ZorkGrandInquisitorItems.SPELL_GOLGATEM, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM, - ), - ), - (ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( - ( - ZorkGrandInquisitorItems.MAP, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY, - ), - ), - (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.CROSSROADS): None, - (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.HADES_SHORE): ( - ( - ZorkGrandInquisitorItems.SPELL_KENDALL, - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES, - ), - ), - (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): ( - ( - ZorkGrandInquisitorItems.SPELL_KENDALL, - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM, - ), - ), - (ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( - ( - ZorkGrandInquisitorItems.SPELL_KENDALL, - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY, - ), - ), - (ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.HADES_SHORE): ( - (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES,), - ), - (ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None, - (ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, ZorkGrandInquisitorRegions.SUBWAY_MONASTERY): ( - (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY,), - ), - (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.HADES_SHORE): ( - (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES,), - ), - (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.MONASTERY): ( - ( - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorEvents.ROPE_GLORFABLE, - ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT, - ), - ), - (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS): None, - (ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM): ( - (ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM,), - ), - (ZorkGrandInquisitorRegions.WALKING_CASTLE, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): None, - (ZorkGrandInquisitorRegions.WHITE_HOUSE, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR): None, - (ZorkGrandInquisitorRegions.WHITE_HOUSE, ZorkGrandInquisitorRegions.ENDGAME): ( - ( - ZorkGrandInquisitorItems.TOTEM_BROG, # Needed here since White House is not broken down in 2 regions - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG, - ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, - ZorkGrandInquisitorItems.BROGS_PLANK, - ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, - ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, - ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, - ), - ), -} diff --git a/worlds/zork_grand_inquisitor/data/item_data.py b/worlds/zork_grand_inquisitor/data/item_data.py deleted file mode 100644 index c312bbce3d..0000000000 --- a/worlds/zork_grand_inquisitor/data/item_data.py +++ /dev/null @@ -1,792 +0,0 @@ -from typing import Dict, NamedTuple, Optional, Tuple, Union - -from BaseClasses import ItemClassification - -from ..enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorTags - - -class ZorkGrandInquisitorItemData(NamedTuple): - statemap_keys: Optional[Tuple[int, ...]] - archipelago_id: Optional[int] - classification: ItemClassification - tags: Tuple[ZorkGrandInquisitorTags, ...] - maximum_quantity: Optional[int] = 1 - - -ITEM_OFFSET = 9758067000 - -item_data: Dict[ZorkGrandInquisitorItems, ZorkGrandInquisitorItemData] = { - # Inventory Items - ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH: ZorkGrandInquisitorItemData( - statemap_keys=(67,), # Extinguished = 103 - archipelago_id=ITEM_OFFSET + 0, - classification=ItemClassification.filler, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH: ZorkGrandInquisitorItemData( - statemap_keys=(68,), # Extinguished = 104 - archipelago_id=ITEM_OFFSET + 1, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.BROGS_GRUE_EGG: ZorkGrandInquisitorItemData( - statemap_keys=(70,), # Boiled = 71 - archipelago_id=ITEM_OFFSET + 2, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.BROGS_PLANK: ZorkGrandInquisitorItemData( - statemap_keys=(69,), - archipelago_id=ITEM_OFFSET + 3, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.FLATHEADIA_FUDGE: ZorkGrandInquisitorItemData( - statemap_keys=(54,), - archipelago_id=ITEM_OFFSET + 4, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP: ZorkGrandInquisitorItemData( - statemap_keys=(86,), - archipelago_id=ITEM_OFFSET + 5, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH: ZorkGrandInquisitorItemData( - statemap_keys=(84,), - archipelago_id=ITEM_OFFSET + 6, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT: ZorkGrandInquisitorItemData( - statemap_keys=(9,), - archipelago_id=ITEM_OFFSET + 7, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN: ZorkGrandInquisitorItemData( - statemap_keys=(16,), - archipelago_id=ITEM_OFFSET + 8, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.HAMMER: ZorkGrandInquisitorItemData( - statemap_keys=(23,), - archipelago_id=ITEM_OFFSET + 9, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.HUNGUS_LARD: ZorkGrandInquisitorItemData( - statemap_keys=(55,), - archipelago_id=ITEM_OFFSET + 10, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.JAR_OF_HOTBUGS: ZorkGrandInquisitorItemData( - statemap_keys=(56,), - archipelago_id=ITEM_OFFSET + 11, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.LANTERN: ZorkGrandInquisitorItemData( - statemap_keys=(4,), - archipelago_id=ITEM_OFFSET + 12, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER: ZorkGrandInquisitorItemData( - statemap_keys=(88,), - archipelago_id=ITEM_OFFSET + 13, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1: ZorkGrandInquisitorItemData( - statemap_keys=(116,), # With fly = 120 - archipelago_id=ITEM_OFFSET + 14, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2: ZorkGrandInquisitorItemData( - statemap_keys=(117,), # With fly = 121 - archipelago_id=ITEM_OFFSET + 15, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3: ZorkGrandInquisitorItemData( - statemap_keys=(118,), # With fly = 122 - archipelago_id=ITEM_OFFSET + 16, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4: ZorkGrandInquisitorItemData( - statemap_keys=(119,), # With fly = 123 - archipelago_id=ITEM_OFFSET + 17, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.MAP: ZorkGrandInquisitorItemData( - statemap_keys=(6,), - archipelago_id=ITEM_OFFSET + 18, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.MEAD_LIGHT: ZorkGrandInquisitorItemData( - statemap_keys=(2,), - archipelago_id=ITEM_OFFSET + 19, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.MOSS_OF_MAREILON: ZorkGrandInquisitorItemData( - statemap_keys=(57,), - archipelago_id=ITEM_OFFSET + 20, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.MUG: ZorkGrandInquisitorItemData( - statemap_keys=(35,), - archipelago_id=ITEM_OFFSET + 21, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.OLD_SCRATCH_CARD: ZorkGrandInquisitorItemData( - statemap_keys=(17,), - archipelago_id=ITEM_OFFSET + 22, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE: ZorkGrandInquisitorItemData( - statemap_keys=(36,), - archipelago_id=ITEM_OFFSET + 23, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER: ZorkGrandInquisitorItemData( - statemap_keys=(3,), - archipelago_id=ITEM_OFFSET + 24, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: ZorkGrandInquisitorItemData( - statemap_keys=(5827,), - archipelago_id=ITEM_OFFSET + 25, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.PROZORK_TABLET: ZorkGrandInquisitorItemData( - statemap_keys=(65,), - archipelago_id=ITEM_OFFSET + 26, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB: ZorkGrandInquisitorItemData( - statemap_keys=(53,), - archipelago_id=ITEM_OFFSET + 27, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.ROPE: ZorkGrandInquisitorItemData( - statemap_keys=(83,), - archipelago_id=ITEM_OFFSET + 28, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS: ZorkGrandInquisitorItemData( - statemap_keys=(101,), # SNA = 41 - archipelago_id=ITEM_OFFSET + 29, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV: ZorkGrandInquisitorItemData( - statemap_keys=(102,), # VIG = 48 - archipelago_id=ITEM_OFFSET + 30, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.SHOVEL: ZorkGrandInquisitorItemData( - statemap_keys=(49,), - archipelago_id=ITEM_OFFSET + 31, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.SNAPDRAGON: ZorkGrandInquisitorItemData( - statemap_keys=(50,), - archipelago_id=ITEM_OFFSET + 32, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.STUDENT_ID: ZorkGrandInquisitorItemData( - statemap_keys=(39,), - archipelago_id=ITEM_OFFSET + 33, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.SUBWAY_TOKEN: ZorkGrandInquisitorItemData( - statemap_keys=(20,), - archipelago_id=ITEM_OFFSET + 34, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.SWORD: ZorkGrandInquisitorItemData( - statemap_keys=(21,), - archipelago_id=ITEM_OFFSET + 35, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.ZIMDOR_SCROLL: ZorkGrandInquisitorItemData( - statemap_keys=(25,), - archipelago_id=ITEM_OFFSET + 36, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - ZorkGrandInquisitorItems.ZORK_ROCKS: ZorkGrandInquisitorItemData( - statemap_keys=(37,), - archipelago_id=ITEM_OFFSET + 37, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.INVENTORY_ITEM,), - ), - # Hotspots - ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX: ZorkGrandInquisitorItemData( - statemap_keys=(9116,), - archipelago_id=ITEM_OFFSET + 100 + 0, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS: ZorkGrandInquisitorItemData( - statemap_keys=(15434, 15436, 15438, 15440), - archipelago_id=ITEM_OFFSET + 100 + 1, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX: ZorkGrandInquisitorItemData( - statemap_keys=(12096,), - archipelago_id=ITEM_OFFSET + 100 + 2, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_BLINDS: ZorkGrandInquisitorItemData( - statemap_keys=(4799,), - archipelago_id=ITEM_OFFSET + 100 + 3, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS: ZorkGrandInquisitorItemData( - statemap_keys=(12691, 12692, 12693, 12694, 12695, 12696, 12697, 12698, 12699, 12700, 12701), - archipelago_id=ITEM_OFFSET + 100 + 4, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData( - statemap_keys=(12702,), - archipelago_id=ITEM_OFFSET + 100 + 5, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT: ZorkGrandInquisitorItemData( - statemap_keys=(12909,), - archipelago_id=ITEM_OFFSET + 100 + 6, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT: ZorkGrandInquisitorItemData( - statemap_keys=(12900,), - archipelago_id=ITEM_OFFSET + 100 + 7, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR: ZorkGrandInquisitorItemData( - statemap_keys=(5010,), - archipelago_id=ITEM_OFFSET + 100 + 8, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT: ZorkGrandInquisitorItemData( - statemap_keys=(9539,), - archipelago_id=ITEM_OFFSET + 100 + 9, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER: ZorkGrandInquisitorItemData( - statemap_keys=(19712,), - archipelago_id=ITEM_OFFSET + 100 + 10, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT: ZorkGrandInquisitorItemData( - statemap_keys=(2586,), - archipelago_id=ITEM_OFFSET + 100 + 11, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER: ZorkGrandInquisitorItemData( - statemap_keys=(11878,), - archipelago_id=ITEM_OFFSET + 100 + 12, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND: ZorkGrandInquisitorItemData( - statemap_keys=(11751,), - archipelago_id=ITEM_OFFSET + 100 + 13, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH: ZorkGrandInquisitorItemData( - statemap_keys=(15147, 15153), - archipelago_id=ITEM_OFFSET + 100 + 14, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW: ZorkGrandInquisitorItemData( - statemap_keys=(1705,), - archipelago_id=ITEM_OFFSET + 100 + 15, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS: ZorkGrandInquisitorItemData( - statemap_keys=(1425, 1426), - archipelago_id=ITEM_OFFSET + 100 + 16, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE: ZorkGrandInquisitorItemData( - statemap_keys=(13106,), - archipelago_id=ITEM_OFFSET + 100 + 17, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS: ZorkGrandInquisitorItemData( - statemap_keys=(13219, 13220, 13221, 13222), - archipelago_id=ITEM_OFFSET + 100 + 18, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS: ZorkGrandInquisitorItemData( - statemap_keys=(14327, 14332, 14337, 14342), - archipelago_id=ITEM_OFFSET + 100 + 19, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData( - statemap_keys=(12528,), - archipelago_id=ITEM_OFFSET + 100 + 20, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS: ZorkGrandInquisitorItemData( - statemap_keys=(12523, 12524, 12525), - archipelago_id=ITEM_OFFSET + 100 + 21, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE: ZorkGrandInquisitorItemData( - statemap_keys=(13002,), - archipelago_id=ITEM_OFFSET + 100 + 22, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL: ZorkGrandInquisitorItemData( - statemap_keys=(10726,), - archipelago_id=ITEM_OFFSET + 100 + 23, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR: ZorkGrandInquisitorItemData( - statemap_keys=(12280,), - archipelago_id=ITEM_OFFSET + 100 + 24, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS: ZorkGrandInquisitorItemData( - statemap_keys=( - 17694, - 17695, - 17696, - 17697, - 18200, - 17703, - 17704, - 17705, - 17710, - 17711, - 17712, - 17713, - 17714, - 17715, - 17716, - 17722, - 17723, - 17724, - 17725, - 17726, - 17727 - ), - archipelago_id=ITEM_OFFSET + 100 + 25, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS: ZorkGrandInquisitorItemData( - statemap_keys=(8448, 8449, 8450, 8451, 8452, 8453, 8454, 8455, 8456, 8457, 8458, 8459), - archipelago_id=ITEM_OFFSET + 100 + 26, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER: ZorkGrandInquisitorItemData( - statemap_keys=(8446,), - archipelago_id=ITEM_OFFSET + 100 + 27, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_HARRY: ZorkGrandInquisitorItemData( - statemap_keys=(4260,), - archipelago_id=ITEM_OFFSET + 100 + 28, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY: ZorkGrandInquisitorItemData( - statemap_keys=(18026,), - archipelago_id=ITEM_OFFSET + 100 + 29, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH: ZorkGrandInquisitorItemData( - statemap_keys=(17623,), - archipelago_id=ITEM_OFFSET + 100 + 30, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR: ZorkGrandInquisitorItemData( - statemap_keys=(13140,), - archipelago_id=ITEM_OFFSET + 100 + 31, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR: ZorkGrandInquisitorItemData( - statemap_keys=(10441,), - archipelago_id=ITEM_OFFSET + 100 + 32, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS: ZorkGrandInquisitorItemData( - statemap_keys=(19632, 19627), - archipelago_id=ITEM_OFFSET + 100 + 33, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR: ZorkGrandInquisitorItemData( - statemap_keys=(3025,), - archipelago_id=ITEM_OFFSET + 100 + 34, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG: ZorkGrandInquisitorItemData( - statemap_keys=(3036,), - archipelago_id=ITEM_OFFSET + 100 + 35, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_MIRROR: ZorkGrandInquisitorItemData( - statemap_keys=(5031,), - archipelago_id=ITEM_OFFSET + 100 + 36, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT: ZorkGrandInquisitorItemData( - statemap_keys=(13597,), - archipelago_id=ITEM_OFFSET + 100 + 37, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE: ZorkGrandInquisitorItemData( - statemap_keys=(13390,), - archipelago_id=ITEM_OFFSET + 100 + 38, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR: ZorkGrandInquisitorItemData( - statemap_keys=(2455, 2447), - archipelago_id=ITEM_OFFSET + 100 + 39, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS: ZorkGrandInquisitorItemData( - statemap_keys=(12389, 12390), - archipelago_id=ITEM_OFFSET + 100 + 40, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE: ZorkGrandInquisitorItemData( - statemap_keys=(4302,), - archipelago_id=ITEM_OFFSET + 100 + 41, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE: ZorkGrandInquisitorItemData( - statemap_keys=(16383, 16384), - archipelago_id=ITEM_OFFSET + 100 + 42, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE: ZorkGrandInquisitorItemData( - statemap_keys=(2769,), - archipelago_id=ITEM_OFFSET + 100 + 43, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON: ZorkGrandInquisitorItemData( - statemap_keys=(4149,), - archipelago_id=ITEM_OFFSET + 100 + 44, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS: ZorkGrandInquisitorItemData( - statemap_keys=(12584, 12585, 12586, 12587), - archipelago_id=ITEM_OFFSET + 100 + 45, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT: ZorkGrandInquisitorItemData( - statemap_keys=(12574,), - archipelago_id=ITEM_OFFSET + 100 + 46, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT: ZorkGrandInquisitorItemData( - statemap_keys=(13412,), - archipelago_id=ITEM_OFFSET + 100 + 47, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER: ZorkGrandInquisitorItemData( - statemap_keys=(12170,), - archipelago_id=ITEM_OFFSET + 100 + 48, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM: ZorkGrandInquisitorItemData( - statemap_keys=(16382,), - archipelago_id=ITEM_OFFSET + 100 + 49, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM: ZorkGrandInquisitorItemData( - statemap_keys=(4209,), - archipelago_id=ITEM_OFFSET + 100 + 50, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE: ZorkGrandInquisitorItemData( - statemap_keys=(11973,), - archipelago_id=ITEM_OFFSET + 100 + 51, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT: ZorkGrandInquisitorItemData( - statemap_keys=(13168,), - archipelago_id=ITEM_OFFSET + 100 + 52, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY: ZorkGrandInquisitorItemData( - statemap_keys=(15396,), - archipelago_id=ITEM_OFFSET + 100 + 53, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH: ZorkGrandInquisitorItemData( - statemap_keys=(9706,), - archipelago_id=ITEM_OFFSET + 100 + 54, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS: ZorkGrandInquisitorItemData( - statemap_keys=(9728, 9729, 9730), - archipelago_id=ITEM_OFFSET + 100 + 55, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - ZorkGrandInquisitorItems.HOTSPOT_WELL: ZorkGrandInquisitorItemData( - statemap_keys=(10314,), - archipelago_id=ITEM_OFFSET + 100 + 56, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.HOTSPOT,), - ), - # Spells - ZorkGrandInquisitorItems.SPELL_GLORF: ZorkGrandInquisitorItemData( - statemap_keys=(202,), - archipelago_id=ITEM_OFFSET + 200 + 0, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SPELL,), - ), - ZorkGrandInquisitorItems.SPELL_GOLGATEM: ZorkGrandInquisitorItemData( - statemap_keys=(192,), - archipelago_id=ITEM_OFFSET + 200 + 1, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SPELL,), - ), - ZorkGrandInquisitorItems.SPELL_IGRAM: ZorkGrandInquisitorItemData( - statemap_keys=(199,), - archipelago_id=ITEM_OFFSET + 200 + 2, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SPELL,), - ), - ZorkGrandInquisitorItems.SPELL_KENDALL: ZorkGrandInquisitorItemData( - statemap_keys=(196,), - archipelago_id=ITEM_OFFSET + 200 + 3, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SPELL,), - ), - ZorkGrandInquisitorItems.SPELL_NARWILE: ZorkGrandInquisitorItemData( - statemap_keys=(197,), - archipelago_id=ITEM_OFFSET + 200 + 4, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SPELL,), - ), - ZorkGrandInquisitorItems.SPELL_REZROV: ZorkGrandInquisitorItemData( - statemap_keys=(195,), - archipelago_id=ITEM_OFFSET + 200 + 5, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SPELL,), - ), - ZorkGrandInquisitorItems.SPELL_THROCK: ZorkGrandInquisitorItemData( - statemap_keys=(200,), - archipelago_id=ITEM_OFFSET + 200 + 6, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SPELL,), - ), - ZorkGrandInquisitorItems.SPELL_VOXAM: ZorkGrandInquisitorItemData( - statemap_keys=(191,), - archipelago_id=ITEM_OFFSET + 200 + 7, - classification=ItemClassification.useful, - tags=(ZorkGrandInquisitorTags.SPELL,), - ), - # Subway Destinations - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM: ZorkGrandInquisitorItemData( - statemap_keys=(13757, 13297, 13486, 13625), - archipelago_id=ITEM_OFFSET + 300 + 0, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,), - ), - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES: ZorkGrandInquisitorItemData( - statemap_keys=(13758, 13309, 13498, 13637), - archipelago_id=ITEM_OFFSET + 300 + 1, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,), - ), - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY: ZorkGrandInquisitorItemData( - statemap_keys=(13759, 13316, 13505, 13644), - archipelago_id=ITEM_OFFSET + 300 + 2, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.SUBWAY_DESTINATION,), - ), - # Teleporter Destinations - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR: ZorkGrandInquisitorItemData( - statemap_keys=(2203,), - archipelago_id=ITEM_OFFSET + 400 + 0, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), - ), - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH: ZorkGrandInquisitorItemData( - statemap_keys=(7132,), - archipelago_id=ITEM_OFFSET + 400 + 1, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), - ), - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES: ZorkGrandInquisitorItemData( - statemap_keys=(7119,), - archipelago_id=ITEM_OFFSET + 400 + 2, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), - ), - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY: ZorkGrandInquisitorItemData( - statemap_keys=(7148,), - archipelago_id=ITEM_OFFSET + 400 + 3, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), - ), - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB: ZorkGrandInquisitorItemData( - statemap_keys=(16545,), - archipelago_id=ITEM_OFFSET + 400 + 4, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TELEPORTER_DESTINATION,), - ), - # Totemizer Destinations - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION: ZorkGrandInquisitorItemData( - statemap_keys=(9660,), - archipelago_id=ITEM_OFFSET + 500 + 0, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), - ), - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY: ZorkGrandInquisitorItemData( - statemap_keys=(9666,), - archipelago_id=ITEM_OFFSET + 500 + 1, - classification=ItemClassification.filler, - tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), - ), - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL: ZorkGrandInquisitorItemData( - statemap_keys=(9668,), - archipelago_id=ITEM_OFFSET + 500 + 2, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), - ), - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ: ZorkGrandInquisitorItemData( - statemap_keys=(9662,), - archipelago_id=ITEM_OFFSET + 500 + 3, - classification=ItemClassification.filler, - tags=(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION,), - ), - # Totems - ZorkGrandInquisitorItems.TOTEM_BROG: ZorkGrandInquisitorItemData( - statemap_keys=(4853,), - archipelago_id=ITEM_OFFSET + 600 + 0, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TOTEM,), - ), - ZorkGrandInquisitorItems.TOTEM_GRIFF: ZorkGrandInquisitorItemData( - statemap_keys=(4315,), - archipelago_id=ITEM_OFFSET + 600 + 1, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TOTEM,), - ), - ZorkGrandInquisitorItems.TOTEM_LUCY: ZorkGrandInquisitorItemData( - statemap_keys=(5223,), - archipelago_id=ITEM_OFFSET + 600 + 2, - classification=ItemClassification.progression, - tags=(ZorkGrandInquisitorTags.TOTEM,), - ), - # Filler - ZorkGrandInquisitorItems.FILLER_INQUISITION_PROPAGANDA_FLYER: ZorkGrandInquisitorItemData( - statemap_keys=None, - archipelago_id=ITEM_OFFSET + 700 + 0, - classification=ItemClassification.filler, - tags=(ZorkGrandInquisitorTags.FILLER,), - maximum_quantity=None, - ), - ZorkGrandInquisitorItems.FILLER_UNREADABLE_SPELL_SCROLL: ZorkGrandInquisitorItemData( - statemap_keys=None, - archipelago_id=ITEM_OFFSET + 700 + 1, - classification=ItemClassification.filler, - tags=(ZorkGrandInquisitorTags.FILLER,), - maximum_quantity=None, - ), - ZorkGrandInquisitorItems.FILLER_MAGIC_CONTRABAND: ZorkGrandInquisitorItemData( - statemap_keys=None, - archipelago_id=ITEM_OFFSET + 700 + 2, - classification=ItemClassification.filler, - tags=(ZorkGrandInquisitorTags.FILLER,), - maximum_quantity=None, - ), - ZorkGrandInquisitorItems.FILLER_FROBOZZ_ELECTRIC_GADGET: ZorkGrandInquisitorItemData( - statemap_keys=None, - archipelago_id=ITEM_OFFSET + 700 + 3, - classification=ItemClassification.filler, - tags=(ZorkGrandInquisitorTags.FILLER,), - maximum_quantity=None, - ), - ZorkGrandInquisitorItems.FILLER_NONSENSICAL_INQUISITION_PAPERWORK: ZorkGrandInquisitorItemData( - statemap_keys=None, - archipelago_id=ITEM_OFFSET + 700 + 4, - classification=ItemClassification.filler, - tags=(ZorkGrandInquisitorTags.FILLER,), - maximum_quantity=None, - ), -} diff --git a/worlds/zork_grand_inquisitor/data/location_data.py b/worlds/zork_grand_inquisitor/data/location_data.py deleted file mode 100644 index 8b4e57392d..0000000000 --- a/worlds/zork_grand_inquisitor/data/location_data.py +++ /dev/null @@ -1,1535 +0,0 @@ -from typing import Dict, NamedTuple, Optional, Tuple, Union - -from ..enums import ( - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorLocations, - ZorkGrandInquisitorRegions, - ZorkGrandInquisitorTags, -) - - -class ZorkGrandInquisitorLocationData(NamedTuple): - game_state_trigger: Optional[ - Tuple[ - Union[ - Tuple[str, str], - Tuple[int, int], - Tuple[int, Tuple[int, ...]], - ], - ..., - ] - ] - archipelago_id: Optional[int] - region: ZorkGrandInquisitorRegions - tags: Optional[Tuple[ZorkGrandInquisitorTags, ...]] = None - requirements: Optional[ - Tuple[ - Union[ - Union[ - ZorkGrandInquisitorItems, - ZorkGrandInquisitorEvents, - ], - Tuple[ - Union[ - ZorkGrandInquisitorItems, - ZorkGrandInquisitorEvents, - ], - ..., - ], - ], - ..., - ] - ] = None - event_item_name: Optional[str] = None - - -LOCATION_OFFSET = 9758067000 - -location_data: Dict[ - Union[ZorkGrandInquisitorLocations, ZorkGrandInquisitorEvents], ZorkGrandInquisitorLocationData -] = { - ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "tr2m"),), - archipelago_id=LOCATION_OFFSET + 0, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.ARREST_THE_VANDAL: ZorkGrandInquisitorLocationData( - game_state_trigger=((10789, 1),), - archipelago_id=LOCATION_OFFSET + 1, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, - ), - ), - ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED: ZorkGrandInquisitorLocationData( - game_state_trigger=((11787, 1), (11788, 1), (11789, 1)), - archipelago_id=LOCATION_OFFSET + 2, - region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER: ZorkGrandInquisitorLocationData( - game_state_trigger=((8929, 1),), - archipelago_id=LOCATION_OFFSET + 3, - region=ZorkGrandInquisitorRegions.HADES, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorEvents.KNOWS_OBIDIL,), - ), - ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE: ZorkGrandInquisitorLocationData( - game_state_trigger=((9124, 1),), - archipelago_id=LOCATION_OFFSET + 4, - region=ZorkGrandInquisitorRegions.HADES, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE, - ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX, - ), - ), - ZorkGrandInquisitorLocations.A_SMALLWAY: ZorkGrandInquisitorLocationData( - game_state_trigger=((11777, 1),), - archipelago_id=LOCATION_OFFSET + 5, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, - ZorkGrandInquisitorItems.SPELL_IGRAM, - ), - ), - ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY: ZorkGrandInquisitorLocationData( - game_state_trigger=((13278, 1),), - archipelago_id=LOCATION_OFFSET + 6, - region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE, - ZorkGrandInquisitorItems.SPELL_THROCK, - ), - ), - ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED: ZorkGrandInquisitorLocationData( - game_state_trigger=((16315, 1),), - archipelago_id=LOCATION_OFFSET + 7, - region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE, - ZorkGrandInquisitorItems.SPELL_KENDALL, - ), - ), - ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "th3x"),), - archipelago_id=LOCATION_OFFSET + 8, - region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE,), - ), - ZorkGrandInquisitorLocations.BOING_BOING_BOING: ZorkGrandInquisitorLocationData( - game_state_trigger=((4220, 1),), - archipelago_id=LOCATION_OFFSET + 9, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.HAMMER, - ZorkGrandInquisitorItems.SNAPDRAGON, - ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, - ), - ), - ZorkGrandInquisitorLocations.BONK: ZorkGrandInquisitorLocationData( - game_state_trigger=((19491, 1),), - archipelago_id=LOCATION_OFFSET + 10, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.HAMMER, - ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON, - ), - ), - ZorkGrandInquisitorLocations.BRAVE_SOULS_WANTED: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "us2g"),), - archipelago_id=LOCATION_OFFSET + 11, - region=ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.BROG_DO_GOOD: ZorkGrandInquisitorLocationData( - game_state_trigger=((2644, 1),), - archipelago_id=LOCATION_OFFSET + 12, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_BROG, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG, - ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, - ) - ), - ZorkGrandInquisitorLocations.BROG_EAT_ROCKS: ZorkGrandInquisitorLocationData( - game_state_trigger=((2629, 1),), - archipelago_id=LOCATION_OFFSET + 13, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_BROG, - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, - ) - ), - ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB: ZorkGrandInquisitorLocationData( - game_state_trigger=((2650, 1),), - archipelago_id=LOCATION_OFFSET + 14, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_BROG, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG, - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, - ) - ), - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME: ZorkGrandInquisitorLocationData( - game_state_trigger=((15715, 1),), - archipelago_id=LOCATION_OFFSET + 15, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_BROG, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG, - ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT, - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, - ZorkGrandInquisitorItems.BROGS_PLANK, - ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE, - ) - ), - ZorkGrandInquisitorLocations.CASTLE_WATCHING_A_FIELD_GUIDE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "dv1t"),), - archipelago_id=LOCATION_OFFSET + 16, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.CAVES_NOTES: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "th3y"),), - archipelago_id=LOCATION_OFFSET + 17, - region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE,), - ), - ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS: ZorkGrandInquisitorLocationData( - game_state_trigger=((9543, 1),), - archipelago_id=LOCATION_OFFSET + 18, - region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.CRISIS_AVERTED: ZorkGrandInquisitorLocationData( - game_state_trigger=((11769, 1),), - archipelago_id=LOCATION_OFFSET + 19, - region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED, - ZorkGrandInquisitorItems.SPELL_IGRAM, - ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, - ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER, - ), - ), - ZorkGrandInquisitorLocations.CUT_THAT_OUT_YOU_LITTLE_CREEP: ZorkGrandInquisitorLocationData( - game_state_trigger=((19350, 1),), - archipelago_id=LOCATION_OFFSET + 20, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER: ZorkGrandInquisitorLocationData( - game_state_trigger=((17632, 1),), - archipelago_id=LOCATION_OFFSET + 21, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_BLINDS, - ZorkGrandInquisitorItems.SPELL_GOLGATEM, - ), - ), - ZorkGrandInquisitorLocations.DESPERATELY_SEEKING_TUTOR: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "tr2q"),), - archipelago_id=LOCATION_OFFSET + 22, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "hp5e"), (8919, 2), (9, 100)), - archipelago_id=LOCATION_OFFSET + 23, - region=ZorkGrandInquisitorRegions.HADES, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.SWORD,), - ), - ZorkGrandInquisitorLocations.DOOOOOOWN: ZorkGrandInquisitorLocationData( - game_state_trigger=((3619, 3600),), - archipelago_id=LOCATION_OFFSET + 24, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_GRIFF, - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, - ), - ), - ZorkGrandInquisitorLocations.DOWN: ZorkGrandInquisitorLocationData( - game_state_trigger=((3619, 5300),), - archipelago_id=LOCATION_OFFSET + 25, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_LUCY, - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, - ), - ), - ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL: ZorkGrandInquisitorLocationData( - game_state_trigger=((9216, 1),), - archipelago_id=LOCATION_OFFSET + 26, - region=ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.SPELL_NARWILE,), - ), - ZorkGrandInquisitorLocations.DUNCE_LOCKER: ZorkGrandInquisitorLocationData( - game_state_trigger=((11851, 1),), - archipelago_id=LOCATION_OFFSET + 27, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, - ), - ), - ZorkGrandInquisitorLocations.EGGPLANTS: ZorkGrandInquisitorLocationData( - game_state_trigger=((3816, 11000),), - archipelago_id=LOCATION_OFFSET + 28, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.ELSEWHERE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "pc1e"),), - archipelago_id=LOCATION_OFFSET + 29, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE: ZorkGrandInquisitorLocationData( - game_state_trigger=((11784, 1),), - archipelago_id=LOCATION_OFFSET + 30, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - ), - ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP: ZorkGrandInquisitorLocationData( - game_state_trigger=((13743, 1),), - archipelago_id=LOCATION_OFFSET + 31, - region=ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.SPELL_KENDALL,), - ), - ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA: ZorkGrandInquisitorLocationData( - game_state_trigger=((16368, 1),), - archipelago_id=LOCATION_OFFSET + 32, - region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorItems.SPELL_IGRAM,), - ), - ZorkGrandInquisitorLocations.FIRE_FIRE: ZorkGrandInquisitorLocationData( - game_state_trigger=((10277, 1),), - archipelago_id=LOCATION_OFFSET + 33, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, - ), - ), - ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "ue1h"),), - archipelago_id=LOCATION_OFFSET + 34, - region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON: ZorkGrandInquisitorLocationData( - game_state_trigger=((4222, 1),), - archipelago_id=LOCATION_OFFSET + 35, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.SPELL_THROCK, - ZorkGrandInquisitorItems.SNAPDRAGON, - ZorkGrandInquisitorItems.HAMMER, - ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, - ), - ), - ZorkGrandInquisitorLocations.FROBUARY_3_UNDERGROUNDHOG_DAY: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "dw2g"),), - archipelago_id=LOCATION_OFFSET + 36, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE: ZorkGrandInquisitorLocationData( - game_state_trigger=((12892, 1),), - archipelago_id=LOCATION_OFFSET + 37, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE, - ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT, - ), - ), - ZorkGrandInquisitorLocations.GO_AWAY: ZorkGrandInquisitorLocationData( - game_state_trigger=((10654, 1),), - archipelago_id=LOCATION_OFFSET + 38, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.GUE_TECH_DEANS_LIST: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "tr2k"),), - archipelago_id=LOCATION_OFFSET + 39, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.GUE_TECH_ENTRANCE_EXAM: ZorkGrandInquisitorLocationData( - game_state_trigger=((11082, 1), (11307, 1), (11536, 1)), - archipelago_id=LOCATION_OFFSET + 40, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.GUE_TECH_HEALTH_MEMO: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "tr2j"),), - archipelago_id=LOCATION_OFFSET + 41, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.GUE_TECH_MAGEMEISTERS: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "tr2n"),), - archipelago_id=LOCATION_OFFSET + 42, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY: ZorkGrandInquisitorLocationData( - game_state_trigger=((8443, 1),), - archipelago_id=LOCATION_OFFSET + 43, - region=ZorkGrandInquisitorRegions.HADES_SHORE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER, - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS, - ) - ), - ZorkGrandInquisitorLocations.HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING: ZorkGrandInquisitorLocationData( - game_state_trigger=((4698, 1),), - archipelago_id=LOCATION_OFFSET + 44, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE: ZorkGrandInquisitorLocationData( - game_state_trigger=((10421, 1),), - archipelago_id=LOCATION_OFFSET + 45, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH, - ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER, - ), - ), - ZorkGrandInquisitorLocations.HEY_FREE_DIRT: ZorkGrandInquisitorLocationData( - game_state_trigger=((11747, 1),), - archipelago_id=LOCATION_OFFSET + 46, - region=ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND, - ZorkGrandInquisitorItems.SHOVEL, - ), - ), - ZorkGrandInquisitorLocations.HI_MY_NAME_IS_DOUG: ZorkGrandInquisitorLocationData( - game_state_trigger=((4698, 2),), - archipelago_id=LOCATION_OFFSET + 47, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "mt2h"),), - archipelago_id=LOCATION_OFFSET + 48, - region=ZorkGrandInquisitorRegions.MONASTERY, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.HOLD_ON_FOR_AN_IMPORTANT_MESSAGE: ZorkGrandInquisitorLocationData( - game_state_trigger=((4698, 5),), - archipelago_id=LOCATION_OFFSET + 49, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.HOW_TO_HYPNOTIZE_YOURSELF: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "uh1e"),), - archipelago_id=LOCATION_OFFSET + 50, - region=ZorkGrandInquisitorRegions.HADES_SHORE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.HOW_TO_WIN_AT_DOUBLE_FANUCCI: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "th3s"),), - archipelago_id=LOCATION_OFFSET + 51, - region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE,), - ), - ZorkGrandInquisitorLocations.IMBUE_BEBURTT: ZorkGrandInquisitorLocationData( - game_state_trigger=((194, 1),), - archipelago_id=LOCATION_OFFSET + 52, - region=ZorkGrandInquisitorRegions.SPELL_LAB, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, - ), - ), - ZorkGrandInquisitorLocations.IM_COMPLETELY_NUDE: ZorkGrandInquisitorLocationData( - game_state_trigger=((19344, 1),), - archipelago_id=LOCATION_OFFSET + 53, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE: ZorkGrandInquisitorLocationData( - game_state_trigger=((13060, 1),), - archipelago_id=LOCATION_OFFSET + 54, - region=ZorkGrandInquisitorRegions.CROSSROADS, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE, - ), - ), - ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS: ZorkGrandInquisitorLocationData( - game_state_trigger=((12967, 1),), - archipelago_id=LOCATION_OFFSET + 55, - region=ZorkGrandInquisitorRegions.CROSSROADS, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.SPELL_IGRAM,), - ), - ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE: ZorkGrandInquisitorLocationData( - game_state_trigger=((12931, 1),), - archipelago_id=LOCATION_OFFSET + 56, - region=ZorkGrandInquisitorRegions.CROSSROADS, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HAMMER, - ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE, - ), - ), - ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST: ZorkGrandInquisitorLocationData( - game_state_trigger=((13062, 1),), - archipelago_id=LOCATION_OFFSET + 57, - region=ZorkGrandInquisitorRegions.CROSSROADS, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.SPELL_REZROV, - ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR, - ), - ), - ZorkGrandInquisitorLocations.ITS_ONE_OF_THOSE_ADVENTURERS_AGAIN: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "pe3j"),), - archipelago_id=LOCATION_OFFSET + 58, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY: ZorkGrandInquisitorLocationData( - game_state_trigger=((3816, 1008),), - archipelago_id=LOCATION_OFFSET + 59, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.SPELL_THROCK, - ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON, - ), - ), - ZorkGrandInquisitorLocations.I_DONT_WANT_NO_TROUBLE: ZorkGrandInquisitorLocationData( - game_state_trigger=((10694, 1),), - archipelago_id=LOCATION_OFFSET + 60, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE: ZorkGrandInquisitorLocationData( - game_state_trigger=((9637, 1),), - archipelago_id=LOCATION_OFFSET + 61, - region=ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorEvents.ROPE_GLORFABLE, - ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT, - ), - ), - ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE: ZorkGrandInquisitorLocationData( - game_state_trigger=((16374, 1),), - archipelago_id=LOCATION_OFFSET + 62, - region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE, - ZorkGrandInquisitorEvents.DAM_DESTROYED, - ZorkGrandInquisitorItems.SPELL_GOLGATEM, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM, - ), - ), - ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "tp1e"), (9, 87), (1011, 1)), - archipelago_id=LOCATION_OFFSET + 63, - region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,), - ), - ZorkGrandInquisitorLocations.LIT_SUNFLOWERS: ZorkGrandInquisitorLocationData( - game_state_trigger=((4129, 1),), - archipelago_id=LOCATION_OFFSET + 64, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.SPELL_THROCK,), - ), - ZorkGrandInquisitorLocations.MAGIC_FOREVER: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "pc1e"), (10304, 1), (5221, 1)), - archipelago_id=LOCATION_OFFSET + 65, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE, - ZorkGrandInquisitorItems.ROPE, - ZorkGrandInquisitorItems.HOTSPOT_WELL, - ), - ), - ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL: ZorkGrandInquisitorLocationData( - game_state_trigger=((2498, (1, 2)),), - archipelago_id=LOCATION_OFFSET + 66, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - (ZorkGrandInquisitorItems.TOTEM_GRIFF, ZorkGrandInquisitorItems.TOTEM_LUCY), - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR, - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, - ), - ), - ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR: ZorkGrandInquisitorLocationData( - game_state_trigger=((8623, 21),), - archipelago_id=LOCATION_OFFSET + 67, - region=ZorkGrandInquisitorRegions.HADES_SHORE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.CHARON_CALLED, - ZorkGrandInquisitorItems.SWORD, - ), - ), - ZorkGrandInquisitorLocations.MEAD_LIGHT: ZorkGrandInquisitorLocationData( - game_state_trigger=((10485, 1),), - archipelago_id=LOCATION_OFFSET + 68, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.MEAD_LIGHT, - ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, - ), - ), - ZorkGrandInquisitorLocations.MIKES_PANTS: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "tr2p"),), - archipelago_id=LOCATION_OFFSET + 69, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED: ZorkGrandInquisitorLocationData( - game_state_trigger=((4217, 1),), - archipelago_id=LOCATION_OFFSET + 70, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.HAMMER, - ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, - ), - ), - ZorkGrandInquisitorLocations.NATIONAL_TREASURE: ZorkGrandInquisitorLocationData( - game_state_trigger=((14318, 1),), - archipelago_id=LOCATION_OFFSET + 71, - region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.SPELL_REZROV, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS, - ), - ), - ZorkGrandInquisitorLocations.NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "dv1p"),), - archipelago_id=LOCATION_OFFSET + 72, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO: ZorkGrandInquisitorLocationData( - game_state_trigger=((12706, 1),), - archipelago_id=LOCATION_OFFSET + 73, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, - ), - ), - ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE: ZorkGrandInquisitorLocationData( - game_state_trigger=((4237, 1),), - archipelago_id=LOCATION_OFFSET + 74, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY, - ), - ), - ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT: ZorkGrandInquisitorLocationData( - game_state_trigger=((8935, 1),), - archipelago_id=LOCATION_OFFSET + 75, - region=ZorkGrandInquisitorRegions.HADES, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorEvents.KNOWS_SNAVIG,), - ), - ZorkGrandInquisitorLocations.NO_AUTOGRAPHS: ZorkGrandInquisitorLocationData( - game_state_trigger=((10476, 1),), - archipelago_id=LOCATION_OFFSET + 76, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR,), - ), - ZorkGrandInquisitorLocations.NO_BONDAGE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "pe2e"), (10262, 2), (15150, 83)), - archipelago_id=LOCATION_OFFSET + 77, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.ROPE, - ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH, - ), - ), - ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP: ZorkGrandInquisitorLocationData( - game_state_trigger=((12164, 1),), - archipelago_id=LOCATION_OFFSET + 78, - region=ZorkGrandInquisitorRegions.SPELL_LAB, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, - ), - ), - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON: ZorkGrandInquisitorLocationData( - game_state_trigger=((1300, 1),), - archipelago_id=LOCATION_OFFSET + 79, - region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, - ), - ), - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS: ZorkGrandInquisitorLocationData( - game_state_trigger=((2448, 1),), - archipelago_id=LOCATION_OFFSET + 80, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_BROG, - ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR, - ), - ), - ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU: ZorkGrandInquisitorLocationData( - game_state_trigger=((4869, 1),), - archipelago_id=LOCATION_OFFSET + 81, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.FLATHEADIA_FUDGE, - ZorkGrandInquisitorItems.HUNGUS_LARD, - ZorkGrandInquisitorItems.JAR_OF_HOTBUGS, - ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB, - ZorkGrandInquisitorItems.MOSS_OF_MAREILON, - ZorkGrandInquisitorItems.MUG, - ), - ), - ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER: ZorkGrandInquisitorLocationData( - game_state_trigger=((4512, 32),), - archipelago_id=LOCATION_OFFSET + 82, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, # This can be done anywhere if the item requirement is met - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD,), - ), - ZorkGrandInquisitorLocations.ONLY_YOU_CAN_PREVENT_FOOZLE_FIRES: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "pe5n"),), - archipelago_id=LOCATION_OFFSET + 83, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL: ZorkGrandInquisitorLocationData( - game_state_trigger=((8730, 1),), - archipelago_id=LOCATION_OFFSET + 84, - region=ZorkGrandInquisitorRegions.HADES, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorEvents.KNOWS_SNAVIG,), - ), - ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES: ZorkGrandInquisitorLocationData( - game_state_trigger=((4241, 1),), - archipelago_id=LOCATION_OFFSET + 85, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HUNGUS_LARD, - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE, - ), - ), - ZorkGrandInquisitorLocations.PERMASEAL: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "mt1g"),), - archipelago_id=LOCATION_OFFSET + 86, - region=ZorkGrandInquisitorRegions.MONASTERY, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.PLANETFALL: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "pp1j"),), - archipelago_id=LOCATION_OFFSET + 87, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.PLEASE_DONT_THROCK_THE_GRASS: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "te1g"),), - archipelago_id=LOCATION_OFFSET + 88, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL: ZorkGrandInquisitorLocationData( - game_state_trigger=((9404, 1),), - archipelago_id=LOCATION_OFFSET + 89, - region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER, - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT, - ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER, - ZorkGrandInquisitorItems.SPELL_NARWILE, - ), - ), - ZorkGrandInquisitorLocations.PROZORKED: ZorkGrandInquisitorLocationData( - game_state_trigger=((4115, 1),), - archipelago_id=LOCATION_OFFSET + 90, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.PROZORK_TABLET, - ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON, - ), - ), - ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG: ZorkGrandInquisitorLocationData( - game_state_trigger=((4512, 98),), - archipelago_id=LOCATION_OFFSET + 91, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS, - ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV, - ZorkGrandInquisitorItems.HOTSPOT_MIRROR, - ), - ), - ZorkGrandInquisitorLocations.RESTOCKED_ON_GRUESDAY: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "tr2h"),), - archipelago_id=LOCATION_OFFSET + 92, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE: ZorkGrandInquisitorLocationData( - game_state_trigger=((4698, 3),), - archipelago_id=LOCATION_OFFSET + 93, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE: ZorkGrandInquisitorLocationData( - game_state_trigger=((4698, 4),), - archipelago_id=LOCATION_OFFSET + 94, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.SNAVIG_REPAIRED: ZorkGrandInquisitorLocationData( - game_state_trigger=((201, 1),), - archipelago_id=LOCATION_OFFSET + 95, - region=ZorkGrandInquisitorRegions.SPELL_LAB, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, - ), - ), - ZorkGrandInquisitorLocations.SOUVENIR: ZorkGrandInquisitorLocationData( - game_state_trigger=((13408, 1),), - archipelago_id=LOCATION_OFFSET + 96, - region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, - ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT, - ), - ), - ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL: ZorkGrandInquisitorLocationData( - game_state_trigger=((9719, 1),), - archipelago_id=LOCATION_OFFSET + 97, - region=ZorkGrandInquisitorRegions.MONASTERY, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, - ), - ), - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER: ZorkGrandInquisitorLocationData( - game_state_trigger=((14511, 1), (14524, 5)), - archipelago_id=LOCATION_OFFSET + 98, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, - ), - ), - ZorkGrandInquisitorLocations.SUCKING_ROCKS: ZorkGrandInquisitorLocationData( - game_state_trigger=((12859, 1),), - archipelago_id=LOCATION_OFFSET + 99, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE, - ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT, - ), - ), - ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR: ZorkGrandInquisitorLocationData( - game_state_trigger=((10299, 1),), - archipelago_id=LOCATION_OFFSET + 100, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL,), - ), - ZorkGrandInquisitorLocations.TAMING_YOUR_SNAPDRAGON: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "dv1h"),), - archipelago_id=LOCATION_OFFSET + 101, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS: ZorkGrandInquisitorLocationData( - game_state_trigger=((1311, 1), (1312, 1)), - archipelago_id=LOCATION_OFFSET + 102, - region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, - ), - ), - ZorkGrandInquisitorLocations.THATS_A_ROPE: ZorkGrandInquisitorLocationData( - game_state_trigger=((10486, 1),), - archipelago_id=LOCATION_OFFSET + 103, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.ROPE, - ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, - ), - ), - ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS: ZorkGrandInquisitorLocationData( - game_state_trigger=((13805, 1),), - archipelago_id=LOCATION_OFFSET + 104, - region=ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - ), - ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "tp1e"), (9, 83), (1011, 1)), - archipelago_id=LOCATION_OFFSET + 105, - region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorEvents.ROPE_GLORFABLE,), - ), - ZorkGrandInquisitorLocations.THATS_THE_SPIRIT: ZorkGrandInquisitorLocationData( - game_state_trigger=((10341, 95),), - archipelago_id=LOCATION_OFFSET + 106, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS,), - ), - ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE: ZorkGrandInquisitorLocationData( - game_state_trigger=((9459, 1),), - archipelago_id=LOCATION_OFFSET + 107, - region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE: ZorkGrandInquisitorLocationData( - game_state_trigger=((9473, 1),), - archipelago_id=LOCATION_OFFSET + 108, - region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO: ZorkGrandInquisitorLocationData( - game_state_trigger=((9520, 1),), - archipelago_id=LOCATION_OFFSET + 109, - region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "me1j"),), - archipelago_id=LOCATION_OFFSET + 110, - region=ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND: ZorkGrandInquisitorLocationData( - game_state_trigger=((13167, 1),), - archipelago_id=LOCATION_OFFSET + 111, - region=ZorkGrandInquisitorRegions.CROSSROADS, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.SUBWAY_TOKEN, - ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT, - ), - ), - ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "cd60"), (1524, 1)), - archipelago_id=LOCATION_OFFSET + 112, - region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.TOTEM_LUCY,), - ), - ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED: ZorkGrandInquisitorLocationData( - game_state_trigger=((4219, 1),), - archipelago_id=LOCATION_OFFSET + 113, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HAMMER, - ZorkGrandInquisitorItems.SPELL_THROCK, - ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM, - ), - ), - ZorkGrandInquisitorLocations.TIME_TRAVEL_FOR_DUMMIES: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "th3z"),), - archipelago_id=LOCATION_OFFSET + 114, - region=ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE,), - ), - ZorkGrandInquisitorLocations.TOTEMIZED_DAILY_BILLBOARD: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "px1h"),), - archipelago_id=LOCATION_OFFSET + 115, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "cd60"), (1520, 1)), - archipelago_id=LOCATION_OFFSET + 116, - region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.TOTEM_BROG,), - ), - ZorkGrandInquisitorLocations.UMBRELLA_FLOWERS: ZorkGrandInquisitorLocationData( - game_state_trigger=((12926, 1),), - archipelago_id=LOCATION_OFFSET + 117, - region=ZorkGrandInquisitorRegions.CROSSROADS, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorEvents.KNOWS_BEBURTT,), - ), - ZorkGrandInquisitorLocations.UP: ZorkGrandInquisitorLocationData( - game_state_trigger=((3619, 5200),), - archipelago_id=LOCATION_OFFSET + 118, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_LUCY, - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, - ), - ), - ZorkGrandInquisitorLocations.USELESS_BUT_FUN: ZorkGrandInquisitorLocationData( - game_state_trigger=((14321, 1),), - archipelago_id=LOCATION_OFFSET + 119, - region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=(ZorkGrandInquisitorItems.SPELL_GOLGATEM,), - ), - ZorkGrandInquisitorLocations.UUUUUP: ZorkGrandInquisitorLocationData( - game_state_trigger=((3619, 3500),), - archipelago_id=LOCATION_OFFSET + 120, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_GRIFF, - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, - ), - ), - ZorkGrandInquisitorLocations.VOYAGE_OF_CAPTAIN_ZAHAB: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "uh1h"),), - archipelago_id=LOCATION_OFFSET + 121, - region=ZorkGrandInquisitorRegions.HADES_SHORE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO: ZorkGrandInquisitorLocationData( - game_state_trigger=((4034, 1),), - archipelago_id=LOCATION_OFFSET + 122, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR, - ZorkGrandInquisitorItems.MEAD_LIGHT, - ZorkGrandInquisitorItems.ZIMDOR_SCROLL, - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH, - ), - ), - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE: ZorkGrandInquisitorLocationData( - game_state_trigger=((2461, 1),), - archipelago_id=LOCATION_OFFSET + 123, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.TOTEM_GRIFF, - ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR, - ), - ), - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER: ZorkGrandInquisitorLocationData( - game_state_trigger=((15472, 1),), - archipelago_id=LOCATION_OFFSET + 124, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, - ), - ), - ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID: ZorkGrandInquisitorLocationData( - game_state_trigger=((10484, 1),), - archipelago_id=LOCATION_OFFSET + 125, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER, - ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, - ), - ), - ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL: ZorkGrandInquisitorLocationData( - game_state_trigger=((4983, 1),), - archipelago_id=LOCATION_OFFSET + 126, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR, - ZorkGrandInquisitorItems.SPELL_NARWILE, - ), - ), - ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "dc10"), (1596, 1)), - archipelago_id=LOCATION_OFFSET + 127, - region=ZorkGrandInquisitorRegions.WALKING_CASTLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "dm2g"),), - archipelago_id=LOCATION_OFFSET + 128, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorItems.HOTSPOT_MIRROR,), - ), - ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "dg4e"), (4266, 1), (9, 21), (4035, 1)), - archipelago_id=LOCATION_OFFSET + 129, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorItems.HOTSPOT_HARRY, - ), - ), - ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER: ZorkGrandInquisitorLocationData( - game_state_trigger=((16405, 1),), - archipelago_id=LOCATION_OFFSET + 130, - region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorItems.SPELL_REZROV,), - ), - ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS: ZorkGrandInquisitorLocationData( - game_state_trigger=((16342, 1),), - archipelago_id=LOCATION_OFFSET + 131, - region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - tags=(ZorkGrandInquisitorTags.CORE,), - requirements=( - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE, - ), - ), - ZorkGrandInquisitorLocations.YOU_ONE_OF_THEM_AGITATORS_AINT_YA: ZorkGrandInquisitorLocationData( - game_state_trigger=((10586, 1),), - archipelago_id=LOCATION_OFFSET + 132, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE,), - ), - ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY: ZorkGrandInquisitorLocationData( - game_state_trigger=((15151, 1),), - archipelago_id=LOCATION_OFFSET + 133, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.CORE, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH,), - ), - # Deathsanity - ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 1)), - archipelago_id=LOCATION_OFFSET + 200 + 0, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, - ), - ), - ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 20)), - archipelago_id=LOCATION_OFFSET + 200 + 1, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.SWORD, - ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE, - ), - ), - ZorkGrandInquisitorLocations.DEATH_CLIMBED_OUT_OF_THE_WELL: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 21)), - archipelago_id=LOCATION_OFFSET + 200 + 2, - region=ZorkGrandInquisitorRegions.CROSSROADS, - tags=(ZorkGrandInquisitorTags.DEATHSANITY,), - ), - ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 18)), - archipelago_id=LOCATION_OFFSET + 200 + 3, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.ROPE, - ZorkGrandInquisitorItems.HOTSPOT_WELL, - ), - ), - ZorkGrandInquisitorLocations.DEATH_JUMPED_IN_BOTTOMLESS_PIT: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 3)), - archipelago_id=LOCATION_OFFSET + 200 + 4, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.DEATHSANITY,), - ), - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 37)), - archipelago_id=LOCATION_OFFSET + 200 + 5, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY, - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS, - ), - ), - ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 23)), - archipelago_id=LOCATION_OFFSET + 200 + 6, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD,), - ), - ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 29)), - archipelago_id=LOCATION_OFFSET + 200 + 7, - region=ZorkGrandInquisitorRegions.DM_LAIR, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.HUNGUS_LARD, - ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE, - ), - ), - ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 30)), - archipelago_id=LOCATION_OFFSET + 200 + 8, - region=ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - ), - ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 4)), - archipelago_id=LOCATION_OFFSET + 200 + 9, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.SPELL_IGRAM, - ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS, - ), - ), - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 11)), - archipelago_id=LOCATION_OFFSET + 200 + 10, - region=ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=( - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS, - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, - ), - ), - ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 34)), - archipelago_id=LOCATION_OFFSET + 200 + 11, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.DEATHSANITY,), - requirements=( - ZorkGrandInquisitorItems.SPELL_THROCK, - ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS, - ), - ), - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, (9, 32, 33))), - archipelago_id=LOCATION_OFFSET + 200 + 12, - region=ZorkGrandInquisitorRegions.MONASTERY, - tags=(ZorkGrandInquisitorTags.DEATHSANITY,), - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH, - ), - ), - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, (5, 6, 7, 8, 13))), - archipelago_id=LOCATION_OFFSET + 200 + 13, - region=ZorkGrandInquisitorRegions.MONASTERY, - tags=(ZorkGrandInquisitorTags.DEATHSANITY,), - requirements=(ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH,), - ), - ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 10)), - archipelago_id=LOCATION_OFFSET + 200 + 14, - region=ZorkGrandInquisitorRegions.HADES, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorEvents.KNOWS_SNAVIG,), - ), - ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED: ZorkGrandInquisitorLocationData( - game_state_trigger=(("location", "gjde"), (2201, 19)), - archipelago_id=LOCATION_OFFSET + 200 + 15, - region=ZorkGrandInquisitorRegions.GUE_TECH, - tags=(ZorkGrandInquisitorTags.DEATHSANITY, ZorkGrandInquisitorTags.MISSABLE), - requirements=(ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED,), - ), - # Events - ZorkGrandInquisitorEvents.CHARON_CALLED: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.HADES_SHORE, - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER, - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS, - ), - event_item_name=ZorkGrandInquisitorEvents.CHARON_CALLED.value, - ), - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - requirements=( - ZorkGrandInquisitorItems.LANTERN, - ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR, - ), - event_item_name=ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE.value, - ), - ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.GUE_TECH, - requirements=( - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, - ), - event_item_name=ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, - ), - ZorkGrandInquisitorEvents.DAM_DESTROYED: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, - requirements=( - ZorkGrandInquisitorItems.SPELL_REZROV, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS, - ), - event_item_name=ZorkGrandInquisitorEvents.DAM_DESTROYED.value, - ), - ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.DM_LAIR, - requirements=( - ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR, - ZorkGrandInquisitorItems.MEAD_LIGHT, - ZorkGrandInquisitorItems.ZIMDOR_SCROLL, - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH, - ), - event_item_name=ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, - ), - ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.DM_LAIR, - requirements=( - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY, - ), - event_item_name=ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, - ), - ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.GUE_TECH, - requirements=( - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, - ), - event_item_name=ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, - ), - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.GUE_TECH, - requirements=( - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, - ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT, - ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS, - ), - event_item_name=ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, - ), - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - requirements=( - ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS, - ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV, - ZorkGrandInquisitorItems.HOTSPOT_MIRROR, - ), - event_item_name=ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, - ), - ZorkGrandInquisitorEvents.KNOWS_BEBURTT: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.SPELL_LAB, - requirements=( - ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, - ), - event_item_name=ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, - ), - ZorkGrandInquisitorEvents.KNOWS_OBIDIL: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.SPELL_LAB, - requirements=( - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, - ), - event_item_name=ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, - ), - ZorkGrandInquisitorEvents.KNOWS_SNAVIG: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.SPELL_LAB, - requirements=( - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER, - ), - event_item_name=ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, - ), - ZorkGrandInquisitorEvents.KNOWS_YASTARD: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - requirements=( - ZorkGrandInquisitorItems.FLATHEADIA_FUDGE, - ZorkGrandInquisitorItems.HUNGUS_LARD, - ZorkGrandInquisitorItems.JAR_OF_HOTBUGS, - ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB, - ZorkGrandInquisitorItems.MOSS_OF_MAREILON, - ZorkGrandInquisitorItems.MUG, - ), - event_item_name=ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, - ), - ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - requirements=( - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE, - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL, - ), - event_item_name=ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE.value, - ), - ZorkGrandInquisitorEvents.ROPE_GLORFABLE: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.CROSSROADS, - requirements=(ZorkGrandInquisitorItems.SPELL_GLORF,), - event_item_name=ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, - ), - ZorkGrandInquisitorEvents.VICTORY: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.ENDGAME, - event_item_name=ZorkGrandInquisitorEvents.VICTORY.value, - ), - ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.WHITE_HOUSE, - requirements=( - (ZorkGrandInquisitorItems.TOTEM_GRIFF, ZorkGrandInquisitorItems.TOTEM_LUCY), - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG, - ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR, - ), - event_item_name=ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, - ), - ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.PORT_FOOZLE, - requirements=(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD,), - event_item_name=ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE.value, - ), - ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.GUE_TECH, - requirements=( - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, - ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT, - ZorkGrandInquisitorItems.ZORK_ROCKS, - ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS, - ), - event_item_name=ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, - ), - ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE: ZorkGrandInquisitorLocationData( - game_state_trigger=None, - archipelago_id=None, - region=ZorkGrandInquisitorRegions.GUE_TECH, - requirements=( - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT, - ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS, - ), - event_item_name=ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, - ), -} diff --git a/worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py b/worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py deleted file mode 100644 index ef6eacb78c..0000000000 --- a/worlds/zork_grand_inquisitor/data/missable_location_grant_conditions_data.py +++ /dev/null @@ -1,200 +0,0 @@ -from typing import Dict, NamedTuple, Optional, Tuple - -from ..enums import ZorkGrandInquisitorItems, ZorkGrandInquisitorLocations - - -class ZorkGrandInquisitorMissableLocationGrantConditionsData(NamedTuple): - location_condition: ZorkGrandInquisitorLocations - item_conditions: Optional[Tuple[ZorkGrandInquisitorItems, ...]] - - -missable_location_grant_conditions_data: Dict[ - ZorkGrandInquisitorLocations, ZorkGrandInquisitorMissableLocationGrantConditionsData -] = { - ZorkGrandInquisitorLocations.BOING_BOING_BOING: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.BONK: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.PROZORKED, - item_conditions=(ZorkGrandInquisitorItems.HAMMER,), - ) - , - ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.ARREST_THE_VANDAL, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.MAGIC_FOREVER, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.A_SMALLWAY, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.THAR_SHE_BLOWS, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.CRISIS_AVERTED, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE, - item_conditions=(ZorkGrandInquisitorItems.SPELL_GOLGATEM,), - ) - , - ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, - item_conditions=(ZorkGrandInquisitorItems.SPELL_IGRAM,), - ) - , - ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.PROZORKED, - item_conditions=(ZorkGrandInquisitorItems.SPELL_THROCK,), - ) - , - ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, - item_conditions=(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS,), - ) - , - ZorkGrandInquisitorLocations.MEAD_LIGHT: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, - item_conditions=(ZorkGrandInquisitorItems.MEAD_LIGHT,), - ) - , - ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.NO_AUTOGRAPHS: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.NO_BONDAGE: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE, - item_conditions=(ZorkGrandInquisitorItems.ROPE,), - ) - , - ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.THATS_A_ROPE: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, - item_conditions=(ZorkGrandInquisitorItems.ROPE,), - ) - , - ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, - item_conditions=(ZorkGrandInquisitorItems.SPELL_GLORF,), - ) - , - ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.FIRE_FIRE, - item_conditions=(ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER,), - ) - , - ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG, - item_conditions=None, - ) - , - ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO, - item_conditions=(ZorkGrandInquisitorItems.SWORD, ZorkGrandInquisitorItems.HOTSPOT_HARRY), - ) - , - ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS, - item_conditions=(ZorkGrandInquisitorItems.SPELL_REZROV,), - ) - , - ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY: - ZorkGrandInquisitorMissableLocationGrantConditionsData( - location_condition=ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE, - item_conditions=None, - ) - , -} diff --git a/worlds/zork_grand_inquisitor/data/region_data.py b/worlds/zork_grand_inquisitor/data/region_data.py deleted file mode 100644 index 1aed160f30..0000000000 --- a/worlds/zork_grand_inquisitor/data/region_data.py +++ /dev/null @@ -1,183 +0,0 @@ -from typing import Dict, NamedTuple, Optional, Tuple - -from ..enums import ZorkGrandInquisitorRegions - - -class ZorkGrandInquisitorRegionData(NamedTuple): - exits: Optional[Tuple[ZorkGrandInquisitorRegions, ...]] - - -region_data: Dict[ZorkGrandInquisitorRegions, ZorkGrandInquisitorRegionData] = { - ZorkGrandInquisitorRegions.CROSSROADS: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.DM_LAIR, - ZorkGrandInquisitorRegions.GUE_TECH, - ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, - ZorkGrandInquisitorRegions.HADES_SHORE, - ZorkGrandInquisitorRegions.PORT_FOOZLE, - ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, - ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, - ) - ), - ZorkGrandInquisitorRegions.DM_LAIR: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.CROSSROADS, - ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, - ZorkGrandInquisitorRegions.HADES_SHORE, - ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, - ) - ), - ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.DM_LAIR, - ZorkGrandInquisitorRegions.WALKING_CASTLE, - ZorkGrandInquisitorRegions.WHITE_HOUSE, - ) - ), - ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, - ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, - ) - ), - ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, - ZorkGrandInquisitorRegions.ENDGAME, - ) - ), - ZorkGrandInquisitorRegions.ENDGAME: ZorkGrandInquisitorRegionData(exits=None), - ZorkGrandInquisitorRegions.GUE_TECH: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.CROSSROADS, - ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, - ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, - ) - ), - ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.GUE_TECH, - ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - ) - ), - ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.CROSSROADS, - ZorkGrandInquisitorRegions.DM_LAIR, - ZorkGrandInquisitorRegions.GUE_TECH, - ZorkGrandInquisitorRegions.HADES_SHORE, - ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, - ) - ), - ZorkGrandInquisitorRegions.HADES: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.HADES_BEYOND_GATES, - ZorkGrandInquisitorRegions.HADES_SHORE, - ) - ), - ZorkGrandInquisitorRegions.HADES_BEYOND_GATES: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO, - ZorkGrandInquisitorRegions.HADES, - ) - ), - ZorkGrandInquisitorRegions.HADES_SHORE: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.CROSSROADS, - ZorkGrandInquisitorRegions.DM_LAIR, - ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, - ZorkGrandInquisitorRegions.HADES, - ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE, - ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, - ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, - ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, - ) - ), - ZorkGrandInquisitorRegions.MENU: ZorkGrandInquisitorRegionData( - exits=(ZorkGrandInquisitorRegions.PORT_FOOZLE,) - ), - ZorkGrandInquisitorRegions.MONASTERY: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.HADES_SHORE, - ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, - ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, - ) - ), - ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.MONASTERY, - ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, - ) - ), - ZorkGrandInquisitorRegions.PORT_FOOZLE: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.CROSSROADS, - ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP, - ) - ), - ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP: ZorkGrandInquisitorRegionData( - exits=(ZorkGrandInquisitorRegions.PORT_FOOZLE,) - ), - ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT, - ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN, - ) - ), - ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.ENDGAME, - ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST, - ) - ), - ZorkGrandInquisitorRegions.SPELL_LAB: ZorkGrandInquisitorRegionData( - exits=(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE,) - ), - ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.CROSSROADS, - ZorkGrandInquisitorRegions.DM_LAIR, - ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, - ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY, - ZorkGrandInquisitorRegions.HADES_SHORE, - ZorkGrandInquisitorRegions.SPELL_LAB, - ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, - ) - ), - ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.CROSSROADS, - ZorkGrandInquisitorRegions.HADES_SHORE, - ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, - ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, - ) - ), - ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.HADES_SHORE, - ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, - ZorkGrandInquisitorRegions.SUBWAY_MONASTERY, - ) - ), - ZorkGrandInquisitorRegions.SUBWAY_MONASTERY: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.HADES_SHORE, - ZorkGrandInquisitorRegions.MONASTERY, - ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS, - ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM, - ) - ), - ZorkGrandInquisitorRegions.WALKING_CASTLE: ZorkGrandInquisitorRegionData( - exits=(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR,) - ), - ZorkGrandInquisitorRegions.WHITE_HOUSE: ZorkGrandInquisitorRegionData( - exits=( - ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, - ZorkGrandInquisitorRegions.ENDGAME, - ) - ), -} diff --git a/worlds/zork_grand_inquisitor/data_funcs.py b/worlds/zork_grand_inquisitor/data_funcs.py deleted file mode 100644 index 2a7bff1fbb..0000000000 --- a/worlds/zork_grand_inquisitor/data_funcs.py +++ /dev/null @@ -1,247 +0,0 @@ -from typing import Dict, List, Set, Tuple, Union - -from .data.entrance_rule_data import entrance_rule_data -from .data.item_data import item_data, ZorkGrandInquisitorItemData -from .data.location_data import location_data, ZorkGrandInquisitorLocationData - -from .enums import ( - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorGoals, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorLocations, - ZorkGrandInquisitorRegions, - ZorkGrandInquisitorTags, -) - - -def item_names_to_id() -> Dict[str, int]: - return {item.value: data.archipelago_id for item, data in item_data.items()} - - -def item_names_to_item() -> Dict[str, ZorkGrandInquisitorItems]: - return {item.value: item for item in item_data} - - -def location_names_to_id() -> Dict[str, int]: - return { - location.value: data.archipelago_id - for location, data in location_data.items() - if data.archipelago_id is not None - } - - -def location_names_to_location() -> Dict[str, ZorkGrandInquisitorLocations]: - return { - location.value: location - for location, data in location_data.items() - if data.archipelago_id is not None - } - - -def id_to_goals() -> Dict[int, ZorkGrandInquisitorGoals]: - return {goal.value: goal for goal in ZorkGrandInquisitorGoals} - - -def id_to_items() -> Dict[int, ZorkGrandInquisitorItems]: - return {data.archipelago_id: item for item, data in item_data.items()} - - -def id_to_locations() -> Dict[int, ZorkGrandInquisitorLocations]: - return { - data.archipelago_id: location - for location, data in location_data.items() - if data.archipelago_id is not None - } - - -def item_groups() -> Dict[str, List[str]]: - groups: Dict[str, List[str]] = dict() - - item: ZorkGrandInquisitorItems - data: ZorkGrandInquisitorItemData - for item, data in item_data.items(): - if data.tags is not None: - for tag in data.tags: - groups.setdefault(tag.value, list()).append(item.value) - - return {k: v for k, v in groups.items() if len(v)} - - -def items_with_tag(tag: ZorkGrandInquisitorTags) -> Set[ZorkGrandInquisitorItems]: - items: Set[ZorkGrandInquisitorItems] = set() - - item: ZorkGrandInquisitorItems - data: ZorkGrandInquisitorItemData - for item, data in item_data.items(): - if data.tags is not None and tag in data.tags: - items.add(item) - - return items - - -def game_id_to_items() -> Dict[int, ZorkGrandInquisitorItems]: - mapping: Dict[int, ZorkGrandInquisitorItems] = dict() - - item: ZorkGrandInquisitorItems - data: ZorkGrandInquisitorItemData - for item, data in item_data.items(): - if data.statemap_keys is not None: - for key in data.statemap_keys: - mapping[key] = item - - return mapping - - -def location_groups() -> Dict[str, List[str]]: - groups: Dict[str, List[str]] = dict() - - tag: ZorkGrandInquisitorTags - for tag in ZorkGrandInquisitorTags: - groups[tag.value] = list() - - location: ZorkGrandInquisitorLocations - data: ZorkGrandInquisitorLocationData - for location, data in location_data.items(): - if data.tags is not None: - for tag in data.tags: - groups[tag.value].append(location.value) - - return {k: v for k, v in groups.items() if len(v)} - - -def locations_by_region(include_deathsanity: bool = False) -> Dict[ - ZorkGrandInquisitorRegions, List[ZorkGrandInquisitorLocations] -]: - mapping: Dict[ZorkGrandInquisitorRegions, List[ZorkGrandInquisitorLocations]] = dict() - - region: ZorkGrandInquisitorRegions - for region in ZorkGrandInquisitorRegions: - mapping[region] = list() - - location: ZorkGrandInquisitorLocations - data: ZorkGrandInquisitorLocationData - for location, data in location_data.items(): - if not include_deathsanity and ZorkGrandInquisitorTags.DEATHSANITY in ( - data.tags or tuple() - ): - continue - - mapping[data.region].append(location) - - return mapping - - -def locations_with_tag(tag: ZorkGrandInquisitorTags) -> Set[ZorkGrandInquisitorLocations]: - location: ZorkGrandInquisitorLocations - data: ZorkGrandInquisitorLocationData - - return {location for location, data in location_data.items() if data.tags is not None and tag in data.tags} - - -def location_access_rule_for(location: ZorkGrandInquisitorLocations, player: int) -> str: - data: ZorkGrandInquisitorLocationData = location_data[location] - - if data.requirements is None: - return "lambda state: True" - - lambda_string: str = "lambda state: " - - i: int - requirement: Union[ - Tuple[ - Union[ - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems, - ], - ..., - ], - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems - ] - - for i, requirement in enumerate(data.requirements): - if isinstance(requirement, tuple): - lambda_string += "(" - - ii: int - sub_requirement: Union[ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems] - for ii, sub_requirement in enumerate(requirement): - lambda_string += f"state.has(\"{sub_requirement.value}\", {player})" - - if ii < len(requirement) - 1: - lambda_string += " or " - - lambda_string += ")" - else: - lambda_string += f"state.has(\"{requirement.value}\", {player})" - - if i < len(data.requirements) - 1: - lambda_string += " and " - - return lambda_string - - -def entrance_access_rule_for( - region_origin: ZorkGrandInquisitorRegions, - region_destination: ZorkGrandInquisitorRegions, - player: int -) -> str: - data: Union[ - Tuple[ - Tuple[ - Union[ - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorRegions, - ], - ..., - ], - ..., - ], - None, - ] = entrance_rule_data[(region_origin, region_destination)] - - if data is None: - return "lambda state: True" - - lambda_string: str = "lambda state: " - - i: int - requirement_group: Tuple[ - Union[ - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorRegions, - ], - ..., - ] - for i, requirement_group in enumerate(data): - lambda_string += "(" - - ii: int - requirement: Union[ - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorRegions, - ] - for ii, requirement in enumerate(requirement_group): - requirement_type: Union[ - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorRegions, - ] = type(requirement) - - if requirement_type in (ZorkGrandInquisitorEvents, ZorkGrandInquisitorItems): - lambda_string += f"state.has(\"{requirement.value}\", {player})" - elif requirement_type == ZorkGrandInquisitorRegions: - lambda_string += f"state.can_reach(\"{requirement.value}\", \"Region\", {player})" - - if ii < len(requirement_group) - 1: - lambda_string += " and " - - lambda_string += ")" - - if i < len(data) - 1: - lambda_string += " or " - - return lambda_string diff --git a/worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md b/worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md deleted file mode 100644 index d5821914be..0000000000 --- a/worlds/zork_grand_inquisitor/docs/en_Zork Grand Inquisitor.md +++ /dev/null @@ -1,102 +0,0 @@ -# Zork Grand Inquisitor - -## Where is the options page? - -The [player options page for this game](../player-options) contains all the options you need to configure and export a -configuration file. - -## Is a tracker available for this game? - -Yes! You can download the latest PopTracker pack for Zork Grand Inquisitor [here](https://github.com/SerpentAI/ZorkGrandInquisitorAPTracker/releases/latest). - -## What does randomization do to this game? - -A majority of inventory items you can normally pick up are completely removed from the game (e.g. the lantern won't be -in the crate, the mead won't be at the fish market, etc.). Instead, these items will be distributed in the multiworld. -This means that you can expect to access areas and be in a position to solve certain puzzles in a completely different -order than you normally would. - -Subway, teleporter and totemizer destinations are initially locked and need to be unlocked by receiving the -corresponding item in the multiworld. This alone enables creative routing in a game that would otherwise be rather -linear. The Crossroads destination is always unlocked for both the subway and teleporter to prevent softlocks. Until you -receive your first totemizer destination, it will be locked to Newark, New Jersey. - -Important hotspots are also randomized. This means that you will be unable to interact with certain objects until you -receive the corresponding item in the multiworld. This can be a bit confusing at first, but it adds depth to the -randomization and makes the game more interesting to play. - -You can travel back to the surface without dying by looking inside the bucket. This will work as long as the rope is -still attached to the well. - -Attempting to cast VOXAM will teleport you back to the Crossroads. Fast Travel! - -## What item types are distributed in the multiworld? - -- Inventory items -- Pouch of Zorkmids -- Spells -- Totems -- Subway destinations -- Teleporter destinations -- Totemizer destinations -- Hotspots (with option to start with the items enabling them instead if you prefer not playing with the randomization - of hotspots) - -## When the player receives an item, what happens? - -- **Inventory items**: Directly added to the player's inventory. -- **Pouch of Zorkmids**: Appears on the inventory screen. The player can then pick up Zorkmid coins from it. -- **Spells**: Learned and directly added to the spell book. -- **Totems**: Appears on the inventory screen. -- **Subway destinations**: The destination button on the subway map becomes functional. -- **Teleporter destinations**: The destination can show up on the teleporter screen. -- **Totemizer destinations**: The destination button on the panel becomes functional. -- **Hotspots**: The hotspot becomes interactable. - -## What is considered a location check in Zork Grand Inquisitor? - -- Solving puzzles -- Accessing certain areas for the first time -- Triggering certain interactions, even if they aren't puzzles per se -- Dying in unique ways (Optional; Deathsanity option) - -## The location check names are fun but don't always convey well what's needed to unlock them. Is there a guide? - -Yes! You can find a complete guide for the location checks [here](https://gist.github.com/nbrochu/f7bed7a1fef4e2beb67ad6ddbf18b970). - -## What is the victory condition? - -Victory is achieved when the 3 artifacts of magic are retrieved and placed inside the walking castle. - -## Can I use the save system without a problem? - -Absolutely! The save system is fully supported (and its use is in fact strongly encouraged!). You can save and load your -game as you normally would and the client will automatically sync your items and hotspots with what you should have in -that game state. - -Depending on how your game progresses, there's a chance that certain location checks might become missable. This -presents an excellent opportunity to utilize the save system. Simply make it a habit to save before undertaking -irreversible actions, ensuring you can revert to a previous state if necessary. If you prefer not to depend on the save -system for accessing missable location checks, there's an option to automatically unlock them as they become -unavailable. - -## Unique Local Commands -The following commands are only available when using the Zork Grand Inquisitor Client to play the game with Archipelago. - -- `/zork` Attempts to attach to a running instance of Zork Grand Inquisitor. If successful, the client will then be able - to read and control the state of the game. -- `/brog` Lists received items for Brog. -- `/griff` Lists received items for Griff. -- `/lucy` Lists received items for Lucy. -- `/hotspots` Lists received hotspots. - -## Known issues - -- You will get a second rope right after using GLORF (one in your inventory and one on your cursor). This is a harmless - side effect that will go away after you store it in your inventory as duplicates are actively removed. -- After climbing up to the Monastery for the first time, a rope will forever remain in place in the vent. When you come - back to the Monastery, you will be able to climb up without needing to combine the sword and rope again. However, when - arriving at the top, you will receive a duplicate sword on a rope. This is a harmless side effect that will go away - after you store it in your inventory as duplicates are actively removed. -- Since the client is reading and manipulating the game's memory, rare game crashes can happen. If you encounter one, - simply restart the game, load your latest save and use the `/zork` command again in the client. Nothing will be lost. diff --git a/worlds/zork_grand_inquisitor/docs/setup_en.md b/worlds/zork_grand_inquisitor/docs/setup_en.md deleted file mode 100644 index f9078c6d39..0000000000 --- a/worlds/zork_grand_inquisitor/docs/setup_en.md +++ /dev/null @@ -1,42 +0,0 @@ -# Zork Grand Inquisitor Randomizer Setup Guide - -## Requirements - -- Windows OS (Hard required. Client is using memory reading / writing through Win32 API) -- A copy of Zork Grand Inquisitor. Only the GOG version is supported. The Steam version can work with some tinkering but - is not officially supported. -- ScummVM 2.7.1 64-bit (Important: Will not work with any other version. [Direct Download](https://downloads.scummvm.org/frs/scummvm/2.7.1/scummvm-2.7.1-win32-x86_64.zip)) -- Archipelago 0.4.4+ - -## Game Setup Instructions - -No game modding is required to play Zork Grand Inquisitor with Archipelago. The client does all the work by attaching to -the game process and reading and manipulating the game state in real-time. - -This being said, the game does need to be played through ScummVM 2.7.1, so some configuration is required around that. - -### GOG - -- Open the directory where you installed Zork Grand Inquisitor. You should see a `Launch Zork Grand Inquisitor` - shortcut. -- Open the `scummvm` directory. Delete the entire contents of that directory. -- Still inside the `scummvm` directory, unzip the contents of the ScummVM 2.7.1 zip file you downloaded earlier. -- Go back to the directory where you installed Zork Grand Inquisitor. -- Verify that the game still launches when using the `Launch Zork Grand Inquisitor` shortcut. -- Your game is now ready to be played with Archipelago. From now on, you can use the `Launch Zork Grand Inquisitor` - shortcut to launch the game. - -## Joining a Multiworld Game - -- Launch Zork Grand Inquisitor and start a new game. -- Open the Archipelago Launcher and click `Zork Grand Inquisitor Client`. -- Using the `Zork Grand Inquisitor Client`: - - Enter the room's hostname and port number (e.g. `archipelago.gg:54321`) in the top box and press `Connect`. - - Input your player name at the bottom when prompted and press `Enter`. - - You should now be connected to the Archipelago room. - - Next, input `/zork` at the bottom and press `Enter`. This will attach the client to the game process. - - If the command is successful, you are now ready to play Zork Grand Inquisitor with Archipelago. - -## Continuing a Multiworld Game - -- Perform the same steps as above, but instead of starting a new game, load your latest save file. diff --git a/worlds/zork_grand_inquisitor/enums.py b/worlds/zork_grand_inquisitor/enums.py deleted file mode 100644 index ecbb38a949..0000000000 --- a/worlds/zork_grand_inquisitor/enums.py +++ /dev/null @@ -1,350 +0,0 @@ -import enum - - -class ZorkGrandInquisitorEvents(enum.Enum): - CHARON_CALLED = "Event: Charon Called" - CIGAR_ACCESSIBLE = "Event: Cigar Accessible" - DALBOZ_LOCKER_OPENABLE = "Event: Dalboz Locker Openable" - DAM_DESTROYED = "Event: Dam Destroyed" - DOOR_DRANK_MEAD = "Event: Door Drank Mead" - DOOR_SMOKED_CIGAR = "Event: Door Smoked Cigar" - DUNCE_LOCKER_OPENABLE = "Event: Dunce Locker Openable" - HAS_REPAIRABLE_OBIDIL = "Event: Has Repairable OBIDIL" - HAS_REPAIRABLE_SNAVIG = "Event: Has Repairable SNAVIG" - KNOWS_BEBURTT = "Event: Knows BEBURTT" - KNOWS_OBIDIL = "Event: Knows OBIDIL" - KNOWS_SNAVIG = "Event: Knows SNAVIG" - KNOWS_YASTARD = "Event: Knows YASTARD" - LANTERN_DALBOZ_ACCESSIBLE = "Event: Lantern (Dalboz) Accessible" - ROPE_GLORFABLE = "Event: Rope GLORFable" - VICTORY = "Victory" - WHITE_HOUSE_LETTER_MAILABLE = "Event: White House Letter Mailable" - ZORKMID_BILL_ACCESSIBLE = "Event: 500 Zorkmid Bill Accessible" - ZORK_ROCKS_ACTIVATED = "Event: Zork Rocks Activated" - ZORK_ROCKS_SUCKABLE = "Event: Zork Rocks Suckable" - - -class ZorkGrandInquisitorGoals(enum.Enum): - THREE_ARTIFACTS = 0 - - -class ZorkGrandInquisitorItems(enum.Enum): - BROGS_BICKERING_TORCH = "Brog's Bickering Torch" - BROGS_FLICKERING_TORCH = "Brog's Flickering Torch" - BROGS_GRUE_EGG = "Brog's Grue Egg" - BROGS_PLANK = "Brog's Plank" - FILLER_FROBOZZ_ELECTRIC_GADGET = "Frobozz Electric Gadget" - FILLER_INQUISITION_PROPAGANDA_FLYER = "Inquisition Propaganda Flyer" - FILLER_MAGIC_CONTRABAND = "Magic Contraband" - FILLER_NONSENSICAL_INQUISITION_PAPERWORK = "Nonsensical Inquisition Paperwork" - FILLER_UNREADABLE_SPELL_SCROLL = "Unreadable Spell Scroll" - FLATHEADIA_FUDGE = "Flatheadia Fudge" - GRIFFS_AIR_PUMP = "Griff's Air Pump" - GRIFFS_DRAGON_TOOTH = "Griff's Dragon Tooth" - GRIFFS_INFLATABLE_RAFT = "Griff's Inflatable Raft" - GRIFFS_INFLATABLE_SEA_CAPTAIN = "Griff's Inflatable Sea Captain" - HAMMER = "Hammer" - HOTSPOT_666_MAILBOX = "Hotspot: 666 Mailbox" - HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS = "Hotspot: Alpine's Quandry Card Slots" - HOTSPOT_BLANK_SCROLL_BOX = "Hotspot: Blank Scroll Box" - HOTSPOT_BLINDS = "Hotspot: Blinds" - HOTSPOT_CANDY_MACHINE_BUTTONS = "Hotspot: Candy Machine Buttons" - HOTSPOT_CANDY_MACHINE_COIN_SLOT = "Hotspot: Candy Machine Coin Slot" - HOTSPOT_CANDY_MACHINE_VACUUM_SLOT = "Hotspot: Candy Machine Vacuum Slot" - HOTSPOT_CHANGE_MACHINE_SLOT = "Hotspot: Change Machine Slot" - HOTSPOT_CLOSET_DOOR = "Hotspot: Closet Door" - HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT = "Hotspot: Closing the Time Tunnels Hammer Slot" - HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER = "Hotspot: Closing the Time Tunnels Lever" - HOTSPOT_COOKING_POT = "Hotspot: Cooking Pot" - HOTSPOT_DENTED_LOCKER = "Hotspot: Dented Locker" - HOTSPOT_DIRT_MOUND = "Hotspot: Dirt Mound" - HOTSPOT_DOCK_WINCH = "Hotspot: Dock Winch" - HOTSPOT_DRAGON_CLAW = "Hotspot: Dragon Claw" - HOTSPOT_DRAGON_NOSTRILS = "Hotspot: Dragon Nostrils" - HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE = "Hotspot: Dungeon Master's Lair Entrance" - HOTSPOT_FLOOD_CONTROL_BUTTONS = "Hotspot: Flood Control Buttons" - HOTSPOT_FLOOD_CONTROL_DOORS = "Hotspot: Flood Control Doors" - HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT = "Hotspot: Frozen Treat Machine Coin Slot" - HOTSPOT_FROZEN_TREAT_MACHINE_DOORS = "Hotspot: Frozen Treat Machine Doors" - HOTSPOT_GLASS_CASE = "Hotspot: Glass Case" - HOTSPOT_GRAND_INQUISITOR_DOLL = "Hotspot: Grand Inquisitor Doll" - HOTSPOT_GUE_TECH_DOOR = "Hotspot: GUE Tech Door" - HOTSPOT_GUE_TECH_GRASS = "Hotspot: GUE Tech Grass" - HOTSPOT_HADES_PHONE_BUTTONS = "Hotspot: Hades Phone Buttons" - HOTSPOT_HADES_PHONE_RECEIVER = "Hotspot: Hades Phone Receiver" - HOTSPOT_HARRY = "Hotspot: Harry" - HOTSPOT_HARRYS_ASHTRAY = "Hotspot: Harry's Ashtray" - HOTSPOT_HARRYS_BIRD_BATH = "Hotspot: Harry's Bird Bath" - HOTSPOT_IN_MAGIC_WE_TRUST_DOOR = "Hotspot: In Magic We Trust Door" - HOTSPOT_JACKS_DOOR = "Hotspot: Jack's Door" - HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS = "Hotspot: Loudspeaker Volume Buttons" - HOTSPOT_MAILBOX_DOOR = "Hotspot: Mailbox Door" - HOTSPOT_MAILBOX_FLAG = "Hotspot: Mailbox Flag" - HOTSPOT_MIRROR = "Hotspot: Mirror" - HOTSPOT_MONASTERY_VENT = "Hotspot: Monastery Vent" - HOTSPOT_MOSSY_GRATE = "Hotspot: Mossy Grate" - HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR = "Hotspot: Port Foozle Past Tavern Door" - HOTSPOT_PURPLE_WORDS = "Hotspot: Purple Words" - HOTSPOT_QUELBEE_HIVE = "Hotspot: Quelbee Hive" - HOTSPOT_ROPE_BRIDGE = "Hotspot: Rope Bridge" - HOTSPOT_SKULL_CAGE = "Hotspot: Skull Cage" - HOTSPOT_SNAPDRAGON = "Hotspot: Snapdragon" - HOTSPOT_SODA_MACHINE_BUTTONS = "Hotspot: Soda Machine Buttons" - HOTSPOT_SODA_MACHINE_COIN_SLOT = "Hotspot: Soda Machine Coin Slot" - HOTSPOT_SOUVENIR_COIN_SLOT = "Hotspot: Souvenir Coin Slot" - HOTSPOT_SPELL_CHECKER = "Hotspot: Spell Checker" - HOTSPOT_SPELL_LAB_CHASM = "Hotspot: Spell Lab Chasm" - HOTSPOT_SPRING_MUSHROOM = "Hotspot: Spring Mushroom" - HOTSPOT_STUDENT_ID_MACHINE = "Hotspot: Student ID Machine" - HOTSPOT_SUBWAY_TOKEN_SLOT = "Hotspot: Subway Token Slot" - HOTSPOT_TAVERN_FLY = "Hotspot: Tavern Fly" - HOTSPOT_TOTEMIZER_SWITCH = "Hotspot: Totemizer Switch" - HOTSPOT_TOTEMIZER_WHEELS = "Hotspot: Totemizer Wheels" - HOTSPOT_WELL = "Hotspot: Well" - HUNGUS_LARD = "Hungus Lard" - JAR_OF_HOTBUGS = "Jar of Hotbugs" - LANTERN = "Lantern" - LARGE_TELEGRAPH_HAMMER = "Large Telegraph Hammer" - LUCYS_PLAYING_CARD_1 = "Lucy's Playing Card: 1 Pip" - LUCYS_PLAYING_CARD_2 = "Lucy's Playing Card: 2 Pips" - LUCYS_PLAYING_CARD_3 = "Lucy's Playing Card: 3 Pips" - LUCYS_PLAYING_CARD_4 = "Lucy's Playing Card: 4 Pips" - MAP = "Map" - MEAD_LIGHT = "Mead Light" - MOSS_OF_MAREILON = "Moss of Mareilon" - MUG = "Mug" - OLD_SCRATCH_CARD = "Old Scratch Card" - PERMA_SUCK_MACHINE = "Perma-Suck Machine" - PLASTIC_SIX_PACK_HOLDER = "Plastic Six-Pack Holder" - POUCH_OF_ZORKMIDS = "Pouch of Zorkmids" - PROZORK_TABLET = "Prozork Tablet" - QUELBEE_HONEYCOMB = "Quelbee Honeycomb" - ROPE = "Rope" - SCROLL_FRAGMENT_ANS = "Scroll Fragment: ANS" - SCROLL_FRAGMENT_GIV = "Scroll Fragment: GIV" - SHOVEL = "Shovel" - SNAPDRAGON = "Snapdragon" - SPELL_GLORF = "Spell: GLORF" - SPELL_GOLGATEM = "Spell: GOLGATEM" - SPELL_IGRAM = "Spell: IGRAM" - SPELL_KENDALL = "Spell: KENDALL" - SPELL_NARWILE = "Spell: NARWILE" - SPELL_REZROV = "Spell: REZROV" - SPELL_THROCK = "Spell: THROCK" - SPELL_VOXAM = "Spell: VOXAM" - STUDENT_ID = "Student ID" - SUBWAY_DESTINATION_FLOOD_CONTROL_DAM = "Subway Destination: Flood Control Dam #3" - SUBWAY_DESTINATION_HADES = "Subway Destination: Hades" - SUBWAY_DESTINATION_MONASTERY = "Subway Destination: Monastery" - SUBWAY_TOKEN = "Subway Token" - SWORD = "Sword" - TELEPORTER_DESTINATION_DM_LAIR = "Teleporter Destination: Dungeon Master's Lair" - TELEPORTER_DESTINATION_GUE_TECH = "Teleporter Destination: GUE Tech" - TELEPORTER_DESTINATION_HADES = "Teleporter Destination: Hades" - TELEPORTER_DESTINATION_MONASTERY = "Teleporter Destination: Monastery Station" - TELEPORTER_DESTINATION_SPELL_LAB = "Teleporter Destination: Spell Lab" - TOTEM_BROG = "Totem: Brog" - TOTEM_GRIFF = "Totem: Griff" - TOTEM_LUCY = "Totem: Lucy" - TOTEMIZER_DESTINATION_HALL_OF_INQUISITION = "Totemizer Destination: Hall of Inquisition" - TOTEMIZER_DESTINATION_INFINITY = "Totemizer Destination: Infinity" - TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL = "Totemizer Destination: Straight to Hell" - TOTEMIZER_DESTINATION_SURFACE_OF_MERZ = "Totemizer Destination: Surface of Merz" - ZIMDOR_SCROLL = "ZIMDOR Scroll" - ZORK_ROCKS = "Zork Rocks" - - -class ZorkGrandInquisitorLocations(enum.Enum): - ALARM_SYSTEM_IS_DOWN = "Alarm System is Down" - ARREST_THE_VANDAL = "Arrest the Vandal!" - ARTIFACTS_EXPLAINED = "Artifacts, Explained" - A_BIG_FAT_SASSY_2_HEADED_MONSTER = "A Big, Fat, SASSY 2-Headed Monster" - A_LETTER_FROM_THE_WHITE_HOUSE = "A Letter from the White House" - A_SMALLWAY = "A Smallway" - BEAUTIFUL_THATS_PLENTY = "Beautiful, That's Plenty!" - BEBURTT_DEMYSTIFIED = "BEBURTT, Demystified" - BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES = "Better Spell Manufacturing in Under 10 Minutes" - BOING_BOING_BOING = "Boing, Boing, Boing" - BONK = "Bonk!" - BRAVE_SOULS_WANTED = "Brave Souls Wanted" - BROG_DO_GOOD = "Brog Do Good!" - BROG_EAT_ROCKS = "Brog Eat Rocks" - BROG_KNOW_DUMB_THAT_DUMB = "Brog Know Dumb. That Dumb" - BROG_MUCH_BETTER_AT_THIS_GAME = "Brog Much Better at This Game" - CASTLE_WATCHING_A_FIELD_GUIDE = "Castle Watching: A Field Guide" - CAVES_NOTES = "Cave's Notes" - CLOSING_THE_TIME_TUNNELS = "Closing the Time Tunnels" - CRISIS_AVERTED = "Crisis Averted" - CUT_THAT_OUT_YOU_LITTLE_CREEP = "Cut That Out You Little Creep!" - DEATH_ARRESTED_WITH_JACK = "Death: Arrested With Jack" - DEATH_ATTACKED_THE_QUELBEES = "Death: Attacked the Quelbees" - DEATH_CLIMBED_OUT_OF_THE_WELL = "Death: Climbed Out of the Well" - DEATH_EATEN_BY_A_GRUE = "Death: Eaten by a Grue" - DEATH_JUMPED_IN_BOTTOMLESS_PIT = "Death: Jumped in Bottomless Pit" - DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER = "Death: Lost Game of Strip Grue, Fire, Water" - DEATH_LOST_SOUL_TO_OLD_SCRATCH = "Death: Lost Soul to Old Scratch" - DEATH_OUTSMARTED_BY_THE_QUELBEES = "Death: Outsmarted by the Quelbees" - DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD = "Death: Sliced up by the Invisible Guard" - DEATH_STEPPED_INTO_THE_INFINITE = "Death: Step Into the Infinite" - DEATH_SWALLOWED_BY_A_DRAGON = "Death: Swallowed by a Dragon" - DEATH_THROCKED_THE_GRASS = "Death: THROCKed the Grass" - DEATH_TOTEMIZED = "Death: Totemized?" - DEATH_TOTEMIZED_PERMANENTLY = "Death: Totemized... Permanently" - DEATH_YOURE_NOT_CHARON = "Death: You're Not Charon!?" - DEATH_ZORK_ROCKS_EXPLODED = "Death: Zork Rocks Exploded" - DENIED_BY_THE_LAKE_MONSTER = "Denied by the Lake Monster" - DESPERATELY_SEEKING_TUTOR = "Desperately Seeking Tutor" - DONT_EVEN_START_WITH_US_SPARKY = "Don't Even Start With Us, Sparky" - DOOOOOOWN = "Doooooown" - DOWN = "Down" - DRAGON_ARCHIPELAGO_TIME_TUNNEL = "Dragon Archipelago Time Tunnel" - DUNCE_LOCKER = "Dunce Locker" - EGGPLANTS = "Eggplants" - ELSEWHERE = "Elsewhere" - EMERGENCY_MAGICATRONIC_MESSAGE = "Emergency Magicatronic Message" - ENJOY_YOUR_TRIP = "Enjoy Your Trip!" - FAT_LOT_OF_GOOD_THATLL_DO_YA = "Fat Lot of Good That'll Do Ya" - FIRE_FIRE = "Fire! Fire!" - FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE = "Flood Control Dam #3: The Not Remotely Boring Tale" - FLYING_SNAPDRAGON = "Flying Snapdragon" - FROBUARY_3_UNDERGROUNDHOG_DAY = "Frobruary 3 - Undergroundhog Day" - GETTING_SOME_CHANGE = "Getting Some Change" - GO_AWAY = "GO AWAY!" - GUE_TECH_DEANS_LIST = "GUE Tech Dean's List" - GUE_TECH_ENTRANCE_EXAM = "GUE Tech Entrance Exam" - GUE_TECH_HEALTH_MEMO = "GUE Tech Health Memo" - GUE_TECH_MAGEMEISTERS = "GUE Tech Magemeisters" - HAVE_A_HELL_OF_A_DAY = "Have a Hell of a Day!" - HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING = "Hello, This is Shona from Gurth Publishing" - HELP_ME_CANT_BREATHE = "Help... Me. Can't... Breathe" - HEY_FREE_DIRT = "Hey, Free Dirt!" - HI_MY_NAME_IS_DOUG = "Hi, My Name is Doug" - HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING = "Hmmm. Informative. Yet Deeply Disturbing" - HOLD_ON_FOR_AN_IMPORTANT_MESSAGE = "Hold on for an Important Message" - HOW_TO_HYPNOTIZE_YOURSELF = "How to Hypnotize Yourself" - HOW_TO_WIN_AT_DOUBLE_FANUCCI = "How to Win at Double Fanucci" - IMBUE_BEBURTT = "Imbue BEBURTT" - IM_COMPLETELY_NUDE = "I'm Completely Nude" - INTO_THE_FOLIAGE = "Into the Foliage" - INVISIBLE_FLOWERS = "Invisible Flowers" - IN_CASE_OF_ADVENTURE = "In Case of Adventure, Break Glass!" - IN_MAGIC_WE_TRUST = "In Magic We Trust" - ITS_ONE_OF_THOSE_ADVENTURERS_AGAIN = "It's One of Those Adventurers Again..." - I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY = "I Don't Think You Would've Wanted That to Work Anyway" - I_DONT_WANT_NO_TROUBLE = "I Don't Want No Trouble!" - I_HOPE_YOU_CAN_CLIMB_UP_THERE = "I Hope You Can Climb Up There With All This Junk" - I_LIKE_YOUR_STYLE = "I Like Your Style!" - I_SPIT_ON_YOUR_FILTHY_COINAGE = "I Spit on Your Filthy Coinage" - LIT_SUNFLOWERS = "Lit Sunflowers" - MAGIC_FOREVER = "Magic Forever!" - MAILED_IT_TO_HELL = "Mailed it to Hell" - MAKE_LOVE_NOT_WAR = "Make Love, Not War" - MEAD_LIGHT = "Mead Light?" - MIKES_PANTS = "Mike's Pants" - MUSHROOM_HAMMERED = "Mushroom, Hammered" - NATIONAL_TREASURE = "300 Year Old National Treasure" - NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR = "Natural and Supernatural Creatures of Quendor" - NOOOOOOOOOOOOO = "NOOOOOOOOOOOOO!" - NOTHIN_LIKE_A_GOOD_STOGIE = "Nothin' Like a Good Stogie" - NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT = "Now You Look Like Us, Which is an Improvement" - NO_AUTOGRAPHS = "No Autographs" - NO_BONDAGE = "No Bondage" - OBIDIL_DRIED_UP = "OBIDIL, Dried Up" - OH_DEAR_GOD_ITS_A_DRAGON = "Oh Dear God, It's a Dragon!" - OH_VERY_FUNNY_GUYS = "Oh, Very Funny Guys" - OH_WOW_TALK_ABOUT_DEJA_VU = "Oh, Wow! Talk About Deja Vu" - OLD_SCRATCH_WINNER = "Old Scratch Winner!" - ONLY_YOU_CAN_PREVENT_FOOZLE_FIRES = "Only You Can Prevent Foozle Fires" - OPEN_THE_GATES_OF_HELL = "Open the Gates of Hell" - OUTSMART_THE_QUELBEES = "Outsmart the Quelbees" - PERMASEAL = "PermaSeal" - PLANETFALL = "Planetfall" - PLEASE_DONT_THROCK_THE_GRASS = "Please Don't THROCK the Grass" - PORT_FOOZLE_TIME_TUNNEL = "Port Foozle Time Tunnel" - PROZORKED = "Prozorked" - REASSEMBLE_SNAVIG = "Reassemble SNAVIG" - RESTOCKED_ON_GRUESDAY = "Restocked on Gruesday" - RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE = "Right. Hello. Yes. Uh, This is Sneffle" - RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE = "Right. Uh, Sorry. It's Me Again. Sneffle" - SNAVIG_REPAIRED = "SNAVIG, Repaired" - SOUVENIR = "Souvenir" - STRAIGHT_TO_HELL = "Straight to Hell" - STRIP_GRUE_FIRE_WATER = "Strip Grue, Fire, Water" - SUCKING_ROCKS = "Sucking Rocks" - TALK_TO_ME_GRAND_INQUISITOR = "Talk to Me Grand Inquisitor" - TAMING_YOUR_SNAPDRAGON = "Taming Your Snapdragon" - THAR_SHE_BLOWS = "Thar She Blows!" - THATS_A_ROPE = "That's a Rope" - THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS = "That's it! Just Keep Hitting Those Buttons" - THATS_STILL_A_ROPE = "That's Still a Rope" - THATS_THE_SPIRIT = "That's the Spirit!" - THE_ALCHEMICAL_DEBACLE = "The Alchemical Debacle" - THE_ENDLESS_FIRE = "The Endless Fire" - THE_FLATHEADIAN_FUDGE_FIASCO = "The Flatheadian Fudge Fiasco" - THE_PERILS_OF_MAGIC = "The Perils of Magic" - THE_UNDERGROUND_UNDERGROUND = "The Underground Underground" - THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE = "This Doesn't Look Anything Like the Brochure" - THROCKED_MUSHROOM_HAMMERED = "THROCKed Mushroom, Hammered" - TIME_TRAVEL_FOR_DUMMIES = "Time Travel for Dummies" - TOTEMIZED_DAILY_BILLBOARD = "Totemized Daily Billboard Functioning Correctly" - UH_OH_BROG_CANT_SWIM = "Uh-Oh. Brog Can't Swim" - UMBRELLA_FLOWERS = "Umbrella Flowers" - UP = "Up" - USELESS_BUT_FUN = "Useless, But Fun" - UUUUUP = "Uuuuup" - VOYAGE_OF_CAPTAIN_ZAHAB = "Voyage of Captain Zahab" - WANT_SOME_RYE_COURSE_YA_DO = "Want Some Rye? Course Ya Do!" - WE_DONT_SERVE_YOUR_KIND_HERE = "We Don't Serve Your Kind Here" - WE_GOT_A_HIGH_ROLLER = "We Got a High Roller!" - WHAT_ARE_YOU_STUPID = "What Are You, Stupid?" - WHITE_HOUSE_TIME_TUNNEL = "White House Time Tunnel" - WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE = "Wow! I've Never Gone Inside Him Before!" - YAD_GOHDNUORGREDNU_3_YRAUBORF = "yaD gohdnuorgrednU - 3 yrauborF" - YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY = "Your Puny Weapons Don't Phase Me, Baby!" - YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER = "You Don't Go Messing With a Man's Zipper" - YOU_GAINED_86_EXPERIENCE_POINTS = "You Gained 86 Experience Points" - YOU_ONE_OF_THEM_AGITATORS_AINT_YA = "You One of Them Agitators, Ain't Ya?" - YOU_WANT_A_PIECE_OF_ME_DOCK_BOY = "You Want a Piece of Me, Dock Boy? or Girl" - - -class ZorkGrandInquisitorRegions(enum.Enum): - CROSSROADS = "Crossroads" - DM_LAIR = "Dungeon Master's Lair" - DM_LAIR_INTERIOR = "Dungeon Master's Lair - Interior" - DRAGON_ARCHIPELAGO = "Dragon Archipelago" - DRAGON_ARCHIPELAGO_DRAGON = "Dragon Archipelago - Dragon" - ENDGAME = "Endgame" - GUE_TECH = "GUE Tech" - GUE_TECH_HALLWAY = "GUE Tech - Hallway" - GUE_TECH_OUTSIDE = "GUE Tech - Outside" - HADES = "Hades" - HADES_BEYOND_GATES = "Hades - Beyond Gates" - HADES_SHORE = "Hades - Shore" - MENU = "Menu" - MONASTERY = "Monastery" - MONASTERY_EXHIBIT = "Monastery - Exhibit" - PORT_FOOZLE = "Port Foozle" - PORT_FOOZLE_JACKS_SHOP = "Port Foozle - Jack's Shop" - PORT_FOOZLE_PAST = "Port Foozle Past" - PORT_FOOZLE_PAST_TAVERN = "Port Foozle Past - Tavern" - SPELL_LAB = "Spell Lab" - SPELL_LAB_BRIDGE = "Spell Lab - Bridge" - SUBWAY_CROSSROADS = "Subway Platform - Crossroads" - SUBWAY_FLOOD_CONTROL_DAM = "Subway Platform - Flood Control Dam #3" - SUBWAY_MONASTERY = "Subway Platform - Monastery" - WALKING_CASTLE = "Walking Castle" - WHITE_HOUSE = "White House" - - -class ZorkGrandInquisitorTags(enum.Enum): - CORE = "Core" - DEATHSANITY = "Deathsanity" - FILLER = "Filler" - HOTSPOT = "Hotspot" - INVENTORY_ITEM = "Inventory Item" - MISSABLE = "Missable" - SPELL = "Spell" - SUBWAY_DESTINATION = "Subway Destination" - TELEPORTER_DESTINATION = "Teleporter Destination" - TOTEMIZER_DESTINATION = "Totemizer Destination" - TOTEM = "Totem" diff --git a/worlds/zork_grand_inquisitor/game_controller.py b/worlds/zork_grand_inquisitor/game_controller.py deleted file mode 100644 index 7a60a14608..0000000000 --- a/worlds/zork_grand_inquisitor/game_controller.py +++ /dev/null @@ -1,1388 +0,0 @@ -import collections -import functools -import logging - -from typing import Dict, Optional, Set, Tuple, Union - -from .data.item_data import item_data, ZorkGrandInquisitorItemData -from .data.location_data import location_data, ZorkGrandInquisitorLocationData - -from .data.missable_location_grant_conditions_data import ( - missable_location_grant_conditions_data, - ZorkGrandInquisitorMissableLocationGrantConditionsData, -) - -from .data_funcs import game_id_to_items, items_with_tag, locations_with_tag - -from .enums import ( - ZorkGrandInquisitorGoals, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorLocations, - ZorkGrandInquisitorTags, -) - -from .game_state_manager import GameStateManager - - -class GameController: - logger: Optional[logging.Logger] - - game_state_manager: GameStateManager - - received_items: Set[ZorkGrandInquisitorItems] - completed_locations: Set[ZorkGrandInquisitorLocations] - - completed_locations_queue: collections.deque - received_items_queue: collections.deque - - all_hotspot_items: Set[ZorkGrandInquisitorItems] - - game_id_to_items: Dict[int, ZorkGrandInquisitorItems] - - possible_inventory_items: Set[ZorkGrandInquisitorItems] - - available_inventory_slots: Set[int] - - goal_completed: bool - - option_goal: Optional[ZorkGrandInquisitorGoals] - option_deathsanity: Optional[bool] - option_grant_missable_location_checks: Optional[bool] - - def __init__(self, logger=None) -> None: - self.logger = logger - - self.game_state_manager = GameStateManager() - - self.received_items = set() - self.completed_locations = set() - - self.completed_locations_queue = collections.deque() - self.received_items_queue = collections.deque() - - self.all_hotspot_items = ( - items_with_tag(ZorkGrandInquisitorTags.HOTSPOT) - | items_with_tag(ZorkGrandInquisitorTags.SUBWAY_DESTINATION) - | items_with_tag(ZorkGrandInquisitorTags.TOTEMIZER_DESTINATION) - ) - - self.game_id_to_items = game_id_to_items() - - self.possible_inventory_items = ( - items_with_tag(ZorkGrandInquisitorTags.INVENTORY_ITEM) - | items_with_tag(ZorkGrandInquisitorTags.SPELL) - | items_with_tag(ZorkGrandInquisitorTags.TOTEM) - ) - - self.available_inventory_slots = set() - - self.goal_completed = False - - self.option_goal = None - self.option_deathsanity = None - self.option_grant_missable_location_checks = None - - @functools.cached_property - def brog_items(self) -> Set[ZorkGrandInquisitorItems]: - return { - ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH, - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG, - ZorkGrandInquisitorItems.BROGS_PLANK, - } - - @functools.cached_property - def griff_items(self) -> Set[ZorkGrandInquisitorItems]: - return { - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP, - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN, - } - - @functools.cached_property - def lucy_items(self) -> Set[ZorkGrandInquisitorItems]: - return { - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4, - } - - @property - def totem_items(self) -> Set[ZorkGrandInquisitorItems]: - return self.brog_items | self.griff_items | self.lucy_items - - @functools.cached_property - def missable_locations(self) -> Set[ZorkGrandInquisitorLocations]: - return locations_with_tag(ZorkGrandInquisitorTags.MISSABLE) - - def log(self, message) -> None: - if self.logger: - self.logger.info(message) - - def log_debug(self, message) -> None: - if self.logger: - self.logger.debug(message) - - def open_process_handle(self) -> bool: - return self.game_state_manager.open_process_handle() - - def close_process_handle(self) -> bool: - return self.game_state_manager.close_process_handle() - - def is_process_running(self) -> bool: - return self.game_state_manager.is_process_running - - def list_received_brog_items(self) -> None: - self.log("Received Brog Items:") - - self._process_received_items() - received_brog_items: Set[ZorkGrandInquisitorItems] = self.received_items & self.brog_items - - if not len(received_brog_items): - self.log(" Nothing") - return - - for item in sorted(i.value for i in received_brog_items): - self.log(f" {item}") - - def list_received_griff_items(self) -> None: - self.log("Received Griff Items:") - - self._process_received_items() - received_griff_items: Set[ZorkGrandInquisitorItems] = self.received_items & self.griff_items - - if not len(received_griff_items): - self.log(" Nothing") - return - - for item in sorted(i.value for i in received_griff_items): - self.log(f" {item}") - - def list_received_lucy_items(self) -> None: - self.log("Received Lucy Items:") - - self._process_received_items() - received_lucy_items: Set[ZorkGrandInquisitorItems] = self.received_items & self.lucy_items - - if not len(received_lucy_items): - self.log(" Nothing") - return - - for item in sorted(i.value for i in received_lucy_items): - self.log(f" {item}") - - def list_received_hotspots(self) -> None: - self.log("Received Hotspots:") - - self._process_received_items() - - hotspot_items: Set[ZorkGrandInquisitorItems] = items_with_tag(ZorkGrandInquisitorTags.HOTSPOT) - received_hotspots: Set[ZorkGrandInquisitorItems] = self.received_items & hotspot_items - - if not len(received_hotspots): - self.log(" Nothing") - return - - for item in sorted(i.value for i in received_hotspots): - self.log(f" {item}") - - def update(self) -> None: - if self.game_state_manager.is_process_still_running(): - try: - self.game_state_manager.refresh_game_location() - - self._apply_permanent_game_state() - self._apply_conditional_game_state() - - self._apply_permanent_game_flags() - - self._check_for_completed_locations() - - if self.option_grant_missable_location_checks: - self._check_for_missable_locations_to_grant() - - self._process_received_items() - - self._manage_hotspots() - self._manage_items() - - self._apply_conditional_teleports() - - self._check_for_victory() - except Exception as e: - self.log_debug(e) - - def _apply_permanent_game_state(self) -> None: - self._write_game_state_value_for(10934, 1) # Rope Taken - self._write_game_state_value_for(10418, 1) # Mead Light Taken - self._write_game_state_value_for(10275, 0) # Lantern in Crate - self._write_game_state_value_for(13929, 1) # Great Underground Door Open - self._write_game_state_value_for(13968, 1) # Subway Token Taken - self._write_game_state_value_for(12930, 1) # Hammer Taken - self._write_game_state_value_for(12935, 1) # Griff Totem Taken - self._write_game_state_value_for(12948, 1) # ZIMDOR Scroll Taken - self._write_game_state_value_for(4058, 1) # Shovel Taken - self._write_game_state_value_for(4059, 1) # THROCK Scroll Taken - self._write_game_state_value_for(11758, 1) # KENDALL Scroll Taken - self._write_game_state_value_for(16959, 1) # Old Scratch Card Taken - self._write_game_state_value_for(12840, 0) # Zork Rocks in Perma-Suck Machine - self._write_game_state_value_for(11886, 1) # Student ID Taken - self._write_game_state_value_for(16279, 1) # Prozork Tablet Taken - self._write_game_state_value_for(13260, 1) # GOLGATEM Scroll Taken - self._write_game_state_value_for(4834, 1) # Flatheadia Fudge Taken - self._write_game_state_value_for(4746, 1) # Jar of Hotbugs Taken - self._write_game_state_value_for(4755, 1) # Hungus Lard Taken - self._write_game_state_value_for(4758, 1) # Mug Taken - self._write_game_state_value_for(3716, 1) # NARWILE Scroll Taken - self._write_game_state_value_for(17147, 1) # Lucy Totem Taken - self._write_game_state_value_for(9818, 1) # Middle Telegraph Hammer Taken - self._write_game_state_value_for(3766, 0) # ANS Scroll in Window - self._write_game_state_value_for(4980, 0) # ANS Scroll in Window - self._write_game_state_value_for(3768, 0) # GIV Scroll in Window - self._write_game_state_value_for(4978, 0) # GIV Scroll in Window - self._write_game_state_value_for(3765, 0) # SNA Scroll in Window - self._write_game_state_value_for(4979, 0) # SNA Scroll in Window - self._write_game_state_value_for(3767, 0) # VIG Scroll in Window - self._write_game_state_value_for(4977, 0) # VIG Scroll in Window - self._write_game_state_value_for(15065, 1) # Brog's Bickering Torch Taken - self._write_game_state_value_for(15088, 1) # Brog's Flickering Torch Taken - self._write_game_state_value_for(2628, 4) # Brog's Grue Eggs Taken - self._write_game_state_value_for(2971, 1) # Brog's Plank Taken - self._write_game_state_value_for(1340, 1) # Griff's Inflatable Sea Captain Taken - self._write_game_state_value_for(1341, 1) # Griff's Inflatable Raft Taken - self._write_game_state_value_for(1477, 1) # Griff's Air Pump Taken - self._write_game_state_value_for(1814, 1) # Griff's Dragon Tooth Taken - self._write_game_state_value_for(15403, 0) # Lucy's Cards Taken - self._write_game_state_value_for(15404, 1) # Lucy's Cards Taken - self._write_game_state_value_for(15405, 4) # Lucy's Cards Taken - self._write_game_state_value_for(5222, 1) # User Has Spell Book - self._write_game_state_value_for(13930, 1) # Skip Well Cutscenes - self._write_game_state_value_for(19057, 1) # Skip Well Cutscenes - self._write_game_state_value_for(13934, 1) # Skip Well Cutscenes - self._write_game_state_value_for(13935, 1) # Skip Well Cutscenes - self._write_game_state_value_for(13384, 1) # Skip Meanwhile... Cutscene - self._write_game_state_value_for(8620, 1) # First Coin Paid to Charon - self._write_game_state_value_for(8731, 1) # First Coin Paid to Charon - - def _apply_conditional_game_state(self): - # Can teleport to Dungeon Master's Lair - if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR): - self._write_game_state_value_for(2203, 1) - else: - self._write_game_state_value_for(2203, 0) - - # Can teleport to GUE Tech - if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH): - self._write_game_state_value_for(7132, 1) - else: - self._write_game_state_value_for(7132, 0) - - # Can Teleport to Spell Lab - if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB): - self._write_game_state_value_for(16545, 1) - else: - self._write_game_state_value_for(16545, 0) - - # Can Teleport to Hades - if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES): - self._write_game_state_value_for(7119, 1) - else: - self._write_game_state_value_for(7119, 0) - - # Can Teleport to Monastery Station - if self._player_has(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY): - self._write_game_state_value_for(7148, 1) - else: - self._write_game_state_value_for(7148, 0) - - # Initial Totemizer Destination - should_force_initial_totemizer_destination: bool = True - - if self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION): - should_force_initial_totemizer_destination = False - elif self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL): - should_force_initial_totemizer_destination = False - elif self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY): - should_force_initial_totemizer_destination = False - elif self._player_has(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ): - should_force_initial_totemizer_destination = False - - if should_force_initial_totemizer_destination: - self._write_game_state_value_for(9617, 2) - - # Pouch of Zorkmids - if self._player_has(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS): - self._write_game_state_value_for(5827, 1) - else: - self._write_game_state_value_for(5827, 0) - - # Brog Torches - if self._player_is_brog() and self._player_has(ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH): - self._write_game_state_value_for(10999, 1) - else: - self._write_game_state_value_for(10999, 0) - - if self._player_is_brog() and self._player_has(ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH): - self._write_game_state_value_for(10998, 1) - else: - self._write_game_state_value_for(10998, 0) - - # Monastery Rope - if ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE in self.completed_locations: - self._write_game_state_value_for(9637, 1) - - def _apply_permanent_game_flags(self) -> None: - self._write_game_flags_value_for(9437, 2) # Monastery Exhibit Door to Outside - self._write_game_flags_value_for(3074, 2) # White House Door - self._write_game_flags_value_for(13005, 2) # Map - self._write_game_flags_value_for(13006, 2) # Sword - self._write_game_flags_value_for(13007, 2) # Sword - self._write_game_flags_value_for(13389, 2) # Moss of Mareilon - self._write_game_flags_value_for(4301, 2) # Quelbee Honeycomb - self._write_game_flags_value_for(12895, 2) # Change Machine Money - self._write_game_flags_value_for(4150, 2) # Prozorked Snapdragon - self._write_game_flags_value_for(13413, 2) # Letter Opener - self._write_game_flags_value_for(15403, 2) # Lucy's Cards - - def _check_for_completed_locations(self) -> None: - location: ZorkGrandInquisitorLocations - data: ZorkGrandInquisitorLocationData - for location, data in location_data.items(): - if location in self.completed_locations or not isinstance( - location, ZorkGrandInquisitorLocations - ): - continue - - is_location_completed: bool = True - - trigger: [Union[str, int]] - value: Union[str, int, Tuple[int, ...]] - for trigger, value in data.game_state_trigger: - if trigger == "location": - if not self._player_is_at(value): - is_location_completed = False - break - elif isinstance(trigger, int): - if isinstance(value, int): - if self._read_game_state_value_for(trigger) != value: - is_location_completed = False - break - elif isinstance(value, tuple): - if self._read_game_state_value_for(trigger) not in value: - is_location_completed = False - break - else: - is_location_completed = False - break - else: - is_location_completed = False - break - - if is_location_completed: - self.completed_locations.add(location) - self.completed_locations_queue.append(location) - - def _check_for_missable_locations_to_grant(self) -> None: - missable_location: ZorkGrandInquisitorLocations - for missable_location in self.missable_locations: - if missable_location in self.completed_locations: - continue - - data: ZorkGrandInquisitorLocationData = location_data[missable_location] - - if ZorkGrandInquisitorTags.DEATHSANITY in data.tags and not self.option_deathsanity: - continue - - condition_data: ZorkGrandInquisitorMissableLocationGrantConditionsData = ( - missable_location_grant_conditions_data.get(missable_location) - ) - - if condition_data is None: - self.log_debug(f"Missable Location {missable_location.value} has no grant conditions") - continue - - if condition_data.location_condition in self.completed_locations: - grant_location: bool = True - - item: ZorkGrandInquisitorItems - for item in condition_data.item_conditions or tuple(): - if self._player_doesnt_have(item): - grant_location = False - break - - if grant_location: - self.completed_locations_queue.append(missable_location) - - def _process_received_items(self) -> None: - while len(self.received_items_queue) > 0: - item: ZorkGrandInquisitorItems = self.received_items_queue.popleft() - data: ZorkGrandInquisitorItemData = item_data[item] - - if ZorkGrandInquisitorTags.FILLER in data.tags: - continue - - self.received_items.add(item) - - def _manage_hotspots(self) -> None: - hotspot_item: ZorkGrandInquisitorItems - for hotspot_item in self.all_hotspot_items: - data: ZorkGrandInquisitorItemData = item_data[hotspot_item] - - if hotspot_item not in self.received_items: - key: int - for key in data.statemap_keys: - self._write_game_flags_value_for(key, 2) - else: - if hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX: - if self.game_state_manager.game_location == "hp5g": - if self._read_game_state_value_for(9113) == 0: - self._write_game_flags_value_for(9116, 0) - else: - self._write_game_flags_value_for(9116, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS: - if self.game_state_manager.game_location == "qb2g": - if self._read_game_state_value_for(15433) == 0: - self._write_game_flags_value_for(15434, 0) - else: - self._write_game_flags_value_for(15434, 2) - - if self._read_game_state_value_for(15435) == 0: - self._write_game_flags_value_for(15436, 0) - else: - self._write_game_flags_value_for(15436, 2) - - if self._read_game_state_value_for(15437) == 0: - self._write_game_flags_value_for(15438, 0) - else: - self._write_game_flags_value_for(15438, 2) - - if self._read_game_state_value_for(15439) == 0: - self._write_game_flags_value_for(15440, 0) - else: - self._write_game_flags_value_for(15440, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX: - if self.game_state_manager.game_location == "tp2g": - if self._read_game_state_value_for(12095) == 1: - self._write_game_flags_value_for(9115, 2) - else: - self._write_game_flags_value_for(9115, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_BLINDS: - if self.game_state_manager.game_location == "dv1e": - if self._read_game_state_value_for(4743) == 0: - self._write_game_flags_value_for(4799, 0) - else: - self._write_game_flags_value_for(4799, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS: - if self.game_state_manager.game_location == "tr5g": - key: int - for key in data.statemap_keys: - self._write_game_flags_value_for(key, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT: - if self.game_state_manager.game_location == "tr5g": - self._write_game_flags_value_for(12702, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT: - if self.game_state_manager.game_location == "tr5m": - self._write_game_flags_value_for(12909, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT: - if self.game_state_manager.game_location == "tr5j": - if self._read_game_state_value_for(12892) == 0: - self._write_game_flags_value_for(12900, 0) - else: - self._write_game_flags_value_for(12900, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR: - if self.game_state_manager.game_location == "dw1e": - if self._read_game_state_value_for(4983) == 0: - self._write_game_flags_value_for(5010, 0) - else: - self._write_game_flags_value_for(5010, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT: - if self.game_state_manager.game_location == "me2j": - if self._read_game_state_value_for(9491) == 2: - self._write_game_flags_value_for(9539, 0) - else: - self._write_game_flags_value_for(9539, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER: - if self.game_state_manager.game_location == "me2j": - if self._read_game_state_value_for(9546) == 2 or self._read_game_state_value_for(9419) == 1: - self._write_game_flags_value_for(19712, 2) - else: - self._write_game_flags_value_for(19712, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT: - if self.game_state_manager.game_location == "sg1f": - self._write_game_flags_value_for(2586, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER: - if self.game_state_manager.game_location == "th3j": - five_is_open: bool = self._read_game_state_value_for(11847) == 1 - six_is_open: bool = self._read_game_state_value_for(11840) == 1 - seven_is_open: bool = self._read_game_state_value_for(11841) == 1 - eight_is_open: bool = self._read_game_state_value_for(11848) == 1 - - rocks_in_six: bool = self._read_game_state_value_for(11769) == 1 - six_blasted: bool = self._read_game_state_value_for(11770) == 1 - - if five_is_open or six_is_open or seven_is_open or eight_is_open or rocks_in_six or six_blasted: - self._write_game_flags_value_for(11878, 2) - else: - self._write_game_flags_value_for(11878, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND: - if self.game_state_manager.game_location == "te5e": - if self._read_game_state_value_for(11747) == 0: - self._write_game_flags_value_for(11751, 0) - else: - self._write_game_flags_value_for(11751, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH: - if self.game_state_manager.game_location == "pe2e": - self._write_game_flags_value_for(15147, 0) - self._write_game_flags_value_for(15153, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW: - if self.game_state_manager.game_location == "cd70": - self._write_game_flags_value_for(1705, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS: - if self.game_state_manager.game_location == "cd3h": - raft_in_left: bool = self._read_game_state_value_for(1301) == 1 - raft_in_right: bool = self._read_game_state_value_for(1304) == 1 - raft_inflated: bool = self._read_game_state_value_for(1379) == 1 - - captain_in_left: bool = self._read_game_state_value_for(1374) == 1 - captain_in_right: bool = self._read_game_state_value_for(1381) == 1 - captain_inflated: bool = self._read_game_state_value_for(1378) == 1 - - left_inflated: bool = (raft_in_left and raft_inflated) or (captain_in_left and captain_inflated) - - right_inflated: bool = (raft_in_right and raft_inflated) or ( - captain_in_right and captain_inflated - ) - - if left_inflated: - self._write_game_flags_value_for(1425, 2) - else: - self._write_game_flags_value_for(1425, 0) - - if right_inflated: - self._write_game_flags_value_for(1426, 2) - else: - self._write_game_flags_value_for(1426, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE: - if self.game_state_manager.game_location == "uc3e": - if self._read_game_state_value_for(13060) == 0: - self._write_game_flags_value_for(13106, 0) - else: - self._write_game_flags_value_for(13106, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS: - if self.game_state_manager.game_location == "ue1e": - if self._read_game_state_value_for(14318) == 0: - self._write_game_flags_value_for(13219, 0) - self._write_game_flags_value_for(13220, 0) - self._write_game_flags_value_for(13221, 0) - self._write_game_flags_value_for(13222, 0) - else: - self._write_game_flags_value_for(13219, 2) - self._write_game_flags_value_for(13220, 2) - self._write_game_flags_value_for(13221, 2) - self._write_game_flags_value_for(13222, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS: - if self.game_state_manager.game_location == "ue1e": - if self._read_game_state_value_for(14318) == 0: - self._write_game_flags_value_for(14327, 0) - self._write_game_flags_value_for(14332, 0) - self._write_game_flags_value_for(14337, 0) - self._write_game_flags_value_for(14342, 0) - else: - self._write_game_flags_value_for(14327, 2) - self._write_game_flags_value_for(14332, 2) - self._write_game_flags_value_for(14337, 2) - self._write_game_flags_value_for(14342, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT: - if self.game_state_manager.game_location == "tr5e": - self._write_game_flags_value_for(12528, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS: - if self.game_state_manager.game_location == "tr5e": - if self._read_game_state_value_for(12220) == 0: - self._write_game_flags_value_for(12523, 2) - self._write_game_flags_value_for(12524, 2) - self._write_game_flags_value_for(12525, 2) - else: - self._write_game_flags_value_for(12523, 0) - self._write_game_flags_value_for(12524, 0) - self._write_game_flags_value_for(12525, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE: - if self.game_state_manager.game_location == "uc1g": - if self._read_game_state_value_for(12931) == 1 or self._read_game_state_value_for(12929) == 1: - self._write_game_flags_value_for(13002, 2) - else: - self._write_game_flags_value_for(13002, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL: - if self.game_state_manager.game_location == "pe5e": - if self._read_game_state_value_for(10277) == 0: - self._write_game_flags_value_for(10726, 0) - else: - self._write_game_flags_value_for(10726, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR: - if self.game_state_manager.game_location == "tr1k": - if self._read_game_state_value_for(12212) == 0: - self._write_game_flags_value_for(12280, 0) - else: - self._write_game_flags_value_for(12280, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS: - if self.game_state_manager.game_location in ("te10", "te1g", "te20", "te30", "te40"): - key: int - for key in data.statemap_keys: - self._write_game_flags_value_for(key, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS: - if self.game_state_manager.game_location == "hp1e": - if self._read_game_state_value_for(8431) == 1: - key: int - for key in data.statemap_keys: - self._write_game_flags_value_for(key, 0) - else: - key: int - for key in data.statemap_keys: - self._write_game_flags_value_for(key, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER: - if self.game_state_manager.game_location == "hp1e": - if self._read_game_state_value_for(8431) == 1: - self._write_game_flags_value_for(8446, 2) - else: - self._write_game_flags_value_for(8446, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HARRY: - if self.game_state_manager.game_location == "dg4e": - if self._read_game_state_value_for(4237) == 1 and self._read_game_state_value_for(4034) == 1: - self._write_game_flags_value_for(4260, 2) - else: - self._write_game_flags_value_for(4260, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY: - if self.game_state_manager.game_location == "dg4h": - if self._read_game_state_value_for(4279) == 1: - self._write_game_flags_value_for(18026, 2) - else: - self._write_game_flags_value_for(18026, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH: - if self.game_state_manager.game_location == "dg4g": - if self._read_game_state_value_for(4034) == 1: - self._write_game_flags_value_for(17623, 2) - else: - self._write_game_flags_value_for(17623, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR: - if self.game_state_manager.game_location == "uc4e": - if self._read_game_state_value_for(13062) == 1: - self._write_game_flags_value_for(13140, 2) - else: - self._write_game_flags_value_for(13140, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR: - if self.game_state_manager.game_location == "pe1e": - if self._read_game_state_value_for(10451) == 1: - self._write_game_flags_value_for(10441, 2) - else: - self._write_game_flags_value_for(10441, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS: - if self.game_state_manager.game_location == "pe2j": - self._write_game_flags_value_for(19632, 0) - self._write_game_flags_value_for(19627, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR: - if self.game_state_manager.game_location == "sw4e": - if self._read_game_state_value_for(2989) == 1: - self._write_game_flags_value_for(3025, 2) - else: - self._write_game_flags_value_for(3025, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG: - if self.game_state_manager.game_location == "sw4e": - self._write_game_flags_value_for(3036, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MIRROR: - if self.game_state_manager.game_location == "dw1f": - self._write_game_flags_value_for(5031, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT: - if self.game_state_manager.game_location == "um1e": - if self._read_game_state_value_for(9637) == 0: - self._write_game_flags_value_for(13597, 0) - else: - self._write_game_flags_value_for(13597, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE: - if self.game_state_manager.game_location == "ue2g": - if self._read_game_state_value_for(13278) == 0: - self._write_game_flags_value_for(13390, 0) - else: - self._write_game_flags_value_for(13390, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR: - if self.game_state_manager.game_location == "qe1e": - if self._player_is_brog(): - self._write_game_flags_value_for(2447, 0) - elif self._player_is_griff(): - self._write_game_flags_value_for(2455, 0) - elif self._player_is_lucy(): - if self._read_game_state_value_for(2457) == 0: - self._write_game_flags_value_for(2455, 0) - else: - self._write_game_flags_value_for(2455, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS: - if self.game_state_manager.game_location == "tr3h": - if self._read_game_state_value_for(11777) == 1: - self._write_game_flags_value_for(12389, 2) - else: - self._write_game_flags_value_for(12389, 0) - - self._write_game_state_value_for(12390, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE: - if self.game_state_manager.game_location == "dg4f": - if self._read_game_state_value_for(4241) == 1: - self._write_game_flags_value_for(4302, 2) - else: - self._write_game_flags_value_for(4302, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE: - if self.game_state_manager.game_location == "tp1e": - if self._read_game_state_value_for(16342) == 1: - self._write_game_flags_value_for(16383, 2) - self._write_game_flags_value_for(16384, 2) - else: - self._write_game_flags_value_for(16383, 0) - self._write_game_flags_value_for(16384, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE: - if self.game_state_manager.game_location == "sg6e": - if self._read_game_state_value_for(15715) == 1: - self._write_game_flags_value_for(2769, 2) - else: - self._write_game_flags_value_for(2769, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON: - if self.game_state_manager.game_location == "dg2f": - if self._read_game_state_value_for(4114) == 1 or self._read_game_state_value_for(4115) == 1: - self._write_game_flags_value_for(4149, 2) - else: - self._write_game_flags_value_for(4149, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS: - if self.game_state_manager.game_location == "tr5f": - self._write_game_flags_value_for(12584, 0) - self._write_game_flags_value_for(12585, 0) - self._write_game_flags_value_for(12586, 0) - self._write_game_flags_value_for(12587, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT: - if self.game_state_manager.game_location == "tr5f": - self._write_game_flags_value_for(12574, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT: - if self.game_state_manager.game_location == "ue2j": - if self._read_game_state_value_for(13408) == 1: - self._write_game_flags_value_for(13412, 2) - else: - self._write_game_flags_value_for(13412, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER: - if self.game_state_manager.game_location == "tp4g": - self._write_game_flags_value_for(12170, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM: - if self.game_state_manager.game_location == "tp1e": - if self._read_game_state_value_for(16342) == 1 and self._read_game_state_value_for(16374) == 0: - self._write_game_flags_value_for(16382, 0) - else: - self._write_game_flags_value_for(16382, 2) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM: - if self.game_state_manager.game_location == "dg3e": - self._write_game_flags_value_for(4209, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE: - if self.game_state_manager.game_location == "th3r": - self._write_game_flags_value_for(11973, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT: - if self.game_state_manager.game_location == "uc6e": - self._write_game_flags_value_for(13168, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY: - if self.game_state_manager.game_location == "qb2e": - if self._read_game_state_value_for(15395) == 1: - self._write_game_flags_value_for(15396, 2) - else: - self._write_game_flags_value_for(15396, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH: - if self.game_state_manager.game_location == "mt2e": - self._write_game_flags_value_for(9706, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS: - if self.game_state_manager.game_location == "mt2g": - self._write_game_flags_value_for(9728, 0) - self._write_game_flags_value_for(9729, 0) - self._write_game_flags_value_for(9730, 0) - elif hotspot_item == ZorkGrandInquisitorItems.HOTSPOT_WELL: - if self.game_state_manager.game_location == "pc1e": - self._write_game_flags_value_for(10314, 0) - elif hotspot_item == ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM: - if self.game_state_manager.game_location == "us2e": - self._write_game_flags_value_for(13757, 0) - elif self.game_state_manager.game_location == "ue2e": - self._write_game_flags_value_for(13297, 0) - elif self.game_state_manager.game_location == "uh2e": - self._write_game_flags_value_for(13486, 0) - elif self.game_state_manager.game_location == "um2e": - self._write_game_flags_value_for(13625, 0) - elif hotspot_item == ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES: - if self.game_state_manager.game_location == "us2e": - self._write_game_flags_value_for(13758, 0) - elif self.game_state_manager.game_location == "ue2e": - self._write_game_flags_value_for(13309, 0) - elif self.game_state_manager.game_location == "uh2e": - self._write_game_flags_value_for(13498, 0) - elif self.game_state_manager.game_location == "um2e": - self._write_game_flags_value_for(13637, 0) - elif hotspot_item == ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY: - if self.game_state_manager.game_location == "us2e": - self._write_game_flags_value_for(13759, 0) - elif self.game_state_manager.game_location == "ue2e": - self._write_game_flags_value_for(13316, 0) - elif self.game_state_manager.game_location == "uh2e": - self._write_game_flags_value_for(13505, 0) - elif self.game_state_manager.game_location == "um2e": - self._write_game_flags_value_for(13644, 0) - elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION: - if self.game_state_manager.game_location == "mt1f": - self._write_game_flags_value_for(9660, 0) - elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_INFINITY: - if self.game_state_manager.game_location == "mt1f": - self._write_game_flags_value_for(9666, 0) - elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL: - if self.game_state_manager.game_location == "mt1f": - self._write_game_flags_value_for(9668, 0) - elif hotspot_item == ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_SURFACE_OF_MERZ: - if self.game_state_manager.game_location == "mt1f": - self._write_game_flags_value_for(9662, 0) - - def _manage_items(self) -> None: - if self._player_is_afgncaap(): - self.available_inventory_slots = self._determine_available_inventory_slots() - - received_inventory_items: Set[ZorkGrandInquisitorItems] - received_inventory_items = self.received_items & self.possible_inventory_items - - received_inventory_items = self._filter_received_inventory_items(received_inventory_items) - elif self._player_is_totem(): - self.available_inventory_slots = self._determine_available_inventory_slots(is_totem=True) - - received_inventory_items: Set[ZorkGrandInquisitorItems] - - if self._player_is_brog(): - received_inventory_items = self.received_items & self.brog_items - received_inventory_items = self._filter_received_brog_inventory_items(received_inventory_items) - elif self._player_is_griff(): - received_inventory_items = self.received_items & self.griff_items - received_inventory_items = self._filter_received_griff_inventory_items(received_inventory_items) - elif self._player_is_lucy(): - received_inventory_items = self.received_items & self.lucy_items - received_inventory_items = self._filter_received_lucy_inventory_items(received_inventory_items) - else: - return None - else: - return None - - game_state_inventory_items: Set[ZorkGrandInquisitorItems] = self._determine_game_state_inventory() - - inventory_items_to_remove: Set[ZorkGrandInquisitorItems] - inventory_items_to_remove = game_state_inventory_items - received_inventory_items - - inventory_items_to_add: Set[ZorkGrandInquisitorItems] - inventory_items_to_add = received_inventory_items - game_state_inventory_items - - item: ZorkGrandInquisitorItems - for item in inventory_items_to_remove: - self._remove_from_inventory(item) - - item: ZorkGrandInquisitorItems - for item in inventory_items_to_add: - self._add_to_inventory(item) - - # Item Deduplication (Just in Case) - seen_items: Set[int] = set() - - i: int - for i in range(151, 171): - item: int = self._read_game_state_value_for(i) - - if item in seen_items: - self._write_game_state_value_for(i, 0) - else: - seen_items.add(item) - - def _apply_conditional_teleports(self) -> None: - if self._player_is_at("uw1x"): - self.game_state_manager.set_game_location("uw10", 0) - - if self._player_is_at("uw1k") and self._read_game_state_value_for(13938) == 0: - self.game_state_manager.set_game_location("pc10", 250) - - if self._player_is_at("ue1q"): - self.game_state_manager.set_game_location("ue1e", 0) - - if self._player_is_at("ej10"): - self.game_state_manager.set_game_location("uc10", 1200) - - if self._read_game_state_value_for(9) == 224: - self._write_game_state_value_for(9, 0) - self.game_state_manager.set_game_location("uc10", 1200) - - def _check_for_victory(self) -> None: - if self.option_goal == ZorkGrandInquisitorGoals.THREE_ARTIFACTS: - coconut_is_placed = self._read_game_state_value_for(2200) == 1 - cube_is_placed = self._read_game_state_value_for(2322) == 1 - skull_is_placed = self._read_game_state_value_for(2321) == 1 - - self.goal_completed = coconut_is_placed and cube_is_placed and skull_is_placed - - def _determine_game_state_inventory(self) -> Set[ZorkGrandInquisitorItems]: - game_state_inventory: Set[ZorkGrandInquisitorItems] = set() - - # Item on Cursor - item_on_cursor: int = self._read_game_state_value_for(9) - - if item_on_cursor != 0: - if item_on_cursor in self.game_id_to_items: - game_state_inventory.add(self.game_id_to_items[item_on_cursor]) - - # Item in Inspector - item_in_inspector: int = self._read_game_state_value_for(4512) - - if item_in_inspector != 0: - if item_in_inspector in self.game_id_to_items: - game_state_inventory.add(self.game_id_to_items[item_in_inspector]) - - # Items in Inventory Slots - i: int - for i in range(151, 171): - if self._read_game_state_value_for(i) != 0: - if self._read_game_state_value_for(i) in self.game_id_to_items: - game_state_inventory.add( - self.game_id_to_items[self._read_game_state_value_for(i)] - ) - - # Pouch of Zorkmids - if self._read_game_state_value_for(5827) == 1: - game_state_inventory.add(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS) - - # Spells - i: int - for i in range(191, 203): - if self._read_game_state_value_for(i) == 1: - if i in self.game_id_to_items: - game_state_inventory.add(self.game_id_to_items[i]) - - # Totems - if self._read_game_state_value_for(4853) == 1: - game_state_inventory.add(ZorkGrandInquisitorItems.TOTEM_BROG) - - if self._read_game_state_value_for(4315) == 1: - game_state_inventory.add(ZorkGrandInquisitorItems.TOTEM_GRIFF) - - if self._read_game_state_value_for(5223) == 1: - game_state_inventory.add(ZorkGrandInquisitorItems.TOTEM_LUCY) - - return game_state_inventory - - def _add_to_inventory(self, item: ZorkGrandInquisitorItems) -> None: - if item == ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: - return None - - data: ZorkGrandInquisitorItemData = item_data[item] - - if ZorkGrandInquisitorTags.INVENTORY_ITEM in data.tags: - if len(self.available_inventory_slots): # Inventory slot overflow protection - inventory_slot: int = self.available_inventory_slots.pop() - self._write_game_state_value_for(inventory_slot, data.statemap_keys[0]) - elif ZorkGrandInquisitorTags.SPELL in data.tags: - self._write_game_state_value_for(data.statemap_keys[0], 1) - elif ZorkGrandInquisitorTags.TOTEM in data.tags: - self._write_game_state_value_for(data.statemap_keys[0], 1) - - def _remove_from_inventory(self, item: ZorkGrandInquisitorItems) -> None: - if item == ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS: - return None - - data: ZorkGrandInquisitorItemData = item_data[item] - - if ZorkGrandInquisitorTags.INVENTORY_ITEM in data.tags: - inventory_slot: Optional[int] = self._inventory_slot_for(item) - - if inventory_slot is None: - return None - - self._write_game_state_value_for(inventory_slot, 0) - - if inventory_slot != 9: - self.available_inventory_slots.add(inventory_slot) - elif ZorkGrandInquisitorTags.SPELL in data.tags: - self._write_game_state_value_for(data.statemap_keys[0], 0) - elif ZorkGrandInquisitorTags.TOTEM in data.tags: - self._write_game_state_value_for(data.statemap_keys[0], 0) - - def _determine_available_inventory_slots(self, is_totem: bool = False) -> Set[int]: - available_inventory_slots: Set[int] = set() - - inventory_slot_range_end: int = 171 - - if is_totem: - if self._player_is_brog(): - inventory_slot_range_end = 161 - elif self._player_is_griff(): - inventory_slot_range_end = 160 - elif self._player_is_lucy(): - inventory_slot_range_end = 157 - - i: int - for i in range(151, inventory_slot_range_end): - if self._read_game_state_value_for(i) == 0: - available_inventory_slots.add(i) - - return available_inventory_slots - - def _inventory_slot_for(self, item) -> Optional[int]: - data: ZorkGrandInquisitorItemData = item_data[item] - - if ZorkGrandInquisitorTags.INVENTORY_ITEM in data.tags: - i: int - for i in range(151, 171): - if self._read_game_state_value_for(i) == data.statemap_keys[0]: - return i - - if self._read_game_state_value_for(9) == data.statemap_keys[0]: - return 9 - - if self._read_game_state_value_for(4512) == data.statemap_keys[0]: - return 4512 - - return None - - def _filter_received_inventory_items( - self, received_inventory_items: Set[ZorkGrandInquisitorItems] - ) -> Set[ZorkGrandInquisitorItems]: - to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = self.totem_items - - inventory_item_values: Set[int] = set() - - i: int - for i in range(151, 171): - inventory_item_values.add(self._read_game_state_value_for(i)) - - cursor_item_value: int = self._read_game_state_value_for(9) - inspector_item_value: int = self._read_game_state_value_for(4512) - - inventory_item_values.add(cursor_item_value) - inventory_item_values.add(inspector_item_value) - - item: ZorkGrandInquisitorItems - for item in received_inventory_items: - if item == ZorkGrandInquisitorItems.FLATHEADIA_FUDGE: - if self._read_game_state_value_for(4766) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(4869) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.HUNGUS_LARD: - if self._read_game_state_value_for(4870) == 1: - to_filter_inventory_items.add(item) - elif ( - self._read_game_state_value_for(4244) == 1 - and self._read_game_state_value_for(4309) == 0 - ): - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.JAR_OF_HOTBUGS: - if self._read_game_state_value_for(4750) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(4869) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.LANTERN: - if self._read_game_state_value_for(10477) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(5221) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER: - if self._read_game_state_value_for(9491) == 3: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.MAP: - if self._read_game_state_value_for(16618) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.MEAD_LIGHT: - if 105 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(17620) > 0: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(4034) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.MOSS_OF_MAREILON: - if self._read_game_state_value_for(4763) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(4869) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.MUG: - if self._read_game_state_value_for(4772) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(4869) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.OLD_SCRATCH_CARD: - if 32 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(12892) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE: - if self._read_game_state_value_for(12218) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER: - if self._read_game_state_value_for(15150) == 3: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(10421) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.PROZORK_TABLET: - if self._read_game_state_value_for(4115) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB: - if self._read_game_state_value_for(4769) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(4869) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.ROPE: - if 22 in inventory_item_values: - to_filter_inventory_items.add(item) - elif 111 in inventory_item_values: - to_filter_inventory_items.add(item) - elif ( - self._read_game_state_value_for(10304) == 1 - and not self._read_game_state_value_for(13938) == 1 - ): - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15150) == 83: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS: - if 41 in inventory_item_values: - to_filter_inventory_items.add(item) - elif 98 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(201) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV: - if 48 in inventory_item_values: - to_filter_inventory_items.add(item) - elif 98 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(201) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.SNAPDRAGON: - if self._read_game_state_value_for(4199) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.STUDENT_ID: - if self._read_game_state_value_for(11838) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.SUBWAY_TOKEN: - if self._read_game_state_value_for(13167) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.SWORD: - if 22 in inventory_item_values: - to_filter_inventory_items.add(item) - elif 100 in inventory_item_values: - to_filter_inventory_items.add(item) - elif 111 in inventory_item_values: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.ZIMDOR_SCROLL: - if 105 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(17620) == 3: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(4034) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.ZORK_ROCKS: - if self._read_game_state_value_for(12486) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(12487) == 1: - to_filter_inventory_items.add(item) - elif 52 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(11769) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(11840) == 1: - to_filter_inventory_items.add(item) - - return received_inventory_items - to_filter_inventory_items - - def _filter_received_brog_inventory_items( - self, received_inventory_items: Set[ZorkGrandInquisitorItems] - ) -> Set[ZorkGrandInquisitorItems]: - to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = set() - - inventory_item_values: Set[int] = set() - - i: int - for i in range(151, 161): - inventory_item_values.add(self._read_game_state_value_for(i)) - - cursor_item_value: int = self._read_game_state_value_for(9) - inspector_item_value: int = self._read_game_state_value_for(2194) - - inventory_item_values.add(cursor_item_value) - inventory_item_values.add(inspector_item_value) - - item: ZorkGrandInquisitorItems - for item in received_inventory_items: - if item == ZorkGrandInquisitorItems.BROGS_BICKERING_TORCH: - if 103 in inventory_item_values: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH: - if 104 in inventory_item_values: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.BROGS_GRUE_EGG: - if self._read_game_state_value_for(2577) == 1: - to_filter_inventory_items.add(item) - elif 71 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(2641) == 1: - to_filter_inventory_items.add(item) - - return received_inventory_items - to_filter_inventory_items - - def _filter_received_griff_inventory_items( - self, received_inventory_items: Set[ZorkGrandInquisitorItems] - ) -> Set[ZorkGrandInquisitorItems]: - to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = set() - - inventory_item_values: Set[int] = set() - - i: int - for i in range(151, 160): - inventory_item_values.add(self._read_game_state_value_for(i)) - - cursor_item_value: int = self._read_game_state_value_for(9) - inspector_item_value: int = self._read_game_state_value_for(4512) - - inventory_item_values.add(cursor_item_value) - inventory_item_values.add(inspector_item_value) - - item: ZorkGrandInquisitorItems - for item in received_inventory_items: - if item == ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT: - if self._read_game_state_value_for(1301) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(1304) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(16562) == 1: - to_filter_inventory_items.add(item) - if item == ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN: - if self._read_game_state_value_for(1374) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(1381) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(16562) == 1: - to_filter_inventory_items.add(item) - - return received_inventory_items - to_filter_inventory_items - - def _filter_received_lucy_inventory_items( - self, received_inventory_items: Set[ZorkGrandInquisitorItems] - ) -> Set[ZorkGrandInquisitorItems]: - to_filter_inventory_items: Set[ZorkGrandInquisitorItems] = set() - - inventory_item_values: Set[int] = set() - - i: int - for i in range(151, 157): - inventory_item_values.add(self._read_game_state_value_for(i)) - - cursor_item_value: int = self._read_game_state_value_for(9) - inspector_item_value: int = self._read_game_state_value_for(2198) - - inventory_item_values.add(cursor_item_value) - inventory_item_values.add(inspector_item_value) - - item: ZorkGrandInquisitorItems - for item in received_inventory_items: - if item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1: - if 120 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15433) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15435) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15437) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15439) == 1: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15472) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2: - if 121 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15433) == 2: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15435) == 2: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15437) == 2: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15439) == 2: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15472) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3: - if 122 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15433) == 3: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15435) == 3: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15437) == 3: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15439) == 3: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15472) == 1: - to_filter_inventory_items.add(item) - elif item == ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4: - if 123 in inventory_item_values: - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15433) in (4, 5): - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15435) in (4, 5): - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15437) in (4, 5): - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15439) in (4, 5): - to_filter_inventory_items.add(item) - elif self._read_game_state_value_for(15472) == 1: - to_filter_inventory_items.add(item) - - return received_inventory_items - to_filter_inventory_items - - def _read_game_state_value_for(self, key: int) -> Optional[int]: - try: - return self.game_state_manager.read_game_state_value_for(key) - except Exception as e: - self.log_debug(f"Exception: {e} while trying to read game state key '{key}'") - raise e - - def _write_game_state_value_for(self, key: int, value: int) -> Optional[bool]: - try: - return self.game_state_manager.write_game_state_value_for(key, value) - except Exception as e: - self.log_debug(f"Exception: {e} while trying to write '{key} = {value}' to game state") - raise e - - def _read_game_flags_value_for(self, key: int) -> Optional[int]: - try: - return self.game_state_manager.read_game_flags_value_for(key) - except Exception as e: - self.log_debug(f"Exception: {e} while trying to read game flags key '{key}'") - raise e - - def _write_game_flags_value_for(self, key: int, value: int) -> Optional[bool]: - try: - return self.game_state_manager.write_game_flags_value_for(key, value) - except Exception as e: - self.log_debug(f"Exception: {e} while trying to write '{key} = {value}' to game flags") - raise e - - def _player_has(self, item: ZorkGrandInquisitorItems) -> bool: - return item in self.received_items - - def _player_doesnt_have(self, item: ZorkGrandInquisitorItems) -> bool: - return item not in self.received_items - - def _player_is_at(self, game_location: str) -> bool: - return self.game_state_manager.game_location == game_location - - def _player_is_afgncaap(self) -> bool: - return self._read_game_state_value_for(1596) == 1 - - def _player_is_totem(self) -> bool: - return self._player_is_brog() or self._player_is_griff() or self._player_is_lucy() - - def _player_is_brog(self) -> bool: - return self._read_game_state_value_for(1520) == 1 - - def _player_is_griff(self) -> bool: - return self._read_game_state_value_for(1296) == 1 - - def _player_is_lucy(self) -> bool: - return self._read_game_state_value_for(1524) == 1 diff --git a/worlds/zork_grand_inquisitor/game_state_manager.py b/worlds/zork_grand_inquisitor/game_state_manager.py deleted file mode 100644 index 25b35969bf..0000000000 --- a/worlds/zork_grand_inquisitor/game_state_manager.py +++ /dev/null @@ -1,370 +0,0 @@ -from typing import Optional, Tuple - -from pymem import Pymem -from pymem.process import close_handle - - -class GameStateManager: - process_name = "scummvm.exe" - - process: Optional[Pymem] - is_process_running: bool - - script_manager_struct_address: int - render_manager_struct_address: int - - game_location: Optional[str] - game_location_offset: Optional[int] - - def __init__(self) -> None: - self.process = None - self.is_process_running = False - - self.script_manager_struct_address = 0x0 - self.render_manager_struct_address = 0x0 - - self.game_location = None - self.game_location_offset = None - - @property - def game_state_storage_pointer_address(self) -> int: - return self.script_manager_struct_address + 0x88 - - @property - def game_state_storage_address(self) -> int: - return self.process.read_longlong(self.game_state_storage_pointer_address) - - @property - def game_state_hashmap_size_address(self) -> int: - return self.script_manager_struct_address + 0x90 - - @property - def game_state_key_count_address(self) -> int: - return self.script_manager_struct_address + 0x94 - - @property - def game_state_deleted_key_count_address(self) -> int: - return self.script_manager_struct_address + 0x98 - - @property - def game_flags_storage_pointer_address(self) -> int: - return self.script_manager_struct_address + 0x120 - - @property - def game_flags_storage_address(self) -> int: - return self.process.read_longlong(self.game_flags_storage_pointer_address) - - @property - def game_flags_hashmap_size_address(self) -> int: - return self.script_manager_struct_address + 0x128 - - @property - def game_flags_key_count_address(self) -> int: - return self.script_manager_struct_address + 0x12C - - @property - def game_flags_deleted_key_count_address(self) -> int: - return self.script_manager_struct_address + 0x130 - - @property - def current_location_address(self) -> int: - return self.script_manager_struct_address + 0x400 - - @property - def current_location_offset_address(self) -> int: - return self.script_manager_struct_address + 0x404 - - @property - def next_location_address(self) -> int: - return self.script_manager_struct_address + 0x408 - - @property - def next_location_offset_address(self) -> int: - return self.script_manager_struct_address + 0x40C - - @property - def panorama_reversed_address(self) -> int: - return self.render_manager_struct_address + 0x1C - - def open_process_handle(self) -> bool: - try: - self.process = Pymem(self.process_name) - self.is_process_running = True - - self.script_manager_struct_address = self._resolve_address(0x5276600, (0xC8, 0x0)) - self.render_manager_struct_address = self._resolve_address(0x5276600, (0xD0, 0x120)) - except Exception: - return False - - return True - - def close_process_handle(self) -> bool: - if close_handle(self.process.process_handle): - self.is_process_running = False - self.process = None - - self.script_manager_struct_address = 0x0 - self.render_manager_struct_address = 0x0 - - return True - - return False - - def is_process_still_running(self) -> bool: - try: - self.process.read_int(self.process.base_address) - except Exception: - self.is_process_running = False - self.process = None - - self.script_manager_struct_address = 0x0 - self.render_manager_struct_address = 0x0 - - return False - - return True - - def read_game_state_value_for(self, key: int) -> Optional[int]: - return self.read_statemap_value_for(key, scope="game_state") - - def read_game_flags_value_for(self, key: int) -> Optional[int]: - return self.read_statemap_value_for(key, scope="game_flags") - - def read_statemap_value_for(self, key: int, scope: str = "game_state") -> Optional[int]: - if self.is_process_running: - offset: int - - address: int - address_value: int - - if scope == "game_state": - offset = self._get_game_state_address_read_offset_for(key) - - address = self.game_state_storage_address + offset - address_value = self.process.read_longlong(address) - elif scope == "game_flags": - offset = self._get_game_flags_address_read_offset_for(key) - - address = self.game_flags_storage_address + offset - address_value = self.process.read_longlong(address) - else: - raise ValueError(f"Invalid scope: {scope}") - - if address_value == 0: - return 0 - - statemap_value: int = self.process.read_int(address_value + 0x0) - statemap_key: int = self.process.read_int(address_value + 0x4) - - assert statemap_key == key - - return statemap_value - - return None - - def write_game_state_value_for(self, key: int, value: int) -> Optional[bool]: - return self.write_statemap_value_for(key, value, scope="game_state") - - def write_game_flags_value_for(self, key: int, value: int) -> Optional[bool]: - return self.write_statemap_value_for(key, value, scope="game_flags") - - def write_statemap_value_for(self, key: int, value: int, scope: str = "game_state") -> Optional[bool]: - if self.is_process_running: - offset: int - is_existing_node: bool - is_reused_dummy_node: bool - - key_count_address: int - deleted_key_count_address: int - - storage_address: int - - if scope == "game_state": - offset, is_existing_node, is_reused_dummy_node = self._get_game_state_address_write_offset_for(key) - - key_count_address = self.game_state_key_count_address - deleted_key_count_address = self.game_state_deleted_key_count_address - - storage_address = self.game_state_storage_address - elif scope == "game_flags": - offset, is_existing_node, is_reused_dummy_node = self._get_game_flags_address_write_offset_for(key) - - key_count_address = self.game_flags_key_count_address - deleted_key_count_address = self.game_flags_deleted_key_count_address - - storage_address = self.game_flags_storage_address - else: - raise ValueError(f"Invalid scope: {scope}") - - statemap_key_count: int = self.process.read_int(key_count_address) - statemap_deleted_key_count: int = self.process.read_int(deleted_key_count_address) - - if value == 0: - if not is_existing_node: - return False - - self.process.write_longlong(storage_address + offset, 1) - - self.process.write_int(key_count_address, statemap_key_count - 1) - self.process.write_int(deleted_key_count_address, statemap_deleted_key_count + 1) - else: - if is_existing_node: - address_value: int = self.process.read_longlong(storage_address + offset) - self.process.write_int(address_value + 0x0, value) - else: - write_address: int = self.process.allocate(0x8) - - self.process.write_int(write_address + 0x0, value) - self.process.write_int(write_address + 0x4, key) - - self.process.write_longlong(storage_address + offset, write_address) - - self.process.write_int(key_count_address, statemap_key_count + 1) - - if is_reused_dummy_node: - self.process.write_int(deleted_key_count_address, statemap_deleted_key_count - 1) - - return True - - return None - - def refresh_game_location(self) -> Optional[bool]: - if self.is_process_running: - game_location_bytes: bytes = self.process.read_bytes(self.current_location_address, 4) - - self.game_location = game_location_bytes.decode("ascii") - self.game_location_offset = self.process.read_int(self.current_location_offset_address) - - return True - - return None - - def set_game_location(self, game_location: str, offset: int) -> Optional[bool]: - if self.is_process_running: - game_location_bytes: bytes = game_location.encode("ascii") - - self.process.write_bytes(self.next_location_address, game_location_bytes, 4) - self.process.write_int(self.next_location_offset_address, offset) - - return True - - return None - - def set_panorama_reversed(self, is_reversed: bool) -> Optional[bool]: - if self.is_process_running: - self.process.write_int(self.panorama_reversed_address, 1 if is_reversed else 0) - - return True - - return None - - def _resolve_address(self, base_offset: int, offsets: Tuple[int, ...]): - address: int = self.process.read_longlong(self.process.base_address + base_offset) - - for offset in offsets[:-1]: - address = self.process.read_longlong(address + offset) - - return address + offsets[-1] - - def _get_game_state_address_read_offset_for(self, key: int): - return self._get_statemap_address_read_offset_for(key, scope="game_state") - - def _get_game_flags_address_read_offset_for(self, key: int): - return self._get_statemap_address_read_offset_for(key, scope="game_flags") - - def _get_statemap_address_read_offset_for(self, key: int, scope: str = "game_state") -> int: - hashmap_size_address: int - storage_address: int - - if scope == "game_state": - hashmap_size_address = self.game_state_hashmap_size_address - storage_address = self.game_state_storage_address - elif scope == "game_flags": - hashmap_size_address = self.game_flags_hashmap_size_address - storage_address = self.game_flags_storage_address - else: - raise ValueError(f"Invalid scope: {scope}") - - statemap_hashmap_size: int = self.process.read_int(hashmap_size_address) - - perturb: int = key - perturb_shift: int = 0x5 - - index: int = key & statemap_hashmap_size - offset: int = index * 0x8 - - while True: - offset_value: int = self.process.read_longlong(storage_address + offset) - - if offset_value == 0: # Null Pointer - break - elif offset_value == 1: # Dummy Node - pass - elif offset_value > 1: # Existing Node - if self.process.read_int(offset_value + 0x4) == key: - break - - index = ((0x5 * index) + perturb + 0x1) & statemap_hashmap_size - offset = index * 0x8 - - perturb >>= perturb_shift - - return offset - - def _get_game_state_address_write_offset_for(self, key: int) -> Tuple[int, bool, bool]: - return self._get_statemap_address_write_offset_for(key, scope="game_state") - - def _get_game_flags_address_write_offset_for(self, key: int) -> Tuple[int, bool, bool]: - return self._get_statemap_address_write_offset_for(key, scope="game_flags") - - def _get_statemap_address_write_offset_for(self, key: int, scope: str = "game_state") -> Tuple[int, bool, bool]: - hashmap_size_address: int - storage_address: int - - if scope == "game_state": - hashmap_size_address = self.game_state_hashmap_size_address - storage_address = self.game_state_storage_address - elif scope == "game_flags": - hashmap_size_address = self.game_flags_hashmap_size_address - storage_address = self.game_flags_storage_address - else: - raise ValueError(f"Invalid scope: {scope}") - - statemap_hashmap_size: int = self.process.read_int(hashmap_size_address) - - perturb: int = key - perturb_shift: int = 0x5 - - index: int = key & statemap_hashmap_size - offset: int = index * 0x8 - - node_found: bool = False - - dummy_node_found: bool = False - dummy_node_offset: Optional[int] = None - - while True: - offset_value: int = self.process.read_longlong(storage_address + offset) - - if offset_value == 0: # Null Pointer - break - elif offset_value == 1: # Dummy Node - if not dummy_node_found: - dummy_node_offset = offset - dummy_node_found = True - elif offset_value > 1: # Existing Node - if self.process.read_int(offset_value + 0x4) == key: - node_found = True - break - - index = ((0x5 * index) + perturb + 0x1) & statemap_hashmap_size - offset = index * 0x8 - - perturb >>= perturb_shift - - if not node_found and dummy_node_found: # We should reuse the dummy node - return dummy_node_offset, False, True - elif not node_found and not dummy_node_found: # We should allocate a new node - return offset, False, False - - return offset, True, False # We should update the existing node diff --git a/worlds/zork_grand_inquisitor/options.py b/worlds/zork_grand_inquisitor/options.py deleted file mode 100644 index f064151999..0000000000 --- a/worlds/zork_grand_inquisitor/options.py +++ /dev/null @@ -1,61 +0,0 @@ -from dataclasses import dataclass - -from Options import Choice, DefaultOnToggle, PerGameCommonOptions, Toggle - - -class Goal(Choice): - """ - Determines the victory condition - - Three Artifacts: Retrieve the three artifacts of magic and place them in the walking castle - """ - display_name: str = "Goal" - - default: int = 0 - option_three_artifacts: int = 0 - - -class QuickPortFoozle(DefaultOnToggle): - """If true, the items needed to go down the well will be found in early locations for a smoother early game""" - - display_name: str = "Quick Port Foozle" - - -class StartWithHotspotItems(DefaultOnToggle): - """ - If true, the player will be given all the hotspot items at the start of the game, effectively removing the need - to enable the important hotspots in the game before interacting with them. Recommended for beginners - - Note: The spots these hotspot items would have occupied in the item pool will instead be filled with junk items. - Expect a higher volume of filler items if you enable this option - """ - - display_name: str = "Start with Hotspot Items" - - -class Deathsanity(Toggle): - """If true, adds 16 player death locations to the world""" - - display_name: str = "Deathsanity" - - -class GrantMissableLocationChecks(Toggle): - """ - If true, performing an irreversible action will grant the locations checks that would have become unobtainable as a - result of that action when you meet the item requirements - - Otherwise, the player is expected to potentially have to use the save system to reach those location checks. If you - don't like the idea of rarely having to reload an earlier save to get a location check, make sure this option is - enabled - """ - - display_name: str = "Grant Missable Checks" - - -@dataclass -class ZorkGrandInquisitorOptions(PerGameCommonOptions): - goal: Goal - quick_port_foozle: QuickPortFoozle - start_with_hotspot_items: StartWithHotspotItems - deathsanity: Deathsanity - grant_missable_location_checks: GrantMissableLocationChecks diff --git a/worlds/zork_grand_inquisitor/requirements.txt b/worlds/zork_grand_inquisitor/requirements.txt deleted file mode 100644 index ca36764fbf..0000000000 --- a/worlds/zork_grand_inquisitor/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Pymem>=1.13.0 diff --git a/worlds/zork_grand_inquisitor/test/__init__.py b/worlds/zork_grand_inquisitor/test/__init__.py deleted file mode 100644 index c8ceda43a7..0000000000 --- a/worlds/zork_grand_inquisitor/test/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from test.bases import WorldTestBase - - -class ZorkGrandInquisitorTestBase(WorldTestBase): - game = "Zork Grand Inquisitor" diff --git a/worlds/zork_grand_inquisitor/test/test_access.py b/worlds/zork_grand_inquisitor/test/test_access.py deleted file mode 100644 index 63a5f8c9ab..0000000000 --- a/worlds/zork_grand_inquisitor/test/test_access.py +++ /dev/null @@ -1,2927 +0,0 @@ -from typing import List - -from . import ZorkGrandInquisitorTestBase - -from ..enums import ( - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorLocations, - ZorkGrandInquisitorRegions, -) - - -class AccessTestRegions(ZorkGrandInquisitorTestBase): - options = { - "start_with_hotspot_items": "false", - } - - def test_access_crossroads_to_dm_lair_sword(self) -> None: - self._go_to_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SWORD.value, - ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - def test_access_crossroads_to_dm_lair_teleporter(self) -> None: - self._go_to_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - def test_access_crossroads_to_gue_tech(self) -> None: - self._go_to_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_REZROV.value, - ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) - - def test_access_crossroads_to_gue_tech_outside(self) -> None: - self._go_to_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) - - def test_access_crossroads_to_hades_shore(self) -> None: - self._go_to_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - def test_access_crossroads_to_port_foozle(self) -> None: - self._go_to_crossroads() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE.value)) - - def test_access_crossroads_to_spell_lab_bridge(self) -> None: - self._go_to_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - def test_access_crossroads_to_subway_crossroads(self) -> None: - self._go_to_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SUBWAY_TOKEN.value, - ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) - - def test_access_crossroads_to_subway_monastery(self) -> None: - self._go_to_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - def test_access_dm_lair_to_crossroads(self) -> None: - self._go_to_dm_lair() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) - - def test_access_dm_lair_to_dm_lair_interior(self) -> None: - self._go_to_dm_lair() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY.value, - ZorkGrandInquisitorItems.MEAD_LIGHT.value, - ZorkGrandInquisitorItems.ZIMDOR_SCROLL.value, - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) - - def test_access_dm_lair_to_gue_tech_outside(self) -> None: - self._go_to_dm_lair() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) - - def test_access_dm_lair_to_hades_shore(self) -> None: - self._go_to_dm_lair() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - def test_access_dm_lair_to_spell_lab_bridge(self) -> None: - self._go_to_dm_lair() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - def test_access_dm_lair_to_subway_monastery(self) -> None: - self._go_to_dm_lair() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - def test_access_dm_lair_interior_to_dm_lair(self) -> None: - self._go_to_dm_lair_interior() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - def test_access_dm_lair_interior_to_walking_castle(self) -> None: - self._go_to_dm_lair_interior() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.WALKING_CASTLE.value)) - - self._obtain_obidil() - - self.collect_by_name(ZorkGrandInquisitorItems.HOTSPOT_BLINDS.value) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.WALKING_CASTLE.value)) - - def test_access_dm_lair_interior_to_white_house(self) -> None: - self._go_to_dm_lair_interior() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.WHITE_HOUSE.value)) - - self._obtain_yastard() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR.value, - ZorkGrandInquisitorItems.SPELL_NARWILE.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.WHITE_HOUSE.value)) - - def test_access_dragon_archipelago_to_dragon_archipelago_dragon(self) -> None: - self._go_to_dragon_archipelago() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEM_GRIFF.value, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON.value)) - - def test_access_dragon_archipelago_to_hades_beyond_gates(self) -> None: - self._go_to_dragon_archipelago() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES.value)) - - def test_access_dragon_archipelago_dragon_to_dragon_archipelago(self) -> None: - self._go_to_dragon_archipelago_dragon() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO.value)) - - def test_access_dragon_archipelago_dragon_to_endgame(self) -> None: - self._go_to_dragon_archipelago_dragon() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value, - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value, - ) - ) - - self._go_to_port_foozle_past_tavern() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value, - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value, - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value, - ) - ) - - self._go_to_white_house() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEM_BROG.value, - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value, - ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value, - ZorkGrandInquisitorItems.BROGS_PLANK.value, - ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) - - def test_access_gue_tech_to_crossroads(self) -> None: - self._go_to_gue_tech() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) - - def test_access_gue_tech_to_gue_tech_hallway(self) -> None: - self._go_to_gue_tech() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_IGRAM.value, - ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY.value)) - - def test_access_gue_tech_to_gue_tech_outside(self) -> None: - self._go_to_gue_tech() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) - - self.collect_by_name(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR.value) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) - - def test_access_gue_tech_hallway_to_gue_tech(self) -> None: - self._go_to_gue_tech_hallway() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) - - def test_access_gue_tech_hallway_to_spell_lab_bridge(self) -> None: - self._go_to_gue_tech_hallway() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.STUDENT_ID.value, - ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - def test_access_gue_tech_outside_to_crossroads(self) -> None: - self._go_to_gue_tech_outside() - - # Direct connection requires the map but indirect connection is free - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) - - def test_access_gue_tech_outside_to_dm_lair(self) -> None: - self._go_to_gue_tech_outside() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - def test_access_gue_tech_outside_to_gue_tech(self) -> None: - self._go_to_gue_tech_outside() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH.value)) - - def test_access_gue_tech_outside_to_hades_shore(self) -> None: - self._go_to_gue_tech_outside() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - def test_access_gue_tech_outside_to_spell_lab_bridge(self) -> None: - self._go_to_gue_tech_outside() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - def test_access_gue_tech_outside_to_subway_monastery(self) -> None: - self._go_to_gue_tech_outside() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - def test_access_hades_to_hades_beyond_gates(self) -> None: - self._go_to_hades() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES.value)) - - self._obtain_snavig() - - self.collect_by_name(ZorkGrandInquisitorItems.TOTEM_BROG.value) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_BEYOND_GATES.value)) - - def test_access_hades_to_hades_shore(self) -> None: - self._go_to_hades() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - def test_access_hades_beyond_gates_to_dragon_archipelago(self) -> None: - self._go_to_hades_beyond_gates() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO.value)) - - self._obtain_yastard() - - self.collect_by_name(ZorkGrandInquisitorItems.SPELL_NARWILE.value) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO.value)) - - def test_access_hades_beyond_gates_to_hades(self) -> None: - self._go_to_hades_beyond_gates() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES.value)) - - def test_access_hades_shore_to_crossroads(self) -> None: - self._go_to_hades_shore() - - # Direct connection requires the map but indirect connection is free - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) - - def test_access_hades_shore_to_dm_lair(self) -> None: - self._go_to_hades_shore() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - def test_access_hades_shore_to_gue_tech_outside(self) -> None: - self._go_to_hades_shore() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) - - def test_access_hades_shore_to_hades(self) -> None: - self._go_to_hades_shore() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER.value, - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS.value, - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES.value)) - - def test_access_hades_shore_to_spell_lab_bridge(self) -> None: - self._go_to_hades_shore() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - def test_access_hades_shore_to_subway_crossroads(self) -> None: - self._go_to_hades_shore() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) - - def test_access_hades_shore_to_subway_flood_control_dam(self) -> None: - self._go_to_hades_shore() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value)) - - self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value)) - - def test_access_hades_shore_to_subway_monastery(self) -> None: - self._go_to_hades_shore() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - def test_access_monastery_to_hades_shore(self) -> None: - self._go_to_monastery() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL.value, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - def test_access_monastery_to_monastery_exhibit(self) -> None: - self._go_to_monastery() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION.value, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT.value)) - - def test_access_monastery_to_subway_monastery(self) -> None: - self._go_to_monastery() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - def test_access_monastery_exhibit_to_monastery(self) -> None: - self._go_to_monastery_exhibit() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY.value)) - - def test_access_monastery_exhibit_to_port_foozle_past(self) -> None: - self._go_to_monastery_exhibit() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST.value)) - - self._obtain_yastard() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER.value, - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT.value, - ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER.value, - ZorkGrandInquisitorItems.SPELL_NARWILE.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST.value)) - - def test_access_port_foozle_to_crossroads(self) -> None: - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, - ZorkGrandInquisitorItems.LANTERN.value, - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, - ZorkGrandInquisitorItems.ROPE.value, - ZorkGrandInquisitorItems.HOTSPOT_WELL.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) - - def test_access_port_foozle_to_port_foozle_jacks_shop(self) -> None: - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, - ZorkGrandInquisitorItems.LANTERN.value, - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_JACKS_SHOP.value)) - - def test_access_port_foozle_jacks_shop_to_port_foozle(self) -> None: - self._go_to_port_foozle_jacks_shop() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE.value)) - - def test_access_port_foozle_past_to_monastery_exhibit(self) -> None: - self._go_to_port_foozle_past() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY_EXHIBIT.value)) - - def test_access_port_foozle_past_to_port_foozle_past_tavern(self) -> None: - self._go_to_port_foozle_past() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEM_LUCY.value, - ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST_TAVERN.value)) - - def test_access_port_foozle_past_tavern_to_endgame(self) -> None: - self._go_to_port_foozle_past_tavern() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value, - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value, - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value, - ) - ) - - self._go_to_dragon_archipelago_dragon() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value, - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value, - ) - ) - - self._go_to_white_house() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEM_BROG.value, - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value, - ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value, - ZorkGrandInquisitorItems.BROGS_PLANK.value, - ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) - - def test_access_port_foozle_past_tavern_to_port_foozle_past(self) -> None: - self._go_to_port_foozle_past_tavern() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.PORT_FOOZLE_PAST.value)) - - def test_access_spell_lab_to_spell_lab_bridge(self) -> None: - self._go_to_spell_lab() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB_BRIDGE.value)) - - def test_access_spell_lab_bridge_to_crossroads(self) -> None: - self._go_to_spell_lab_bridge() - - # Direct connection requires the map but indirect connection is free - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) - - def test_access_spell_lab_bridge_to_dm_lair(self) -> None: - self._go_to_spell_lab_bridge() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR.value)) - - def test_access_spell_lab_bridge_to_gue_tech_outside(self) -> None: - self._go_to_spell_lab_bridge() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE.value)) - - def test_access_spell_lab_bridge_to_gue_tech_hallway(self) -> None: - self._go_to_spell_lab_bridge() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.GUE_TECH_HALLWAY.value)) - - def test_access_spell_lab_bridge_to_hades_shore(self) -> None: - self._go_to_spell_lab_bridge() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - def test_access_spell_lab_bridge_to_spell_lab(self) -> None: - self._go_to_spell_lab_bridge() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB.value)) - - self._go_to_subway_flood_control_dam() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_REZROV.value, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, - ZorkGrandInquisitorItems.SWORD.value, - ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, - ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SPELL_LAB.value)) - - def test_access_spell_lab_bridge_to_subway_monastery(self) -> None: - self._go_to_spell_lab_bridge() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - def test_access_subway_crossroads_to_crossroads(self) -> None: - self._go_to_subway_crossroads() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.CROSSROADS.value)) - - def test_access_subway_crossroads_to_hades_shore(self) -> None: - self._go_to_subway_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_KENDALL.value, - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - def test_access_subway_crossroads_to_subway_flood_control_dam(self) -> None: - self._go_to_subway_crossroads() - - self.assertFalse( - self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) - ) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_KENDALL.value, - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value, - ) - ) - - self.assertTrue( - self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) - ) - - def test_access_subway_crossroads_to_subway_monastery(self) -> None: - self._go_to_subway_crossroads() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_KENDALL.value, - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - def test_access_subway_flood_control_dam_to_hades_shore(self) -> None: - self._go_to_subway_flood_control_dam() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - def test_access_subway_flood_control_dam_to_subway_crossroads(self) -> None: - self._go_to_subway_flood_control_dam() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) - - def test_access_subway_flood_control_dam_to_subway_monastery(self) -> None: - self._go_to_subway_flood_control_dam() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_MONASTERY.value)) - - def test_access_subway_monastery_to_hades_shore(self) -> None: - self._go_to_subway_monastery() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.HADES_SHORE.value)) - - def test_access_subway_monastery_to_monastery(self) -> None: - self._go_to_subway_monastery() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SWORD.value, - ZorkGrandInquisitorItems.SPELL_GLORF.value, - ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.MONASTERY.value)) - - def test_access_subway_monastery_to_subway_crossroads(self) -> None: - self._go_to_subway_monastery() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_CROSSROADS.value)) - - def test_access_subway_monastery_to_subway_flood_control_dam(self) -> None: - self._go_to_subway_monastery() - - self.assertFalse( - self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) - ) - - self.collect_by_name(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value) - - self.assertTrue( - self.can_reach_region(ZorkGrandInquisitorRegions.SUBWAY_FLOOD_CONTROL_DAM.value) - ) - - def test_access_walking_castle_to_dm_lair_interior(self) -> None: - self._go_to_walking_castle() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) - - def test_access_white_house_to_dm_lair_interior(self) -> None: - self._go_to_white_house() - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR.value)) - - def test_access_white_house_to_endgame(self) -> None: - self._go_to_white_house() - - self.assertFalse(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEM_BROG.value, - ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value, - ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value, - ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value, - ZorkGrandInquisitorItems.BROGS_PLANK.value, - ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value, - ) - ) - - self._go_to_dragon_archipelago_dragon() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value, - ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value, - ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value, - ) - ) - - self._go_to_port_foozle_past_tavern() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value, - ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value, - ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value, - ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value, - ) - ) - - self.assertTrue(self.can_reach_region(ZorkGrandInquisitorRegions.ENDGAME.value)) - - def _go_to_crossroads(self) -> None: - self.collect_by_name( - ( - ZorkGrandInquisitorItems.LANTERN.value, - ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, - ZorkGrandInquisitorItems.ROPE.value, - ZorkGrandInquisitorItems.HOTSPOT_WELL.value, - ) - ) - - def _go_to_dm_lair(self) -> None: - self._go_to_crossroads() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SWORD.value, - ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE.value, - ) - ) - - def _go_to_dm_lair_interior(self) -> None: - self._go_to_dm_lair() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY.value, - ZorkGrandInquisitorItems.MEAD_LIGHT.value, - ZorkGrandInquisitorItems.ZIMDOR_SCROLL.value, - ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH.value, - ) - ) - - def _go_to_dragon_archipelago(self) -> None: - self._go_to_hades_beyond_gates() - self._obtain_yastard() - - self.collect_by_name(ZorkGrandInquisitorItems.SPELL_NARWILE.value) - - def _go_to_dragon_archipelago_dragon(self) -> None: - self._go_to_dragon_archipelago() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEM_GRIFF.value, - ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW.value, - ) - ) - - def _go_to_gue_tech(self) -> None: - self._go_to_crossroads() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_REZROV.value, - ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR.value, - ) - ) - - def _go_to_gue_tech_hallway(self) -> None: - self._go_to_gue_tech() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_IGRAM.value, - ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS.value, - ) - ) - - def _go_to_gue_tech_outside(self) -> None: - self._go_to_crossroads() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value, - ) - ) - - def _go_to_hades(self) -> None: - self._go_to_hades_shore() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER.value, - ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS.value, - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value, - ) - ) - - def _go_to_hades_beyond_gates(self) -> None: - self._go_to_hades() - self._obtain_snavig() - - self.collect_by_name(ZorkGrandInquisitorItems.TOTEM_BROG.value) - - def _go_to_hades_shore(self) -> None: - self._go_to_crossroads() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value, - ) - ) - - def _go_to_monastery(self) -> None: - self._go_to_subway_monastery() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SWORD.value, - ZorkGrandInquisitorItems.SPELL_GLORF.value, - ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT.value, - ) - ) - - def _go_to_monastery_exhibit(self) -> None: - self._go_to_monastery() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION.value, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value, - ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value, - ) - ) - - def _go_to_port_foozle_jacks_shop(self) -> None: - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value, - ZorkGrandInquisitorItems.LANTERN.value, - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value, - ) - ) - - def _go_to_port_foozle_past(self) -> None: - self._go_to_monastery_exhibit() - - self._obtain_yastard() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER.value, - ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT.value, - ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER.value, - ZorkGrandInquisitorItems.SPELL_NARWILE.value, - ) - ) - - def _go_to_port_foozle_past_tavern(self) -> None: - self._go_to_port_foozle_past() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.TOTEM_LUCY.value, - ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR.value, - ) - ) - - def _go_to_spell_lab(self) -> None: - self._go_to_subway_flood_control_dam() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_REZROV.value, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, - ) - ) - - self._go_to_spell_lab_bridge() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SWORD.value, - ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, - ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, - ) - ) - - def _go_to_spell_lab_bridge(self) -> None: - self._go_to_crossroads() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value, - ) - ) - - def _go_to_subway_crossroads(self) -> None: - self._go_to_crossroads() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SUBWAY_TOKEN.value, - ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT.value, - ) - ) - - def _go_to_subway_flood_control_dam(self) -> None: - self._go_to_subway_crossroads() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_KENDALL.value, - ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value, - ) - ) - - def _go_to_subway_monastery(self) -> None: - self._go_to_crossroads() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.MAP.value, - ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value, - ) - ) - - def _go_to_white_house(self) -> None: - self._go_to_dm_lair_interior() - - self._obtain_yastard() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR.value, - ZorkGrandInquisitorItems.SPELL_NARWILE.value, - ) - ) - - def _go_to_walking_castle(self) -> None: - self._go_to_dm_lair_interior() - - self._obtain_obidil() - self.collect_by_name(ZorkGrandInquisitorItems.HOTSPOT_BLINDS.value) - - def _obtain_obidil(self) -> None: - self._go_to_crossroads() - self._go_to_gue_tech() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value, - ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT.value, - ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS.value, - ) - ) - - self._go_to_subway_flood_control_dam() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_REZROV.value, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, - ) - ) - - self._go_to_spell_lab_bridge() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SWORD.value, - ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, - ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER.value, - ) - ) - - def _obtain_snavig(self) -> None: - self._go_to_crossroads() - self._go_to_dm_lair_interior() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS.value, - ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV.value, - ZorkGrandInquisitorItems.HOTSPOT_MIRROR.value, - ) - ) - - self._go_to_subway_flood_control_dam() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SPELL_REZROV.value, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value, - ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value, - ) - ) - - self._go_to_spell_lab_bridge() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.SWORD.value, - ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value, - ZorkGrandInquisitorItems.SPELL_GOLGATEM.value, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value, - ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER.value, - ) - ) - - def _obtain_yastard(self) -> None: - self._go_to_crossroads() - self._go_to_dm_lair_interior() - - self.collect_by_name( - ( - ZorkGrandInquisitorItems.FLATHEADIA_FUDGE.value, - ZorkGrandInquisitorItems.HUNGUS_LARD.value, - ZorkGrandInquisitorItems.JAR_OF_HOTBUGS.value, - ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB.value, - ZorkGrandInquisitorItems.MOSS_OF_MAREILON.value, - ZorkGrandInquisitorItems.MUG.value, - ) - ) - - -class AccessTestLocations(ZorkGrandInquisitorTestBase): - options = { - "deathsanity": "true", - "start_with_hotspot_items": "false", - } - - def test_access_locations_requiring_brogs_flickering_torch(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, - ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, - ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.BROGS_FLICKERING_TORCH.value,)] - ) - - def test_access_locations_requiring_brogs_grue_egg(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, - ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.BROGS_GRUE_EGG.value,)] - ) - - def test_access_locations_requiring_brogs_plank(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.BROGS_PLANK.value,)] - ) - - def test_access_locations_requiring_flatheadia_fudge(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, - ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.FLATHEADIA_FUDGE.value,)] - ) - - def test_access_locations_requiring_griffs_air_pump(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.GRIFFS_AIR_PUMP.value,)] - ) - - def test_access_locations_requiring_griffs_dragon_tooth(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.GRIFFS_DRAGON_TOOTH.value,)] - ) - - def test_access_locations_requiring_griffs_inflatable_raft(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_RAFT.value,)] - ) - - def test_access_locations_requiring_griffs_inflatable_sea_captain(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.GRIFFS_INFLATABLE_SEA_CAPTAIN.value,)] - ) - - def test_access_locations_requiring_hammer(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, - ZorkGrandInquisitorLocations.BONK.value, - ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, - ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, - ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, - ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HAMMER.value,)] - ) - - def test_access_locations_requiring_hungus_lard(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, - ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, - ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HUNGUS_LARD.value,)] - ) - - def test_access_locations_requiring_jar_of_hotbugs(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, - ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.JAR_OF_HOTBUGS.value,)] - ) - - def test_access_locations_requiring_lantern(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.LANTERN.value,)] - ) - - def test_access_locations_requiring_large_telegraph_hammer(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.LARGE_TELEGRAPH_HAMMER.value,)] - ) - - def test_access_locations_requiring_lucys_playing_cards(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_1.value,)] - ) - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_2.value,)] - ) - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_3.value,)] - ) - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.LUCYS_PLAYING_CARD_4.value,)] - ) - - def test_access_locations_requiring_map(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.MAP.value,)] - ) - - def test_access_locations_requiring_mead_light(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.MEAD_LIGHT.value, - ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, - ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.MEAD_LIGHT.value,)] - ) - - def test_access_locations_requiring_moss_of_mareilon(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, - ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.MOSS_OF_MAREILON.value,)] - ) - - def test_access_locations_requiring_mug(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, - ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.MUG.value,)] - ) - - def test_access_locations_requiring_old_scratch_card(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_LOST_SOUL_TO_OLD_SCRATCH.value, - ZorkGrandInquisitorLocations.OLD_SCRATCH_WINNER.value, - ZorkGrandInquisitorEvents.ZORKMID_BILL_ACCESSIBLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.OLD_SCRATCH_CARD.value,)] - ) - - def test_access_locations_requiring_perma_suck_machine(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.PERMA_SUCK_MACHINE.value,)] - ) - - def test_access_locations_requiring_plastic_six_pack_holder(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE.value, - ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.PLASTIC_SIX_PACK_HOLDER.value,)] - ) - - def test_access_locations_requiring_pouch_of_zorkmids(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, - ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, - ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, - ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, - ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE.value, - ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, - ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, - ZorkGrandInquisitorLocations.SOUVENIR.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, - ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, - ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, - ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.POUCH_OF_ZORKMIDS.value,)] - ) - - def test_access_locations_requiring_prozork_tablet(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.PROZORKED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.PROZORK_TABLET.value,)] - ) - - def test_access_locations_requiring_quelbee_honeycomb(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, - ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.QUELBEE_HONEYCOMB.value,)] - ) - - def test_access_locations_requiring_rope(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN.value, - ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED.value, - ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, - ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, - ZorkGrandInquisitorLocations.A_SMALLWAY.value, - ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, - ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED.value, - ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES.value, - ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, - ZorkGrandInquisitorLocations.BONK.value, - ZorkGrandInquisitorLocations.BRAVE_SOULS_WANTED.value, - ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, - ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, - ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorLocations.CASTLE_WATCHING_A_FIELD_GUIDE.value, - ZorkGrandInquisitorLocations.CAVES_NOTES.value, - ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, - ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, - ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.DEATH_CLIMBED_OUT_OF_THE_WELL.value, - ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE.value, - ZorkGrandInquisitorLocations.DEATH_JUMPED_IN_BOTTOMLESS_PIT.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD.value, - ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, - ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, - ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED.value, - ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, - ZorkGrandInquisitorLocations.DESPERATELY_SEEKING_TUTOR.value, - ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, - ZorkGrandInquisitorLocations.DOOOOOOWN.value, - ZorkGrandInquisitorLocations.DOWN.value, - ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, - ZorkGrandInquisitorLocations.EGGPLANTS.value, - ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE.value, - ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP.value, - ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA.value, - ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE.value, - ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, - ZorkGrandInquisitorLocations.FROBUARY_3_UNDERGROUNDHOG_DAY.value, - ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE.value, - ZorkGrandInquisitorLocations.GUE_TECH_DEANS_LIST.value, - ZorkGrandInquisitorLocations.GUE_TECH_ENTRANCE_EXAM.value, - ZorkGrandInquisitorLocations.GUE_TECH_HEALTH_MEMO.value, - ZorkGrandInquisitorLocations.GUE_TECH_MAGEMEISTERS.value, - ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, - ZorkGrandInquisitorLocations.HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING.value, - ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, - ZorkGrandInquisitorLocations.HI_MY_NAME_IS_DOUG.value, - ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, - ZorkGrandInquisitorLocations.HOLD_ON_FOR_AN_IMPORTANT_MESSAGE.value, - ZorkGrandInquisitorLocations.HOW_TO_HYPNOTIZE_YOURSELF.value, - ZorkGrandInquisitorLocations.HOW_TO_WIN_AT_DOUBLE_FANUCCI.value, - ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, - ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE.value, - ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, - ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, - ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, - ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, - ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS.value, - ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, - ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, - ZorkGrandInquisitorLocations.LIT_SUNFLOWERS.value, - ZorkGrandInquisitorLocations.MAGIC_FOREVER.value, - ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, - ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR.value, - ZorkGrandInquisitorLocations.MIKES_PANTS.value, - ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, - ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, - ZorkGrandInquisitorLocations.NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR.value, - ZorkGrandInquisitorLocations.NO_BONDAGE.value, - ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, - ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE.value, - ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, - ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, - ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, - ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.PERMASEAL.value, - ZorkGrandInquisitorLocations.PLEASE_DONT_THROCK_THE_GRASS.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.PROZORKED.value, - ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, - ZorkGrandInquisitorLocations.RESTOCKED_ON_GRUESDAY.value, - ZorkGrandInquisitorLocations.RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE.value, - ZorkGrandInquisitorLocations.RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE.value, - ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, - ZorkGrandInquisitorLocations.SOUVENIR.value, - ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, - ZorkGrandInquisitorLocations.TAMING_YOUR_SNAPDRAGON.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.THATS_A_ROPE.value, - ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS.value, - ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE.value, - ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, - ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, - ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, - ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, - ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, - ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, - ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, - ZorkGrandInquisitorLocations.TIME_TRAVEL_FOR_DUMMIES.value, - ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, - ZorkGrandInquisitorLocations.UMBRELLA_FLOWERS.value, - ZorkGrandInquisitorLocations.UP.value, - ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, - ZorkGrandInquisitorLocations.UUUUUP.value, - ZorkGrandInquisitorLocations.VOYAGE_OF_CAPTAIN_ZAHAB.value, - ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE.value, - ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF.value, - ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER.value, - ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, - ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, - ZorkGrandInquisitorEvents.CHARON_CALLED.value, - ZorkGrandInquisitorEvents.DAM_DESTROYED.value, - ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, - ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, - ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, - ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, - ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, - ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, - ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, - ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, - ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, - ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.ROPE.value,)] - ) - - def test_access_locations_requiring_scroll_fragment_ans(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SCROLL_FRAGMENT_ANS.value,)] - ) - - def test_access_locations_requiring_scroll_fragment_giv(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SCROLL_FRAGMENT_GIV.value,)] - ) - - def test_access_locations_requiring_shovel(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SHOVEL.value,)] - ) - - def test_access_locations_requiring_snapdragon(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, - ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SNAPDRAGON.value,)] - ) - - def test_access_locations_requiring_student_id(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.STUDENT_ID.value,)] - ) - - def test_access_locations_requiring_subway_token(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SUBWAY_TOKEN.value,)] - ) - - def test_access_locations_requiring_sword(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, - ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, - ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, - ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, - ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, - ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, - ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, - ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, - ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR.value, - ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.PERMASEAL.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, - ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, - ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, - ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, - ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, - ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, - ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, - ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, - ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SWORD.value,)] - ) - - def test_access_locations_requiring_zimdor_scroll(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, - ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.ZIMDOR_SCROLL.value,)] - ) - - def test_access_locations_requiring_zork_rocks(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.ZORK_ROCKS.value,)] - ) - - def test_access_locations_requiring_hotspot_666_mailbox(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_666_MAILBOX.value,)] - ) - - def test_access_locations_requiring_hotspot_alpines_quandry_card_slots(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_ALPINES_QUANDRY_CARD_SLOTS.value,)] - ) - - def test_access_locations_requiring_hotspot_blank_scroll_box(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, - ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_BLANK_SCROLL_BOX.value,)] - ) - - def test_access_locations_requiring_hotspot_blinds(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, - ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_BLINDS.value,)] - ) - - def test_access_locations_requiring_hotspot_candy_machine_buttons(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, - ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, - ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_BUTTONS.value,)] - ) - - def test_access_locations_requiring_hotspot_candy_machine_coin_slot(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, - ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, - ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_COIN_SLOT.value,)] - ) - - def test_access_locations_requiring_hotspot_candy_machine_vacuum_slot(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_CANDY_MACHINE_VACUUM_SLOT.value,)] - ) - - def test_access_locations_requiring_hotspot_change_machine_slot(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_CHANGE_MACHINE_SLOT.value,)] - ) - - def test_access_locations_requiring_hotspot_closet_door(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, - ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, - ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorLocations.DOOOOOOWN.value, - ZorkGrandInquisitorLocations.DOWN.value, - ZorkGrandInquisitorLocations.UP.value, - ZorkGrandInquisitorLocations.UUUUUP.value, - ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, - ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_CLOSET_DOOR.value,)] - ) - - def test_access_locations_requiring_hotspot_closing_the_time_tunnels_hammer_slot(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_HAMMER_SLOT.value,)] - ) - - def test_access_locations_requiring_hotspot_closing_the_time_tunnels_lever(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_CLOSING_THE_TIME_TUNNELS_LEVER.value,)] - ) - - def test_access_locations_requiring_hotspot_cooking_pot(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_COOKING_POT.value,)] - ) - - def test_access_locations_requiring_hotspot_dented_locker(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_DENTED_LOCKER.value,)] - ) - - def test_access_locations_requiring_hotspot_dirt_mound(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_DIRT_MOUND.value,)] - ) - - def test_access_locations_requiring_hotspot_dock_winch(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.HELP_ME_CANT_BREATHE.value, - ZorkGrandInquisitorLocations.NO_BONDAGE.value, - ZorkGrandInquisitorLocations.YOU_WANT_A_PIECE_OF_ME_DOCK_BOY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_DOCK_WINCH.value,)] - ) - - def test_access_locations_requiring_hotspot_dragon_claw(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_DRAGON_CLAW.value,)] - ) - - def test_access_locations_requiring_hotspot_dragon_nostrils(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_DRAGON_NOSTRILS.value,)] - ) - - def test_access_locations_requiring_hotspot_dungeon_masters_lair_entrance(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_DUNGEON_MASTERS_LAIR_ENTRANCE.value,)] - ) - - def test_access_locations_requiring_hotspot_flood_control_buttons(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, - ZorkGrandInquisitorEvents.DAM_DESTROYED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_BUTTONS.value,)] - ) - - def test_access_locations_requiring_hotspot_flood_control_doors(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, - ZorkGrandInquisitorEvents.DAM_DESTROYED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_FLOOD_CONTROL_DOORS.value,)] - ) - - def test_access_locations_requiring_hotspot_frozen_treat_machine_coin_slot(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_COIN_SLOT.value,)] - ) - - def test_access_locations_requiring_hotspot_frozen_treat_machine_doors(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_FROZEN_TREAT_MACHINE_DOORS.value,)] - ) - - def test_access_locations_requiring_hotspot_glass_case(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_GLASS_CASE.value,)] - ) - - def test_access_locations_requiring_hotspot_grand_inquisitor_doll(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.ARREST_THE_VANDAL.value, - ZorkGrandInquisitorLocations.DEATH_ARRESTED_WITH_JACK.value, - ZorkGrandInquisitorLocations.FIRE_FIRE.value, - ZorkGrandInquisitorLocations.PLANETFALL.value, - ZorkGrandInquisitorLocations.TALK_TO_ME_GRAND_INQUISITOR.value, - ZorkGrandInquisitorEvents.LANTERN_DALBOZ_ACCESSIBLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value,)] - ) - - def test_access_locations_requiring_hotspot_gue_tech_door(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_DOOR.value,)] - ) - - def test_access_locations_requiring_hotspot_gue_tech_grass(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_GUE_TECH_GRASS.value,)] - ) - - def test_access_locations_requiring_hotspot_hades_phone_buttons(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, - ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, - ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, - ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, - ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, - ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, - ZorkGrandInquisitorEvents.CHARON_CALLED.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_BUTTONS.value,)] - ) - - def test_access_locations_requiring_hotspot_hades_phone_receiver(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, - ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, - ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, - ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, - ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, - ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, - ZorkGrandInquisitorEvents.CHARON_CALLED.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_HADES_PHONE_RECEIVER.value,)] - ) - - def test_access_locations_requiring_hotspot_harry(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_HARRY.value,)] - ) - - def test_access_locations_requiring_hotspot_harrys_ashtray(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE.value, - ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_HARRYS_ASHTRAY.value,)] - ) - - def test_access_locations_requiring_hotspot_harrys_bird_bath(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, - ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_HARRYS_BIRD_BATH.value,)] - ) - - def test_access_locations_requiring_hotspot_in_magic_we_trust_door(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_IN_MAGIC_WE_TRUST_DOOR.value,)] - ) - - def test_access_locations_requiring_hotspot_jacks_door(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.MEAD_LIGHT.value, - ZorkGrandInquisitorLocations.NO_AUTOGRAPHS.value, - ZorkGrandInquisitorLocations.THATS_A_ROPE.value, - ZorkGrandInquisitorLocations.WHAT_ARE_YOU_STUPID.value, - ZorkGrandInquisitorEvents.CIGAR_ACCESSIBLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value,)] - ) - - def test_access_locations_requiring_hotspot_loudspeaker_volume_buttons(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.THATS_THE_SPIRIT.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_LOUDSPEAKER_VOLUME_BUTTONS.value,)] - ) - - def test_access_locations_requiring_hotspot_mailbox_door(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, - ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_DOOR.value,)] - ) - - def test_access_locations_requiring_hotspot_mailbox_flag(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DOOOOOOWN.value, - ZorkGrandInquisitorLocations.DOWN.value, - ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, - ZorkGrandInquisitorLocations.UP.value, - ZorkGrandInquisitorLocations.UUUUUP.value, - ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_MAILBOX_FLAG.value,)] - ) - - def test_access_locations_requiring_hotspot_mirror(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, - ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF.value, - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_MIRROR.value,)] - ) - - def test_access_locations_requiring_hotspot_monastery_vent(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, - ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, - ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.PERMASEAL.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, - ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, - ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, - ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_MONASTERY_VENT.value,)] - ) - - def test_access_locations_requiring_hotspot_mossy_grate(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_MOSSY_GRATE.value,)] - ) - - def test_access_locations_requiring_hotspot_port_foozle_past_tavern_door(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_PORT_FOOZLE_PAST_TAVERN_DOOR.value,)] - ) - - def test_access_locations_requiring_hotspot_purple_words(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.A_SMALLWAY.value, - ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, - ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_PURPLE_WORDS.value,)] - ) - - def test_access_locations_requiring_hotspot_quelbee_hive(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_QUELBEE_HIVE.value,)] - ) - - def test_access_locations_requiring_hotspot_rope_bridge(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, - ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, - ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, - ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, - ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, - ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, - ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, - ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_ROPE_BRIDGE.value,)] - ) - - def test_access_locations_requiring_hotspot_skull_cage(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_SKULL_CAGE.value,)] - ) - - def test_access_locations_requiring_hotspot_snapdragon(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BONK.value, - ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, - ZorkGrandInquisitorLocations.PROZORKED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_SNAPDRAGON.value,)] - ) - - def test_access_locations_requiring_hotspot_soda_machine_buttons(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_BUTTONS.value,)] - ) - - def test_access_locations_requiring_hotspot_soda_machine_coin_slot(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_SODA_MACHINE_COIN_SLOT.value,)] - ) - - def test_access_locations_requiring_hotspot_souvenir_coin_slot(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.SOUVENIR.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_SOUVENIR_COIN_SLOT.value,)] - ) - - def test_access_locations_requiring_hotspot_spell_checker(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, - ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, - ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, - ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, - ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, - ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_SPELL_CHECKER.value,)] - ) - - def test_access_locations_requiring_hotspot_spell_lab_chasm(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, - ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, - ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, - ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, - ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, - ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, - ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_SPELL_LAB_CHASM.value,)] - ) - - def test_access_locations_requiring_hotspot_spring_mushroom(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, - ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, - ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, - ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_SPRING_MUSHROOM.value,)] - ) - - def test_access_locations_requiring_hotspot_student_id_machine(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_STUDENT_ID_MACHINE.value,)] - ) - - def test_access_locations_requiring_hotspot_subway_token_slot(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_SUBWAY_TOKEN_SLOT.value,)] - ) - - def test_access_locations_requiring_hotspot_tavern_fly(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_TAVERN_FLY.value,)] - ) - - def test_access_locations_requiring_hotspot_totemizer_switch(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, - ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, - ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, - ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_SWITCH.value,)] - ) - - def test_access_locations_requiring_hotspot_totemizer_wheels(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, - ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, - ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, - ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_TOTEMIZER_WHEELS.value,)] - ) - - def test_access_locations_requiring_hotspot_well(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN.value, - ZorkGrandInquisitorLocations.ARTIFACTS_EXPLAINED.value, - ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER.value, - ZorkGrandInquisitorLocations.A_LETTER_FROM_THE_WHITE_HOUSE.value, - ZorkGrandInquisitorLocations.A_SMALLWAY.value, - ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, - ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED.value, - ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES.value, - ZorkGrandInquisitorLocations.BOING_BOING_BOING.value, - ZorkGrandInquisitorLocations.BONK.value, - ZorkGrandInquisitorLocations.BRAVE_SOULS_WANTED.value, - ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, - ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, - ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorLocations.CASTLE_WATCHING_A_FIELD_GUIDE.value, - ZorkGrandInquisitorLocations.CAVES_NOTES.value, - ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, - ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, - ZorkGrandInquisitorLocations.DEATH_ATTACKED_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.DEATH_CLIMBED_OUT_OF_THE_WELL.value, - ZorkGrandInquisitorLocations.DEATH_EATEN_BY_A_GRUE.value, - ZorkGrandInquisitorLocations.DEATH_JUMPED_IN_BOTTOMLESS_PIT.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.DEATH_OUTSMARTED_BY_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.DEATH_SLICED_UP_BY_THE_INVISIBLE_GUARD.value, - ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED.value, - ZorkGrandInquisitorLocations.DEATH_TOTEMIZED_PERMANENTLY.value, - ZorkGrandInquisitorLocations.DEATH_YOURE_NOT_CHARON.value, - ZorkGrandInquisitorLocations.DEATH_ZORK_ROCKS_EXPLODED.value, - ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, - ZorkGrandInquisitorLocations.DESPERATELY_SEEKING_TUTOR.value, - ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY.value, - ZorkGrandInquisitorLocations.DOOOOOOWN.value, - ZorkGrandInquisitorLocations.DOWN.value, - ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.DUNCE_LOCKER.value, - ZorkGrandInquisitorLocations.EGGPLANTS.value, - ZorkGrandInquisitorLocations.EMERGENCY_MAGICATRONIC_MESSAGE.value, - ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP.value, - ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA.value, - ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE.value, - ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, - ZorkGrandInquisitorLocations.FROBUARY_3_UNDERGROUNDHOG_DAY.value, - ZorkGrandInquisitorLocations.GETTING_SOME_CHANGE.value, - ZorkGrandInquisitorLocations.GUE_TECH_DEANS_LIST.value, - ZorkGrandInquisitorLocations.GUE_TECH_ENTRANCE_EXAM.value, - ZorkGrandInquisitorLocations.GUE_TECH_HEALTH_MEMO.value, - ZorkGrandInquisitorLocations.GUE_TECH_MAGEMEISTERS.value, - ZorkGrandInquisitorLocations.HAVE_A_HELL_OF_A_DAY.value, - ZorkGrandInquisitorLocations.HELLO_THIS_IS_SHONA_FROM_GURTH_PUBLISHING.value, - ZorkGrandInquisitorLocations.HEY_FREE_DIRT.value, - ZorkGrandInquisitorLocations.HI_MY_NAME_IS_DOUG.value, - ZorkGrandInquisitorLocations.HMMM_INFORMATIVE_YET_DEEPLY_DISTURBING.value, - ZorkGrandInquisitorLocations.HOLD_ON_FOR_AN_IMPORTANT_MESSAGE.value, - ZorkGrandInquisitorLocations.HOW_TO_HYPNOTIZE_YOURSELF.value, - ZorkGrandInquisitorLocations.HOW_TO_WIN_AT_DOUBLE_FANUCCI.value, - ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, - ZorkGrandInquisitorLocations.I_SPIT_ON_YOUR_FILTHY_COINAGE.value, - ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, - ZorkGrandInquisitorLocations.INTO_THE_FOLIAGE.value, - ZorkGrandInquisitorLocations.IN_CASE_OF_ADVENTURE.value, - ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, - ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS.value, - ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE.value, - ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, - ZorkGrandInquisitorLocations.LIT_SUNFLOWERS.value, - ZorkGrandInquisitorLocations.MAGIC_FOREVER.value, - ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, - ZorkGrandInquisitorLocations.MAKE_LOVE_NOT_WAR.value, - ZorkGrandInquisitorLocations.MIKES_PANTS.value, - ZorkGrandInquisitorLocations.MUSHROOM_HAMMERED.value, - ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, - ZorkGrandInquisitorLocations.NATURAL_AND_SUPERNATURAL_CREATURES_OF_QUENDOR.value, - ZorkGrandInquisitorLocations.NOOOOOOOOOOOOO.value, - ZorkGrandInquisitorLocations.NOTHIN_LIKE_A_GOOD_STOGIE.value, - ZorkGrandInquisitorLocations.NOW_YOU_LOOK_LIKE_US_WHICH_IS_AN_IMPROVEMENT.value, - ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.OH_WOW_TALK_ABOUT_DEJA_VU.value, - ZorkGrandInquisitorLocations.OPEN_THE_GATES_OF_HELL.value, - ZorkGrandInquisitorLocations.OUTSMART_THE_QUELBEES.value, - ZorkGrandInquisitorLocations.PERMASEAL.value, - ZorkGrandInquisitorLocations.PLEASE_DONT_THROCK_THE_GRASS.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.PROZORKED.value, - ZorkGrandInquisitorLocations.REASSEMBLE_SNAVIG.value, - ZorkGrandInquisitorLocations.RESTOCKED_ON_GRUESDAY.value, - ZorkGrandInquisitorLocations.RIGHT_HELLO_YES_UH_THIS_IS_SNEFFLE.value, - ZorkGrandInquisitorLocations.RIGHT_UH_SORRY_ITS_ME_AGAIN_SNEFFLE.value, - ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, - ZorkGrandInquisitorLocations.SOUVENIR.value, - ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.SUCKING_ROCKS.value, - ZorkGrandInquisitorLocations.TAMING_YOUR_SNAPDRAGON.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.THATS_IT_JUST_KEEP_HITTING_THOSE_BUTTONS.value, - ZorkGrandInquisitorLocations.THATS_STILL_A_ROPE.value, - ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, - ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, - ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, - ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, - ZorkGrandInquisitorLocations.THE_UNDERGROUND_UNDERGROUND.value, - ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, - ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, - ZorkGrandInquisitorLocations.TIME_TRAVEL_FOR_DUMMIES.value, - ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, - ZorkGrandInquisitorLocations.UMBRELLA_FLOWERS.value, - ZorkGrandInquisitorLocations.UP.value, - ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, - ZorkGrandInquisitorLocations.UUUUUP.value, - ZorkGrandInquisitorLocations.VOYAGE_OF_CAPTAIN_ZAHAB.value, - ZorkGrandInquisitorLocations.WANT_SOME_RYE_COURSE_YA_DO.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.WOW_IVE_NEVER_GONE_INSIDE_HIM_BEFORE.value, - ZorkGrandInquisitorLocations.YAD_GOHDNUORGREDNU_3_YRAUBORF.value, - ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER.value, - ZorkGrandInquisitorLocations.YOU_GAINED_86_EXPERIENCE_POINTS.value, - ZorkGrandInquisitorLocations.YOUR_PUNY_WEAPONS_DONT_PHASE_ME_BABY.value, - ZorkGrandInquisitorEvents.CHARON_CALLED.value, - ZorkGrandInquisitorEvents.DAM_DESTROYED.value, - ZorkGrandInquisitorEvents.DOOR_DRANK_MEAD.value, - ZorkGrandInquisitorEvents.DOOR_SMOKED_CIGAR.value, - ZorkGrandInquisitorEvents.DALBOZ_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.DUNCE_LOCKER_OPENABLE.value, - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_OBIDIL.value, - ZorkGrandInquisitorEvents.HAS_REPAIRABLE_SNAVIG.value, - ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, - ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, - ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, - ZorkGrandInquisitorEvents.KNOWS_YASTARD.value, - ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, - ZorkGrandInquisitorEvents.ZORK_ROCKS_ACTIVATED.value, - ZorkGrandInquisitorEvents.ZORK_ROCKS_SUCKABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.HOTSPOT_WELL.value,)] - ) - - def test_access_locations_requiring_spell_glorf(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorEvents.ROPE_GLORFABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SPELL_GLORF.value,)] - ) - - def test_access_locations_requiring_spell_golgatem(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DENIED_BY_THE_LAKE_MONSTER.value, - ZorkGrandInquisitorLocations.I_LIKE_YOUR_STYLE.value, - ZorkGrandInquisitorLocations.IMBUE_BEBURTT.value, - ZorkGrandInquisitorLocations.OBIDIL_DRIED_UP.value, - ZorkGrandInquisitorLocations.SNAVIG_REPAIRED.value, - ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, - ZorkGrandInquisitorEvents.KNOWS_BEBURTT.value, - ZorkGrandInquisitorEvents.KNOWS_OBIDIL.value, - ZorkGrandInquisitorEvents.KNOWS_SNAVIG.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SPELL_GOLGATEM.value,)] - ) - - def test_access_locations_requiring_spell_igram(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.A_SMALLWAY.value, - ZorkGrandInquisitorLocations.CRISIS_AVERTED.value, - ZorkGrandInquisitorLocations.DEATH_STEPPED_INTO_THE_INFINITE.value, - ZorkGrandInquisitorLocations.FAT_LOT_OF_GOOD_THATLL_DO_YA.value, - ZorkGrandInquisitorLocations.INVISIBLE_FLOWERS.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SPELL_IGRAM.value,)] - ) - - def test_access_locations_requiring_spell_kendall(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BEBURTT_DEMYSTIFIED.value, - ZorkGrandInquisitorLocations.ENJOY_YOUR_TRIP.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SPELL_KENDALL.value,)] - ) - - def test_access_locations_requiring_spell_narwile(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, - ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, - ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.DOOOOOOWN.value, - ZorkGrandInquisitorLocations.DOWN.value, - ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, - ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, - ZorkGrandInquisitorLocations.UP.value, - ZorkGrandInquisitorLocations.UUUUUP.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorLocations.WHITE_HOUSE_TIME_TUNNEL.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ZorkGrandInquisitorEvents.WHITE_HOUSE_LETTER_MAILABLE.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SPELL_NARWILE.value,)] - ) - - def test_access_locations_requiring_spell_rezrov(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.IN_MAGIC_WE_TRUST.value, - ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, - ZorkGrandInquisitorLocations.YOU_DONT_GO_MESSING_WITH_A_MANS_ZIPPER.value, - ZorkGrandInquisitorEvents.DAM_DESTROYED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SPELL_REZROV.value,)] - ) - - def test_access_locations_requiring_spell_throck(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, - ZorkGrandInquisitorLocations.DEATH_THROCKED_THE_GRASS.value, - ZorkGrandInquisitorLocations.FLYING_SNAPDRAGON.value, - ZorkGrandInquisitorLocations.I_DONT_THINK_YOU_WOULDVE_WANTED_THAT_TO_WORK_ANYWAY.value, - ZorkGrandInquisitorLocations.LIT_SUNFLOWERS.value, - ZorkGrandInquisitorLocations.THROCKED_MUSHROOM_HAMMERED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SPELL_THROCK.value,)] - ) - - def test_access_locations_requiring_subway_destination_flood_control_dam(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY.value, - ZorkGrandInquisitorLocations.FLOOD_CONTROL_DAM_3_THE_NOT_REMOTELY_BORING_TALE.value, - ZorkGrandInquisitorLocations.NATIONAL_TREASURE.value, - ZorkGrandInquisitorLocations.SOUVENIR.value, - ZorkGrandInquisitorLocations.USELESS_BUT_FUN.value, - ZorkGrandInquisitorEvents.DAM_DESTROYED.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_FLOOD_CONTROL_DAM.value,)] - ) - - def test_access_locations_requiring_subway_destination_hades(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_HADES.value,)] - ) - - def test_access_locations_requiring_subway_destination_monastery(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.SUBWAY_DESTINATION_MONASTERY.value,)] - ) - - def test_access_locations_requiring_teleporter_destination_dm_lair(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_DM_LAIR.value,)] - ) - - def test_access_locations_requiring_teleporter_destination_gue_tech(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_GUE_TECH.value,)] - ) - - def test_access_locations_requiring_teleporter_destination_hades(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_HADES.value,)] - ) - - def test_access_locations_requiring_teleporter_destination_monastery(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_MONASTERY.value,)] - ) - - def test_access_locations_requiring_teleporter_destination_spell_lab(self) -> None: - locations: List[str] = list() - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TELEPORTER_DESTINATION_SPELL_LAB.value,)] - ) - - def test_access_locations_requiring_totemizer_destination_hall_of_inquisition(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.CLOSING_THE_TIME_TUNNELS.value, - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.PORT_FOOZLE_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.THE_ALCHEMICAL_DEBACLE.value, - ZorkGrandInquisitorLocations.THE_ENDLESS_FIRE.value, - ZorkGrandInquisitorLocations.THE_FLATHEADIAN_FUDGE_FIASCO.value, - ZorkGrandInquisitorLocations.THE_PERILS_OF_MAGIC.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_HALL_OF_INQUISITION.value,)] - ) - - def test_access_locations_requiring_totemizer_destination_straight_to_hell(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.STRAIGHT_TO_HELL.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TOTEMIZER_DESTINATION_STRAIGHT_TO_HELL.value,)] - ) - - def test_access_locations_requiring_totem_brog(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.BROG_DO_GOOD.value, - ZorkGrandInquisitorLocations.BROG_EAT_ROCKS.value, - ZorkGrandInquisitorLocations.BROG_KNOW_DUMB_THAT_DUMB.value, - ZorkGrandInquisitorLocations.BROG_MUCH_BETTER_AT_THIS_GAME.value, - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.OH_VERY_FUNNY_GUYS.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, - ZorkGrandInquisitorLocations.UH_OH_BROG_CANT_SWIM.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TOTEM_BROG.value,)] - ) - - def test_access_locations_requiring_totem_griff(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_SWALLOWED_BY_A_DRAGON.value, - ZorkGrandInquisitorLocations.DOOOOOOWN.value, - ZorkGrandInquisitorLocations.OH_DEAR_GOD_ITS_A_DRAGON.value, - ZorkGrandInquisitorLocations.THAR_SHE_BLOWS.value, - ZorkGrandInquisitorLocations.UUUUUP.value, - ZorkGrandInquisitorLocations.WE_DONT_SERVE_YOUR_KIND_HERE.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TOTEM_GRIFF.value,)] - ) - - def test_access_locations_requiring_totem_lucy(self) -> None: - locations: List[str] = [ - ZorkGrandInquisitorLocations.DEATH_LOST_GAME_OF_STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.DOWN.value, - ZorkGrandInquisitorLocations.STRIP_GRUE_FIRE_WATER.value, - ZorkGrandInquisitorLocations.THIS_DOESNT_LOOK_ANYTHING_LIKE_THE_BROCHURE.value, - ZorkGrandInquisitorLocations.UP.value, - ZorkGrandInquisitorLocations.WE_GOT_A_HIGH_ROLLER.value, - ZorkGrandInquisitorEvents.VICTORY.value, - ] - - self.assertAccessDependency( - locations, [(ZorkGrandInquisitorItems.TOTEM_LUCY.value,)] - ) diff --git a/worlds/zork_grand_inquisitor/test/test_data_funcs.py b/worlds/zork_grand_inquisitor/test/test_data_funcs.py deleted file mode 100644 index 9d8d5a4ba3..0000000000 --- a/worlds/zork_grand_inquisitor/test/test_data_funcs.py +++ /dev/null @@ -1,132 +0,0 @@ -import unittest - -from ..data_funcs import location_access_rule_for, entrance_access_rule_for -from ..enums import ZorkGrandInquisitorLocations, ZorkGrandInquisitorRegions - - -class DataFuncsTest(unittest.TestCase): - def test_location_access_rule_for(self) -> None: - # No Requirements - self.assertEqual( - "lambda state: True", - location_access_rule_for(ZorkGrandInquisitorLocations.ALARM_SYSTEM_IS_DOWN, 1), - ) - - # Single Item Requirement - self.assertEqual( - 'lambda state: state.has("Sword", 1)', - location_access_rule_for(ZorkGrandInquisitorLocations.DONT_EVEN_START_WITH_US_SPARKY, 1), - ) - - self.assertEqual( - 'lambda state: state.has("Spell: NARWILE", 1)', - location_access_rule_for(ZorkGrandInquisitorLocations.DRAGON_ARCHIPELAGO_TIME_TUNNEL, 1), - ) - - # Single Event Requirement - self.assertEqual( - 'lambda state: state.has("Event: Knows OBIDIL", 1)', - location_access_rule_for(ZorkGrandInquisitorLocations.A_BIG_FAT_SASSY_2_HEADED_MONSTER, 1), - ) - - self.assertEqual( - 'lambda state: state.has("Event: Dunce Locker Openable", 1)', - location_access_rule_for(ZorkGrandInquisitorLocations.BETTER_SPELL_MANUFACTURING_IN_UNDER_10_MINUTES, 1), - ) - - # Multiple Item Requirements - self.assertEqual( - 'lambda state: state.has("Hotspot: Purple Words", 1) and state.has("Spell: IGRAM", 1)', - location_access_rule_for(ZorkGrandInquisitorLocations.A_SMALLWAY, 1), - ) - - self.assertEqual( - 'lambda state: state.has("Hotspot: Mossy Grate", 1) and state.has("Spell: THROCK", 1)', - location_access_rule_for(ZorkGrandInquisitorLocations.BEAUTIFUL_THATS_PLENTY, 1), - ) - - # Multiple Item Requirements OR - self.assertEqual( - 'lambda state: (state.has("Totem: Griff", 1) or state.has("Totem: Lucy", 1)) and state.has("Hotspot: Mailbox Door", 1) and state.has("Hotspot: Mailbox Flag", 1)', - location_access_rule_for(ZorkGrandInquisitorLocations.MAILED_IT_TO_HELL, 1), - ) - - # Multiple Mixed Requirements - self.assertEqual( - 'lambda state: state.has("Event: Cigar Accessible", 1) and state.has("Hotspot: Grand Inquisitor Doll", 1)', - location_access_rule_for(ZorkGrandInquisitorLocations.ARREST_THE_VANDAL, 1), - ) - - self.assertEqual( - 'lambda state: state.has("Sword", 1) and state.has("Event: Rope GLORFable", 1) and state.has("Hotspot: Monastery Vent", 1)', - location_access_rule_for(ZorkGrandInquisitorLocations.I_HOPE_YOU_CAN_CLIMB_UP_THERE, 1), - ) - - def test_entrance_access_rule_for(self) -> None: - # No Requirements - self.assertEqual( - "lambda state: True", - entrance_access_rule_for( - ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.PORT_FOOZLE, 1 - ), - ) - - self.assertEqual( - "lambda state: True", - entrance_access_rule_for( - ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.CROSSROADS, 1 - ), - ) - - # Single Requirement - self.assertEqual( - 'lambda state: (state.has("Map", 1))', - entrance_access_rule_for( - ZorkGrandInquisitorRegions.GUE_TECH_OUTSIDE, ZorkGrandInquisitorRegions.CROSSROADS, 1 - ), - ) - - self.assertEqual( - 'lambda state: (state.has("Map", 1))', - entrance_access_rule_for( - ZorkGrandInquisitorRegions.HADES_SHORE, ZorkGrandInquisitorRegions.CROSSROADS, 1 - ), - ) - - # Multiple Requirements AND - self.assertEqual( - 'lambda state: (state.has("Spell: REZROV", 1) and state.has("Hotspot: In Magic We Trust Door", 1))', - entrance_access_rule_for( - ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.GUE_TECH, 1 - ), - ) - - self.assertEqual( - 'lambda state: (state.has("Event: Door Smoked Cigar", 1) and state.has("Event: Door Drank Mead", 1))', - entrance_access_rule_for( - ZorkGrandInquisitorRegions.DM_LAIR, ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, 1 - ), - ) - - self.assertEqual( - 'lambda state: (state.has("Hotspot: Closet Door", 1) and state.has("Spell: NARWILE", 1) and state.has("Event: Knows YASTARD", 1))', - entrance_access_rule_for( - ZorkGrandInquisitorRegions.DM_LAIR_INTERIOR, ZorkGrandInquisitorRegions.WHITE_HOUSE, 1 - ), - ) - - # Multiple Requirements AND + OR - self.assertEqual( - 'lambda state: (state.has("Sword", 1) and state.has("Hotspot: Dungeon Master\'s Lair Entrance", 1)) or (state.has("Map", 1) and state.has("Teleporter Destination: Dungeon Master\'s Lair", 1))', - entrance_access_rule_for( - ZorkGrandInquisitorRegions.CROSSROADS, ZorkGrandInquisitorRegions.DM_LAIR, 1 - ), - ) - - # Multiple Requirements Regions - self.assertEqual( - 'lambda state: (state.has("Griff\'s Air Pump", 1) and state.has("Griff\'s Inflatable Raft", 1) and state.has("Griff\'s Inflatable Sea Captain", 1) and state.has("Hotspot: Dragon Nostrils", 1) and state.has("Griff\'s Dragon Tooth", 1) and state.can_reach("Port Foozle Past - Tavern", "Region", 1) and state.has("Lucy\'s Playing Card: 1 Pip", 1) and state.has("Lucy\'s Playing Card: 2 Pips", 1) and state.has("Lucy\'s Playing Card: 3 Pips", 1) and state.has("Lucy\'s Playing Card: 4 Pips", 1) and state.has("Hotspot: Tavern Fly", 1) and state.has("Hotspot: Alpine\'s Quandry Card Slots", 1) and state.can_reach("White House", "Region", 1) and state.has("Totem: Brog", 1) and state.has("Brog\'s Flickering Torch", 1) and state.has("Brog\'s Grue Egg", 1) and state.has("Hotspot: Cooking Pot", 1) and state.has("Brog\'s Plank", 1) and state.has("Hotspot: Skull Cage", 1))', - entrance_access_rule_for( - ZorkGrandInquisitorRegions.DRAGON_ARCHIPELAGO_DRAGON, ZorkGrandInquisitorRegions.ENDGAME, 1 - ), - ) diff --git a/worlds/zork_grand_inquisitor/test/test_locations.py b/worlds/zork_grand_inquisitor/test/test_locations.py deleted file mode 100644 index fa576dd510..0000000000 --- a/worlds/zork_grand_inquisitor/test/test_locations.py +++ /dev/null @@ -1,49 +0,0 @@ -from typing import Dict, Set - -from . import ZorkGrandInquisitorTestBase - -from ..data_funcs import location_names_to_location, locations_with_tag -from ..enums import ZorkGrandInquisitorLocations, ZorkGrandInquisitorTags - - -class LocationsTestNoDeathsanity(ZorkGrandInquisitorTestBase): - options = { - "deathsanity": "false", - } - - def test_correct_locations_exist(self) -> None: - expected_locations: Set[ZorkGrandInquisitorLocations] = locations_with_tag( - ZorkGrandInquisitorTags.CORE - ) - - self._assert_expected_locations_exist(expected_locations) - - def _assert_expected_locations_exist(self, expected_locations: Set[ZorkGrandInquisitorLocations]) -> None: - location_name_to_location: Dict[str, ZorkGrandInquisitorLocations] = location_names_to_location() - - for location_object in self.multiworld.get_locations(1): - location: ZorkGrandInquisitorLocations = location_name_to_location.get( - location_object.name - ) - - if location is None: - continue - - self.assertIn(location, expected_locations) - - expected_locations.remove(location) - - self.assertEqual(0, len(expected_locations)) - - -class LocationsTestDeathsanity(LocationsTestNoDeathsanity): - options = { - "deathsanity": "true", - } - - def test_correct_locations_exist(self) -> None: - expected_locations: Set[ZorkGrandInquisitorLocations] = ( - locations_with_tag(ZorkGrandInquisitorTags.CORE) | locations_with_tag(ZorkGrandInquisitorTags.DEATHSANITY) - ) - - self._assert_expected_locations_exist(expected_locations) diff --git a/worlds/zork_grand_inquisitor/world.py b/worlds/zork_grand_inquisitor/world.py deleted file mode 100644 index 3698ad7f89..0000000000 --- a/worlds/zork_grand_inquisitor/world.py +++ /dev/null @@ -1,205 +0,0 @@ -from typing import Any, Dict, List, Tuple - -from BaseClasses import Item, ItemClassification, Location, Region, Tutorial - -from worlds.AutoWorld import WebWorld, World - -from .data.item_data import item_data, ZorkGrandInquisitorItemData -from .data.location_data import location_data, ZorkGrandInquisitorLocationData -from .data.region_data import region_data - -from .data_funcs import ( - item_names_to_id, - item_names_to_item, - location_names_to_id, - item_groups, - items_with_tag, - location_groups, - locations_by_region, - location_access_rule_for, - entrance_access_rule_for, -) - -from .enums import ( - ZorkGrandInquisitorEvents, - ZorkGrandInquisitorItems, - ZorkGrandInquisitorLocations, - ZorkGrandInquisitorRegions, - ZorkGrandInquisitorTags, -) - -from .options import ZorkGrandInquisitorOptions - - -class ZorkGrandInquisitorItem(Item): - game = "Zork Grand Inquisitor" - - -class ZorkGrandInquisitorLocation(Location): - game = "Zork Grand Inquisitor" - - -class ZorkGrandInquisitorWebWorld(WebWorld): - theme: str = "stone" - - tutorials: List[Tutorial] = [ - Tutorial( - "Multiworld Setup Guide", - "A guide to setting up the Zork Grand Inquisitor randomizer connected to an Archipelago Multiworld", - "English", - "setup_en.md", - "setup/en", - ["Serpent.AI"], - ) - ] - - -class ZorkGrandInquisitorWorld(World): - """ - Zork: Grand Inquisitor is a 1997 point-and-click adventure game for PC. - Magic has been banned from the great Underground Empire of Zork. By edict of the Grand Inquisitor Mir Yannick, the - Empire has been sealed off and the practice of mystic arts declared punishable by "Totemization" (a very bad thing). - The only way to restore magic to the kingdom is to find three hidden artifacts: The Coconut of Quendor, The Cube of - Foundation, and The Skull of Yoruk. - """ - - options_dataclass = ZorkGrandInquisitorOptions - options: ZorkGrandInquisitorOptions - - game = "Zork Grand Inquisitor" - - item_name_to_id = item_names_to_id() - location_name_to_id = location_names_to_id() - - item_name_groups = item_groups() - location_name_groups = location_groups() - - required_client_version: Tuple[int, int, int] = (0, 4, 4) - - web = ZorkGrandInquisitorWebWorld() - - filler_item_names: List[str] = item_groups()["Filler"] - item_name_to_item: Dict[str, ZorkGrandInquisitorItems] = item_names_to_item() - - def create_regions(self) -> None: - deathsanity: bool = bool(self.options.deathsanity) - - region_mapping: Dict[ZorkGrandInquisitorRegions, Region] = dict() - - region_enum_item: ZorkGrandInquisitorRegions - for region_enum_item in region_data.keys(): - region_mapping[region_enum_item] = Region(region_enum_item.value, self.player, self.multiworld) - - region_locations_mapping: Dict[ZorkGrandInquisitorRegions, List[ZorkGrandInquisitorLocations]] - region_locations_mapping = locations_by_region(include_deathsanity=deathsanity) - - region_enum_item: ZorkGrandInquisitorRegions - region: Region - for region_enum_item, region in region_mapping.items(): - regions_locations: List[ZorkGrandInquisitorLocations] = region_locations_mapping[region_enum_item] - - # Locations - location_enum_item: ZorkGrandInquisitorLocations - for location_enum_item in regions_locations: - data: ZorkGrandInquisitorLocationData = location_data[location_enum_item] - - location: ZorkGrandInquisitorLocation = ZorkGrandInquisitorLocation( - self.player, - location_enum_item.value, - data.archipelago_id, - region_mapping[data.region], - ) - - if isinstance(location_enum_item, ZorkGrandInquisitorEvents): - location.place_locked_item( - ZorkGrandInquisitorItem( - data.event_item_name, - ItemClassification.progression, - None, - self.player, - ) - ) - - location_access_rule: str = location_access_rule_for(location_enum_item, self.player) - - if location_access_rule != "lambda state: True": - location.access_rule = eval(location_access_rule) - - region.locations.append(location) - - # Connections - region_exit: ZorkGrandInquisitorRegions - for region_exit in region_data[region_enum_item].exits or tuple(): - entrance_access_rule: str = entrance_access_rule_for(region_enum_item, region_exit, self.player) - - if entrance_access_rule == "lambda state: True": - region.connect(region_mapping[region_exit]) - else: - region.connect(region_mapping[region_exit], rule=eval(entrance_access_rule)) - - self.multiworld.regions.append(region) - - def create_items(self) -> None: - quick_port_foozle: bool = bool(self.options.quick_port_foozle) - start_with_hotspot_items: bool = bool(self.options.start_with_hotspot_items) - - item_pool: List[ZorkGrandInquisitorItem] = list() - - item: ZorkGrandInquisitorItems - data: ZorkGrandInquisitorItemData - for item, data in item_data.items(): - tags: Tuple[ZorkGrandInquisitorTags, ...] = data.tags or tuple() - - if ZorkGrandInquisitorTags.FILLER in tags: - continue - elif ZorkGrandInquisitorTags.HOTSPOT in tags and start_with_hotspot_items: - continue - - item_pool.append(self.create_item(item.value)) - - total_locations: int = len(self.multiworld.get_unfilled_locations(self.player)) - item_pool += [self.create_filler() for _ in range(total_locations - len(item_pool))] - - self.multiworld.itempool += item_pool - - if quick_port_foozle: - self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.ROPE.value] = 1 - self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.LANTERN.value] = 1 - - if not start_with_hotspot_items: - self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.HOTSPOT_WELL.value] = 1 - self.multiworld.early_items[self.player][ZorkGrandInquisitorItems.HOTSPOT_JACKS_DOOR.value] = 1 - - self.multiworld.early_items[self.player][ - ZorkGrandInquisitorItems.HOTSPOT_GRAND_INQUISITOR_DOLL.value - ] = 1 - - if start_with_hotspot_items: - item: ZorkGrandInquisitorItems - for item in sorted(items_with_tag(ZorkGrandInquisitorTags.HOTSPOT), key=lambda item: item.name): - self.multiworld.push_precollected(self.create_item(item.value)) - - def create_item(self, name: str) -> ZorkGrandInquisitorItem: - data: ZorkGrandInquisitorItemData = item_data[self.item_name_to_item[name]] - - return ZorkGrandInquisitorItem( - name, - data.classification, - data.archipelago_id, - self.player, - ) - - def generate_basic(self) -> None: - self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player) - - def fill_slot_data(self) -> Dict[str, Any]: - return self.options.as_dict( - "goal", - "quick_port_foozle", - "start_with_hotspot_items", - "deathsanity", - "grant_missable_location_checks", - ) - - def get_filler_item_name(self) -> str: - return self.random.choice(self.filler_item_names)