From 401a6d9a42d589fc6e3927f82661f3b11cb72dad Mon Sep 17 00:00:00 2001 From: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:27:42 +0200 Subject: [PATCH] The Witness: The big dumb refactor (#3007) --- worlds/witness/__init__.py | 168 +++--- worlds/witness/{ => data}/WitnessItems.txt | 0 worlds/witness/{ => data}/WitnessLogic.txt | 0 .../witness/{ => data}/WitnessLogicExpert.txt | 0 .../{ => data}/WitnessLogicVanilla.txt | 0 worlds/witness/data/__init__.py | 0 .../witness/data/item_definition_classes.py | 59 ++ .../{ => data}/settings/Audio_Logs.txt | 0 .../{ => data}/settings/Door_Shuffle/Boat.txt | 0 .../Complex_Additional_Panels.txt | 0 .../Door_Shuffle/Complex_Door_Panels.txt | 0 .../settings/Door_Shuffle/Complex_Doors.txt | 0 .../Door_Shuffle/Elevators_Come_To_You.txt | 0 .../settings/Door_Shuffle/Obelisk_Keys.txt | 0 .../Door_Shuffle/Simple_Additional_Panels.txt | 0 .../settings/Door_Shuffle/Simple_Doors.txt | 0 .../settings/Door_Shuffle/Simple_Panels.txt | 0 .../{ => data}/settings/EP_Shuffle/EP_All.txt | 0 .../settings/EP_Shuffle/EP_Easy.txt | 0 .../settings/EP_Shuffle/EP_NoEclipse.txt | 0 .../settings/EP_Shuffle/EP_Sides.txt | 0 .../{ => data}/settings/Early_Caves.txt | 0 .../{ => data}/settings/Early_Caves_Start.txt | 0 .../Exclusions/Disable_Unrandomized.txt | 0 .../settings/Exclusions/Discards.txt | 0 .../{ => data}/settings/Exclusions/Vaults.txt | 0 .../{ => data}/settings/Laser_Shuffle.txt | 0 .../settings/Postgame/Beyond_Challenge.txt | 0 .../Postgame/Bottom_Floor_Discard.txt | 0 .../Bottom_Floor_Discard_NonDoors.txt | 0 .../{ => data}/settings/Postgame/Caves.txt | 0 .../settings/Postgame/Challenge_Vault_Box.txt | 0 .../settings/Postgame/Mountain_Lower.txt | 0 .../settings/Postgame/Mountain_Upper.txt | 0 .../settings/Postgame/Path_To_Challenge.txt | 0 .../{ => data}/settings/Symbol_Shuffle.txt | 0 worlds/witness/data/static_items.py | 56 ++ worlds/witness/data/static_locations.py | 482 ++++++++++++++++ worlds/witness/{ => data}/static_logic.py | 257 ++++----- worlds/witness/{ => data}/utils.py | 22 +- worlds/witness/hints.py | 55 +- worlds/witness/locations.py | 528 +----------------- worlds/witness/options.py | 13 +- worlds/witness/{items.py => player_items.py} | 114 +--- worlds/witness/player_logic.py | 204 +++---- worlds/witness/regions.py | 40 +- worlds/witness/ruff.toml | 11 + worlds/witness/rules.py | 112 ++-- 48 files changed, 1080 insertions(+), 1041 deletions(-) rename worlds/witness/{ => data}/WitnessItems.txt (100%) rename worlds/witness/{ => data}/WitnessLogic.txt (100%) rename worlds/witness/{ => data}/WitnessLogicExpert.txt (100%) rename worlds/witness/{ => data}/WitnessLogicVanilla.txt (100%) create mode 100644 worlds/witness/data/__init__.py create mode 100644 worlds/witness/data/item_definition_classes.py rename worlds/witness/{ => data}/settings/Audio_Logs.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Boat.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Complex_Additional_Panels.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Complex_Door_Panels.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Complex_Doors.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Elevators_Come_To_You.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Obelisk_Keys.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Simple_Additional_Panels.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Simple_Doors.txt (100%) rename worlds/witness/{ => data}/settings/Door_Shuffle/Simple_Panels.txt (100%) rename worlds/witness/{ => data}/settings/EP_Shuffle/EP_All.txt (100%) rename worlds/witness/{ => data}/settings/EP_Shuffle/EP_Easy.txt (100%) rename worlds/witness/{ => data}/settings/EP_Shuffle/EP_NoEclipse.txt (100%) rename worlds/witness/{ => data}/settings/EP_Shuffle/EP_Sides.txt (100%) rename worlds/witness/{ => data}/settings/Early_Caves.txt (100%) rename worlds/witness/{ => data}/settings/Early_Caves_Start.txt (100%) rename worlds/witness/{ => data}/settings/Exclusions/Disable_Unrandomized.txt (100%) rename worlds/witness/{ => data}/settings/Exclusions/Discards.txt (100%) rename worlds/witness/{ => data}/settings/Exclusions/Vaults.txt (100%) rename worlds/witness/{ => data}/settings/Laser_Shuffle.txt (100%) rename worlds/witness/{ => data}/settings/Postgame/Beyond_Challenge.txt (100%) rename worlds/witness/{ => data}/settings/Postgame/Bottom_Floor_Discard.txt (100%) rename worlds/witness/{ => data}/settings/Postgame/Bottom_Floor_Discard_NonDoors.txt (100%) rename worlds/witness/{ => data}/settings/Postgame/Caves.txt (100%) rename worlds/witness/{ => data}/settings/Postgame/Challenge_Vault_Box.txt (100%) rename worlds/witness/{ => data}/settings/Postgame/Mountain_Lower.txt (100%) rename worlds/witness/{ => data}/settings/Postgame/Mountain_Upper.txt (100%) rename worlds/witness/{ => data}/settings/Postgame/Path_To_Challenge.txt (100%) rename worlds/witness/{ => data}/settings/Symbol_Shuffle.txt (100%) create mode 100644 worlds/witness/data/static_items.py create mode 100644 worlds/witness/data/static_locations.py rename worlds/witness/{ => data}/static_logic.py (51%) rename worlds/witness/{ => data}/utils.py (93%) rename worlds/witness/{items.py => player_items.py} (66%) create mode 100644 worlds/witness/ruff.toml diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py index 88de0f31..a506cc07 100644 --- a/worlds/witness/__init__.py +++ b/worlds/witness/__init__.py @@ -2,24 +2,26 @@ Archipelago init file for The Witness """ import dataclasses +from logging import error, warning +from typing import Any, Dict, List, Optional, cast + +from BaseClasses import CollectionState, Entrance, Location, Region, Tutorial -from typing import Dict, Optional, cast -from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState from Options import PerGameCommonOptions, Toggle -from .presets import witness_option_presets -from worlds.AutoWorld import World, WebWorld -from .player_logic import WitnessPlayerLogic -from .static_logic import StaticWitnessLogic, ItemCategory, DoorItemDefinition -from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \ - get_priority_hint_items, make_always_and_priority_hints, generate_joke_hints, make_area_hints, get_hintable_areas, \ - make_extra_location_hints, create_all_hints, make_laser_hints, make_compact_hint_data, CompactItemData -from .locations import WitnessPlayerLocations, StaticWitnessLocations -from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData -from .regions import WitnessRegions -from .rules import set_rules +from worlds.AutoWorld import WebWorld, World + +from .data import static_items as static_witness_items +from .data import static_logic as static_witness_logic +from .data.item_definition_classes import DoorItemDefinition, ItemData +from .data.utils import get_audio_logs +from .hints import CompactItemData, create_all_hints, generate_joke_hints, make_compact_hint_data, make_laser_hints +from .locations import WitnessPlayerLocations, static_witness_locations from .options import TheWitnessOptions -from .utils import get_audio_logs, get_laser_shuffle -from logging import warning, error +from .player_items import WitnessItem, WitnessPlayerItems +from .player_logic import WitnessPlayerLogic +from .presets import witness_option_presets +from .regions import WitnessPlayerRegions +from .rules import set_rules class WitnessWebWorld(WebWorld): @@ -50,46 +52,43 @@ class WitnessWorld(World): options: TheWitnessOptions item_name_to_id = { - name: data.ap_code for name, data in StaticWitnessItems.item_data.items() + name: data.ap_code for name, data in static_witness_items.ITEM_DATA.items() } - location_name_to_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID - item_name_groups = StaticWitnessItems.item_groups - location_name_groups = StaticWitnessLocations.AREA_LOCATION_GROUPS + location_name_to_id = static_witness_locations.ALL_LOCATIONS_TO_ID + item_name_groups = static_witness_items.ITEM_GROUPS + location_name_groups = static_witness_locations.AREA_LOCATION_GROUPS required_client_version = (0, 4, 5) - def __init__(self, multiworld: "MultiWorld", player: int): - super().__init__(multiworld, player) + player_logic: WitnessPlayerLogic + player_locations: WitnessPlayerLocations + player_items: WitnessPlayerItems + player_regions: WitnessPlayerRegions - self.player_logic = None - self.locat = None - self.items = None - self.regio = None + log_ids_to_hints: Dict[int, CompactItemData] + laser_ids_to_hints: Dict[int, CompactItemData] - self.log_ids_to_hints: Dict[int, CompactItemData] = dict() - self.laser_ids_to_hints: Dict[int, CompactItemData] = dict() + items_placed_early: List[str] + own_itempool: List[WitnessItem] - self.items_placed_early = [] - self.own_itempool = [] - - def _get_slot_data(self): + def _get_slot_data(self) -> Dict[str, Any]: return { - 'seed': self.random.randrange(0, 1000000), - 'victory_location': int(self.player_logic.VICTORY_LOCATION, 16), - 'panelhex_to_id': self.locat.CHECK_PANELHEX_TO_ID, - 'item_id_to_door_hexes': StaticWitnessItems.get_item_to_door_mappings(), - 'door_hexes_in_the_pool': self.items.get_door_ids_in_pool(), - 'symbols_not_in_the_game': self.items.get_symbol_ids_not_in_pool(), - 'disabled_entities': [int(h, 16) for h in self.player_logic.COMPLETELY_DISABLED_ENTITIES], - 'log_ids_to_hints': self.log_ids_to_hints, - 'laser_ids_to_hints': self.laser_ids_to_hints, - 'progressive_item_lists': self.items.get_progressive_item_ids_in_pool(), - 'obelisk_side_id_to_EPs': StaticWitnessLogic.OBELISK_SIDE_ID_TO_EP_HEXES, - 'precompleted_puzzles': [int(h, 16) for h in self.player_logic.EXCLUDED_LOCATIONS], - 'entity_to_name': StaticWitnessLogic.ENTITY_ID_TO_NAME, + "seed": self.random.randrange(0, 1000000), + "victory_location": int(self.player_logic.VICTORY_LOCATION, 16), + "panelhex_to_id": self.player_locations.CHECK_PANELHEX_TO_ID, + "item_id_to_door_hexes": static_witness_items.get_item_to_door_mappings(), + "door_hexes_in_the_pool": self.player_items.get_door_ids_in_pool(), + "symbols_not_in_the_game": self.player_items.get_symbol_ids_not_in_pool(), + "disabled_entities": [int(h, 16) for h in self.player_logic.COMPLETELY_DISABLED_ENTITIES], + "log_ids_to_hints": self.log_ids_to_hints, + "laser_ids_to_hints": self.laser_ids_to_hints, + "progressive_item_lists": self.player_items.get_progressive_item_ids_in_pool(), + "obelisk_side_id_to_EPs": static_witness_logic.OBELISK_SIDE_ID_TO_EP_HEXES, + "precompleted_puzzles": [int(h, 16) for h in self.player_logic.EXCLUDED_LOCATIONS], + "entity_to_name": static_witness_logic.ENTITY_ID_TO_NAME, } - def determine_sufficient_progression(self): + def determine_sufficient_progression(self) -> None: """ Determine whether there are enough progression items in this world to consider it "interactive". In the case of singleplayer, this just outputs a warning. @@ -127,20 +126,20 @@ class WitnessWorld(World): elif not interacts_sufficiently_with_multiworld and self.multiworld.players > 1: raise Exception(f"{self.multiworld.get_player_name(self.player)}'s Witness world doesn't have enough" f" progression items that can be placed in other players' worlds. Please turn on Symbol" - f" Shuffle, Door Shuffle or Obelisk Keys.") + f" Shuffle, Door Shuffle, or Obelisk Keys.") - def generate_early(self): + def generate_early(self) -> None: disabled_locations = self.options.exclude_locations.value self.player_logic = WitnessPlayerLogic( self, disabled_locations, self.options.start_inventory.value ) - self.locat: WitnessPlayerLocations = WitnessPlayerLocations(self, self.player_logic) - self.items: WitnessPlayerItems = WitnessPlayerItems( - self, self.player_logic, self.locat + self.player_locations: WitnessPlayerLocations = WitnessPlayerLocations(self, self.player_logic) + self.player_items: WitnessPlayerItems = WitnessPlayerItems( + self, self.player_logic, self.player_locations ) - self.regio: WitnessRegions = WitnessRegions(self.locat, self) + self.player_regions: WitnessPlayerRegions = WitnessPlayerRegions(self.player_locations, self) self.log_ids_to_hints = dict() @@ -149,22 +148,27 @@ class WitnessWorld(World): if self.options.shuffle_lasers == "local": self.options.local_items.value |= self.item_name_groups["Lasers"] - def create_regions(self): - self.regio.create_regions(self, self.player_logic) + def create_regions(self) -> None: + self.player_regions.create_regions(self, self.player_logic) # Set rules early so extra locations can be created based on the results of exploring collection states set_rules(self) + # Start creating items + + self.items_placed_early = [] + self.own_itempool = [] + # Add event items and tie them to event locations (e.g. laser activations). event_locations = [] - for event_location in self.locat.EVENT_LOCATION_TABLE: + for event_location in self.player_locations.EVENT_LOCATION_TABLE: item_obj = self.create_item( self.player_logic.EVENT_ITEM_PAIRS[event_location] ) - location_obj = self.multiworld.get_location(event_location, self.player) + location_obj = self.get_location(event_location) location_obj.place_locked_item(item_obj) self.own_itempool.append(item_obj) @@ -172,14 +176,16 @@ class WitnessWorld(World): # Place other locked items dog_puzzle_skip = self.create_item("Puzzle Skip") - self.multiworld.get_location("Town Pet the Dog", self.player).place_locked_item(dog_puzzle_skip) + self.get_location("Town Pet the Dog").place_locked_item(dog_puzzle_skip) self.own_itempool.append(dog_puzzle_skip) self.items_placed_early.append("Puzzle Skip") # Pick an early item to place on the tutorial gate. - early_items = [item for item in self.items.get_early_items() if item in self.items.get_mandatory_items()] + early_items = [ + item for item in self.player_items.get_early_items() if item in self.player_items.get_mandatory_items() + ] if early_items: random_early_item = self.random.choice(early_items) if self.options.puzzle_randomization == "sigma_expert": @@ -188,7 +194,7 @@ class WitnessWorld(World): else: # Force the item onto the tutorial gate check and remove it from our random pool. gate_item = self.create_item(random_early_item) - self.multiworld.get_location("Tutorial Gate Open", self.player).place_locked_item(gate_item) + self.get_location("Tutorial Gate Open").place_locked_item(gate_item) self.own_itempool.append(gate_item) self.items_placed_early.append(random_early_item) @@ -223,19 +229,19 @@ class WitnessWorld(World): break region, loc = extra_checks.pop(0) - self.locat.add_location_late(loc) - self.multiworld.get_region(region, self.player).add_locations({loc: self.location_name_to_id[loc]}) + self.player_locations.add_location_late(loc) + self.get_region(region).add_locations({loc: self.location_name_to_id[loc]}) player = self.multiworld.get_player_name(self.player) - + warning(f"""Location "{loc}" had to be added to {player}'s world due to insufficient sphere 1 size.""") - def create_items(self): + def create_items(self) -> None: # Determine pool size. - pool_size: int = len(self.locat.CHECK_LOCATION_TABLE) - len(self.locat.EVENT_LOCATION_TABLE) + pool_size = len(self.player_locations.CHECK_LOCATION_TABLE) - len(self.player_locations.EVENT_LOCATION_TABLE) # Fill mandatory items and remove precollected and/or starting items from the pool. - item_pool: Dict[str, int] = self.items.get_mandatory_items() + item_pool = self.player_items.get_mandatory_items() # Remove one copy of each item that was placed early for already_placed in self.items_placed_early: @@ -283,7 +289,7 @@ class WitnessWorld(World): # Add junk items. if remaining_item_slots > 0: - item_pool.update(self.items.get_filler_items(remaining_item_slots)) + item_pool.update(self.player_items.get_filler_items(remaining_item_slots)) # Generate the actual items. for item_name, quantity in sorted(item_pool.items()): @@ -291,19 +297,22 @@ class WitnessWorld(World): self.own_itempool += new_items self.multiworld.itempool += new_items - if self.items.item_data[item_name].local_only: + if self.player_items.item_data[item_name].local_only: self.options.local_items.value.add(item_name) def fill_slot_data(self) -> dict: + self.log_ids_to_hints: Dict[int, CompactItemData] = dict() + self.laser_ids_to_hints: Dict[int, CompactItemData] = dict() + already_hinted_locations = set() # Laser hints if self.options.laser_hints: - laser_hints = make_laser_hints(self, StaticWitnessItems.item_groups["Lasers"]) + laser_hints = make_laser_hints(self, static_witness_items.ITEM_GROUPS["Lasers"]) for item_name, hint in laser_hints.items(): - item_def = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name]) + item_def = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name]) self.laser_ids_to_hints[int(item_def.panel_id_hexes[0], 16)] = make_compact_hint_data(hint, self.player) already_hinted_locations.add(hint.location) @@ -356,18 +365,18 @@ class WitnessWorld(World): return slot_data - def create_item(self, item_name: str) -> Item: + def create_item(self, item_name: str) -> WitnessItem: # If the player's plando options are malformed, the item_name parameter could be a dictionary containing the # name of the item, rather than the item itself. This is a workaround to prevent a crash. - if type(item_name) is dict: - item_name = list(item_name.keys())[0] + if isinstance(item_name, dict): + item_name = next(iter(item_name)) # this conditional is purely for unit tests, which need to be able to create an item before generate_early item_data: ItemData - if hasattr(self, 'items') and self.items and item_name in self.items.item_data: - item_data = self.items.item_data[item_name] + if hasattr(self, "player_items") and self.player_items and item_name in self.player_items.item_data: + item_data = self.player_items.item_data[item_name] else: - item_data = StaticWitnessItems.item_data[item_name] + item_data = static_witness_items.ITEM_DATA[item_name] return WitnessItem(item_name, item_data.classification, item_data.ap_code, player=self.player) @@ -382,12 +391,13 @@ class WitnessLocation(Location): game: str = "The Witness" entity_hex: int = -1 - def __init__(self, player: int, name: str, address: Optional[int], parent, ch_hex: int = -1): + def __init__(self, player: int, name: str, address: Optional[int], parent, ch_hex: int = -1) -> None: super().__init__(player, name, address, parent) self.entity_hex = ch_hex -def create_region(world: WitnessWorld, name: str, locat: WitnessPlayerLocations, region_locations=None, exits=None): +def create_region(world: WitnessWorld, name: str, player_locations: WitnessPlayerLocations, + region_locations=None, exits=None) -> Region: """ Create an Archipelago Region for The Witness """ @@ -395,12 +405,12 @@ def create_region(world: WitnessWorld, name: str, locat: WitnessPlayerLocations, ret = Region(name, world.player, world.multiworld) if region_locations: for location in region_locations: - loc_id = locat.CHECK_LOCATION_TABLE[location] + loc_id = player_locations.CHECK_LOCATION_TABLE[location] entity_hex = -1 - if location in StaticWitnessLogic.ENTITIES_BY_NAME: + if location in static_witness_logic.ENTITIES_BY_NAME: entity_hex = int( - StaticWitnessLogic.ENTITIES_BY_NAME[location]["entity_hex"], 0 + static_witness_logic.ENTITIES_BY_NAME[location]["entity_hex"], 0 ) location = WitnessLocation( world.player, location, loc_id, ret, entity_hex diff --git a/worlds/witness/WitnessItems.txt b/worlds/witness/data/WitnessItems.txt similarity index 100% rename from worlds/witness/WitnessItems.txt rename to worlds/witness/data/WitnessItems.txt diff --git a/worlds/witness/WitnessLogic.txt b/worlds/witness/data/WitnessLogic.txt similarity index 100% rename from worlds/witness/WitnessLogic.txt rename to worlds/witness/data/WitnessLogic.txt diff --git a/worlds/witness/WitnessLogicExpert.txt b/worlds/witness/data/WitnessLogicExpert.txt similarity index 100% rename from worlds/witness/WitnessLogicExpert.txt rename to worlds/witness/data/WitnessLogicExpert.txt diff --git a/worlds/witness/WitnessLogicVanilla.txt b/worlds/witness/data/WitnessLogicVanilla.txt similarity index 100% rename from worlds/witness/WitnessLogicVanilla.txt rename to worlds/witness/data/WitnessLogicVanilla.txt diff --git a/worlds/witness/data/__init__.py b/worlds/witness/data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/worlds/witness/data/item_definition_classes.py b/worlds/witness/data/item_definition_classes.py new file mode 100644 index 00000000..b095a83a --- /dev/null +++ b/worlds/witness/data/item_definition_classes.py @@ -0,0 +1,59 @@ +from dataclasses import dataclass +from enum import Enum +from typing import Dict, List, Optional + +from BaseClasses import ItemClassification + + +class ItemCategory(Enum): + SYMBOL = 0 + DOOR = 1 + LASER = 2 + USEFUL = 3 + FILLER = 4 + TRAP = 5 + JOKE = 6 + EVENT = 7 + + +CATEGORY_NAME_MAPPINGS: Dict[str, ItemCategory] = { + "Symbols:": ItemCategory.SYMBOL, + "Doors:": ItemCategory.DOOR, + "Lasers:": ItemCategory.LASER, + "Useful:": ItemCategory.USEFUL, + "Filler:": ItemCategory.FILLER, + "Traps:": ItemCategory.TRAP, + "Jokes:": ItemCategory.JOKE +} + + +@dataclass(frozen=True) +class ItemDefinition: + local_code: int + category: ItemCategory + + +@dataclass(frozen=True) +class ProgressiveItemDefinition(ItemDefinition): + child_item_names: List[str] + + +@dataclass(frozen=True) +class DoorItemDefinition(ItemDefinition): + panel_id_hexes: List[str] + + +@dataclass(frozen=True) +class WeightedItemDefinition(ItemDefinition): + weight: int + + +@dataclass() +class ItemData: + """ + ItemData for an item in The Witness + """ + ap_code: Optional[int] + definition: ItemDefinition + classification: ItemClassification + local_only: bool = False diff --git a/worlds/witness/settings/Audio_Logs.txt b/worlds/witness/data/settings/Audio_Logs.txt similarity index 100% rename from worlds/witness/settings/Audio_Logs.txt rename to worlds/witness/data/settings/Audio_Logs.txt diff --git a/worlds/witness/settings/Door_Shuffle/Boat.txt b/worlds/witness/data/settings/Door_Shuffle/Boat.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Boat.txt rename to worlds/witness/data/settings/Door_Shuffle/Boat.txt diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Additional_Panels.txt b/worlds/witness/data/settings/Door_Shuffle/Complex_Additional_Panels.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Complex_Additional_Panels.txt rename to worlds/witness/data/settings/Door_Shuffle/Complex_Additional_Panels.txt diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt b/worlds/witness/data/settings/Door_Shuffle/Complex_Door_Panels.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt rename to worlds/witness/data/settings/Door_Shuffle/Complex_Door_Panels.txt diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt b/worlds/witness/data/settings/Door_Shuffle/Complex_Doors.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Complex_Doors.txt rename to worlds/witness/data/settings/Door_Shuffle/Complex_Doors.txt diff --git a/worlds/witness/settings/Door_Shuffle/Elevators_Come_To_You.txt b/worlds/witness/data/settings/Door_Shuffle/Elevators_Come_To_You.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Elevators_Come_To_You.txt rename to worlds/witness/data/settings/Door_Shuffle/Elevators_Come_To_You.txt diff --git a/worlds/witness/settings/Door_Shuffle/Obelisk_Keys.txt b/worlds/witness/data/settings/Door_Shuffle/Obelisk_Keys.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Obelisk_Keys.txt rename to worlds/witness/data/settings/Door_Shuffle/Obelisk_Keys.txt diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Additional_Panels.txt b/worlds/witness/data/settings/Door_Shuffle/Simple_Additional_Panels.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Simple_Additional_Panels.txt rename to worlds/witness/data/settings/Door_Shuffle/Simple_Additional_Panels.txt diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt b/worlds/witness/data/settings/Door_Shuffle/Simple_Doors.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Simple_Doors.txt rename to worlds/witness/data/settings/Door_Shuffle/Simple_Doors.txt diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt b/worlds/witness/data/settings/Door_Shuffle/Simple_Panels.txt similarity index 100% rename from worlds/witness/settings/Door_Shuffle/Simple_Panels.txt rename to worlds/witness/data/settings/Door_Shuffle/Simple_Panels.txt diff --git a/worlds/witness/settings/EP_Shuffle/EP_All.txt b/worlds/witness/data/settings/EP_Shuffle/EP_All.txt similarity index 100% rename from worlds/witness/settings/EP_Shuffle/EP_All.txt rename to worlds/witness/data/settings/EP_Shuffle/EP_All.txt diff --git a/worlds/witness/settings/EP_Shuffle/EP_Easy.txt b/worlds/witness/data/settings/EP_Shuffle/EP_Easy.txt similarity index 100% rename from worlds/witness/settings/EP_Shuffle/EP_Easy.txt rename to worlds/witness/data/settings/EP_Shuffle/EP_Easy.txt diff --git a/worlds/witness/settings/EP_Shuffle/EP_NoEclipse.txt b/worlds/witness/data/settings/EP_Shuffle/EP_NoEclipse.txt similarity index 100% rename from worlds/witness/settings/EP_Shuffle/EP_NoEclipse.txt rename to worlds/witness/data/settings/EP_Shuffle/EP_NoEclipse.txt diff --git a/worlds/witness/settings/EP_Shuffle/EP_Sides.txt b/worlds/witness/data/settings/EP_Shuffle/EP_Sides.txt similarity index 100% rename from worlds/witness/settings/EP_Shuffle/EP_Sides.txt rename to worlds/witness/data/settings/EP_Shuffle/EP_Sides.txt diff --git a/worlds/witness/settings/Early_Caves.txt b/worlds/witness/data/settings/Early_Caves.txt similarity index 100% rename from worlds/witness/settings/Early_Caves.txt rename to worlds/witness/data/settings/Early_Caves.txt diff --git a/worlds/witness/settings/Early_Caves_Start.txt b/worlds/witness/data/settings/Early_Caves_Start.txt similarity index 100% rename from worlds/witness/settings/Early_Caves_Start.txt rename to worlds/witness/data/settings/Early_Caves_Start.txt diff --git a/worlds/witness/settings/Exclusions/Disable_Unrandomized.txt b/worlds/witness/data/settings/Exclusions/Disable_Unrandomized.txt similarity index 100% rename from worlds/witness/settings/Exclusions/Disable_Unrandomized.txt rename to worlds/witness/data/settings/Exclusions/Disable_Unrandomized.txt diff --git a/worlds/witness/settings/Exclusions/Discards.txt b/worlds/witness/data/settings/Exclusions/Discards.txt similarity index 100% rename from worlds/witness/settings/Exclusions/Discards.txt rename to worlds/witness/data/settings/Exclusions/Discards.txt diff --git a/worlds/witness/settings/Exclusions/Vaults.txt b/worlds/witness/data/settings/Exclusions/Vaults.txt similarity index 100% rename from worlds/witness/settings/Exclusions/Vaults.txt rename to worlds/witness/data/settings/Exclusions/Vaults.txt diff --git a/worlds/witness/settings/Laser_Shuffle.txt b/worlds/witness/data/settings/Laser_Shuffle.txt similarity index 100% rename from worlds/witness/settings/Laser_Shuffle.txt rename to worlds/witness/data/settings/Laser_Shuffle.txt diff --git a/worlds/witness/settings/Postgame/Beyond_Challenge.txt b/worlds/witness/data/settings/Postgame/Beyond_Challenge.txt similarity index 100% rename from worlds/witness/settings/Postgame/Beyond_Challenge.txt rename to worlds/witness/data/settings/Postgame/Beyond_Challenge.txt diff --git a/worlds/witness/settings/Postgame/Bottom_Floor_Discard.txt b/worlds/witness/data/settings/Postgame/Bottom_Floor_Discard.txt similarity index 100% rename from worlds/witness/settings/Postgame/Bottom_Floor_Discard.txt rename to worlds/witness/data/settings/Postgame/Bottom_Floor_Discard.txt diff --git a/worlds/witness/settings/Postgame/Bottom_Floor_Discard_NonDoors.txt b/worlds/witness/data/settings/Postgame/Bottom_Floor_Discard_NonDoors.txt similarity index 100% rename from worlds/witness/settings/Postgame/Bottom_Floor_Discard_NonDoors.txt rename to worlds/witness/data/settings/Postgame/Bottom_Floor_Discard_NonDoors.txt diff --git a/worlds/witness/settings/Postgame/Caves.txt b/worlds/witness/data/settings/Postgame/Caves.txt similarity index 100% rename from worlds/witness/settings/Postgame/Caves.txt rename to worlds/witness/data/settings/Postgame/Caves.txt diff --git a/worlds/witness/settings/Postgame/Challenge_Vault_Box.txt b/worlds/witness/data/settings/Postgame/Challenge_Vault_Box.txt similarity index 100% rename from worlds/witness/settings/Postgame/Challenge_Vault_Box.txt rename to worlds/witness/data/settings/Postgame/Challenge_Vault_Box.txt diff --git a/worlds/witness/settings/Postgame/Mountain_Lower.txt b/worlds/witness/data/settings/Postgame/Mountain_Lower.txt similarity index 100% rename from worlds/witness/settings/Postgame/Mountain_Lower.txt rename to worlds/witness/data/settings/Postgame/Mountain_Lower.txt diff --git a/worlds/witness/settings/Postgame/Mountain_Upper.txt b/worlds/witness/data/settings/Postgame/Mountain_Upper.txt similarity index 100% rename from worlds/witness/settings/Postgame/Mountain_Upper.txt rename to worlds/witness/data/settings/Postgame/Mountain_Upper.txt diff --git a/worlds/witness/settings/Postgame/Path_To_Challenge.txt b/worlds/witness/data/settings/Postgame/Path_To_Challenge.txt similarity index 100% rename from worlds/witness/settings/Postgame/Path_To_Challenge.txt rename to worlds/witness/data/settings/Postgame/Path_To_Challenge.txt diff --git a/worlds/witness/settings/Symbol_Shuffle.txt b/worlds/witness/data/settings/Symbol_Shuffle.txt similarity index 100% rename from worlds/witness/settings/Symbol_Shuffle.txt rename to worlds/witness/data/settings/Symbol_Shuffle.txt diff --git a/worlds/witness/data/static_items.py b/worlds/witness/data/static_items.py new file mode 100644 index 00000000..8eb889f8 --- /dev/null +++ b/worlds/witness/data/static_items.py @@ -0,0 +1,56 @@ +from typing import Dict, List + +from BaseClasses import ItemClassification + +from . import static_logic as static_witness_logic +from .item_definition_classes import DoorItemDefinition, ItemCategory, ItemData +from .static_locations import ID_START + +ITEM_DATA: Dict[str, ItemData] = {} +ITEM_GROUPS: Dict[str, List[str]] = {} + +# Useful items that are treated specially at generation time and should not be automatically added to the player's +# item list during get_progression_items. +_special_usefuls: List[str] = ["Puzzle Skip"] + + +def populate_items() -> None: + for item_name, definition in static_witness_logic.ALL_ITEMS.items(): + ap_item_code = definition.local_code + ID_START + classification: ItemClassification = ItemClassification.filler + local_only: bool = False + + if definition.category is ItemCategory.SYMBOL: + classification = ItemClassification.progression + ITEM_GROUPS.setdefault("Symbols", []).append(item_name) + elif definition.category is ItemCategory.DOOR: + classification = ItemClassification.progression + ITEM_GROUPS.setdefault("Doors", []).append(item_name) + elif definition.category is ItemCategory.LASER: + classification = ItemClassification.progression_skip_balancing + ITEM_GROUPS.setdefault("Lasers", []).append(item_name) + elif definition.category is ItemCategory.USEFUL: + classification = ItemClassification.useful + elif definition.category is ItemCategory.FILLER: + if item_name in ["Energy Fill (Small)"]: + local_only = True + classification = ItemClassification.filler + elif definition.category is ItemCategory.TRAP: + classification = ItemClassification.trap + elif definition.category is ItemCategory.JOKE: + classification = ItemClassification.filler + + ITEM_DATA[item_name] = ItemData(ap_item_code, definition, + classification, local_only) + + +def get_item_to_door_mappings() -> Dict[int, List[int]]: + output: Dict[int, List[int]] = {} + for item_name, item_data in ITEM_DATA.items(): + if not isinstance(item_data.definition, DoorItemDefinition): + continue + output[item_data.ap_code] = [int(hex_string, 16) for hex_string in item_data.definition.panel_id_hexes] + return output + + +populate_items() diff --git a/worlds/witness/data/static_locations.py b/worlds/witness/data/static_locations.py new file mode 100644 index 00000000..e1154423 --- /dev/null +++ b/worlds/witness/data/static_locations.py @@ -0,0 +1,482 @@ +from . import static_logic as static_witness_logic + +ID_START = 158000 + +GENERAL_LOCATIONS = { + "Tutorial Front Left", + "Tutorial Back Left", + "Tutorial Back Right", + "Tutorial Patio Floor", + "Tutorial Gate Open", + + "Outside Tutorial Vault Box", + "Outside Tutorial Discard", + "Outside Tutorial Shed Row 5", + "Outside Tutorial Tree Row 9", + "Outside Tutorial Outpost Entry Panel", + "Outside Tutorial Outpost Exit Panel", + + "Glass Factory Discard", + "Glass Factory Back Wall 5", + "Glass Factory Front 3", + "Glass Factory Melting 3", + + "Symmetry Island Lower Panel", + "Symmetry Island Right 5", + "Symmetry Island Back 6", + "Symmetry Island Left 7", + "Symmetry Island Upper Panel", + "Symmetry Island Scenery Outlines 5", + "Symmetry Island Laser Yellow 3", + "Symmetry Island Laser Blue 3", + "Symmetry Island Laser Panel", + + "Orchard Apple Tree 5", + + "Desert Vault Box", + "Desert Discard", + "Desert Surface 8", + "Desert Light Room 3", + "Desert Pond Room 5", + "Desert Flood Room 6", + "Desert Elevator Room Hexagonal", + "Desert Elevator Room Bent 3", + "Desert Laser Panel", + + "Quarry Entry 1 Panel", + "Quarry Entry 2 Panel", + "Quarry Stoneworks Entry Left Panel", + "Quarry Stoneworks Entry Right Panel", + "Quarry Stoneworks Lower Row 6", + "Quarry Stoneworks Upper Row 8", + "Quarry Stoneworks Control Room Left", + "Quarry Stoneworks Control Room Right", + "Quarry Stoneworks Stairs Panel", + "Quarry Boathouse Intro Right", + "Quarry Boathouse Intro Left", + "Quarry Boathouse Front Row 5", + "Quarry Boathouse Back First Row 9", + "Quarry Boathouse Back Second Row 3", + "Quarry Discard", + "Quarry Laser Panel", + + "Shadows Intro 8", + "Shadows Far 8", + "Shadows Near 5", + "Shadows Laser Panel", + + "Keep Hedge Maze 1", + "Keep Hedge Maze 2", + "Keep Hedge Maze 3", + "Keep Hedge Maze 4", + "Keep Pressure Plates 1", + "Keep Pressure Plates 2", + "Keep Pressure Plates 3", + "Keep Pressure Plates 4", + "Keep Discard", + "Keep Laser Panel Hedges", + "Keep Laser Panel Pressure Plates", + + "Shipwreck Vault Box", + "Shipwreck Discard", + + "Monastery Outside 3", + "Monastery Inside 4", + "Monastery Laser Panel", + + "Town Cargo Box Entry Panel", + "Town Cargo Box Discard", + "Town Tall Hexagonal", + "Town Church Entry Panel", + "Town Church Lattice", + "Town Maze Panel", + "Town Rooftop Discard", + "Town Red Rooftop 5", + "Town Wooden Roof Lower Row 5", + "Town Wooden Rooftop", + "Windmill Entry Panel", + "Town RGB House Entry Panel", + "Town Laser Panel", + + "Town RGB House Upstairs Left", + "Town RGB House Upstairs Right", + "Town RGB House Sound Room Right", + + "Windmill Theater Entry Panel", + "Theater Exit Left Panel", + "Theater Exit Right Panel", + "Theater Tutorial Video", + "Theater Desert Video", + "Theater Jungle Video", + "Theater Shipwreck Video", + "Theater Mountain Video", + "Theater Discard", + + "Jungle Discard", + "Jungle First Row 3", + "Jungle Second Row 4", + "Jungle Popup Wall 6", + "Jungle Laser Panel", + + "Jungle Vault Box", + "Jungle Monastery Garden Shortcut Panel", + + "Bunker Entry Panel", + "Bunker Intro Left 5", + "Bunker Intro Back 4", + "Bunker Glass Room 3", + "Bunker UV Room 2", + "Bunker Laser Panel", + + "Swamp Entry Panel", + "Swamp Intro Front 6", + "Swamp Intro Back 8", + "Swamp Between Bridges Near Row 4", + "Swamp Cyan Underwater 5", + "Swamp Platform Row 4", + "Swamp Platform Shortcut Right Panel", + "Swamp Between Bridges Far Row 4", + "Swamp Red Underwater 4", + "Swamp Purple Underwater", + "Swamp Beyond Rotating Bridge 4", + "Swamp Blue Underwater 5", + "Swamp Laser Panel", + "Swamp Laser Shortcut Right Panel", + + "Treehouse First Door Panel", + "Treehouse Second Door Panel", + "Treehouse Third Door Panel", + "Treehouse Yellow Bridge 9", + "Treehouse First Purple Bridge 5", + "Treehouse Second Purple Bridge 7", + "Treehouse Green Bridge 7", + "Treehouse Green Bridge Discard", + "Treehouse Left Orange Bridge 15", + "Treehouse Laser Discard", + "Treehouse Right Orange Bridge 12", + "Treehouse Laser Panel", + "Treehouse Drawbridge Panel", + + "Mountainside Discard", + "Mountainside Vault Box", + "Mountaintop River Shape", + + "Tutorial First Hallway EP", + "Tutorial Cloud EP", + "Tutorial Patio Flowers EP", + "Tutorial Gate EP", + "Outside Tutorial Garden EP", + "Outside Tutorial Town Sewer EP", + "Outside Tutorial Path EP", + "Outside Tutorial Tractor EP", + "Mountainside Thundercloud EP", + "Glass Factory Vase EP", + "Symmetry Island Glass Factory Black Line Reflection EP", + "Symmetry Island Glass Factory Black Line EP", + "Desert Sand Snake EP", + "Desert Facade Right EP", + "Desert Facade Left EP", + "Desert Stairs Left EP", + "Desert Stairs Right EP", + "Desert Broken Wall Straight EP", + "Desert Broken Wall Bend EP", + "Desert Shore EP", + "Desert Island EP", + "Desert Pond Room Near Reflection EP", + "Desert Pond Room Far Reflection EP", + "Desert Flood Room EP", + "Desert Elevator EP", + "Quarry Shore EP", + "Quarry Entrance Pipe EP", + "Quarry Sand Pile EP", + "Quarry Rock Line EP", + "Quarry Rock Line Reflection EP", + "Quarry Railroad EP", + "Quarry Stoneworks Ramp EP", + "Quarry Stoneworks Lift EP", + "Quarry Boathouse Moving Ramp EP", + "Quarry Boathouse Hook EP", + "Shadows Quarry Stoneworks Rooftop Vent EP", + "Treehouse Beach Rock Shadow EP", + "Treehouse Beach Sand Shadow EP", + "Treehouse Beach Both Orange Bridges EP", + "Keep Red Flowers EP", + "Keep Purple Flowers EP", + "Shipwreck Circle Near EP", + "Shipwreck Circle Left EP", + "Shipwreck Circle Far EP", + "Shipwreck Stern EP", + "Shipwreck Rope Inner EP", + "Shipwreck Rope Outer EP", + "Shipwreck Couch EP", + "Keep Pressure Plates 1 EP", + "Keep Pressure Plates 2 EP", + "Keep Pressure Plates 3 EP", + "Keep Pressure Plates 4 Left Exit EP", + "Keep Pressure Plates 4 Right Exit EP", + "Keep Path EP", + "Keep Hedges EP", + "Monastery Facade Left Near EP", + "Monastery Facade Left Far Short EP", + "Monastery Facade Left Far Long EP", + "Monastery Facade Right Near EP", + "Monastery Facade Left Stairs EP", + "Monastery Facade Right Stairs EP", + "Monastery Grass Stairs EP", + "Monastery Left Shutter EP", + "Monastery Middle Shutter EP", + "Monastery Right Shutter EP", + "Windmill First Blade EP", + "Windmill Second Blade EP", + "Windmill Third Blade EP", + "Town Tower Underside Third EP", + "Town Tower Underside Fourth EP", + "Town Tower Underside First EP", + "Town Tower Underside Second EP", + "Town RGB House Red EP", + "Town RGB House Green EP", + "Town Maze Bridge Underside EP", + "Town Black Line Redirect EP", + "Town Black Line Church EP", + "Town Brown Bridge EP", + "Town Black Line Tower EP", + "Theater Eclipse EP", + "Theater Window EP", + "Theater Door EP", + "Theater Church EP", + "Jungle Long Arch Moss EP", + "Jungle Straight Left Moss EP", + "Jungle Pop-up Wall Moss EP", + "Jungle Short Arch Moss EP", + "Jungle Entrance EP", + "Jungle Tree Halo EP", + "Jungle Bamboo CCW EP", + "Jungle Bamboo CW EP", + "Jungle Green Leaf Moss EP", + "Monastery Garden Left EP", + "Monastery Garden Right EP", + "Monastery Wall EP", + "Bunker Tinted Door EP", + "Bunker Green Room Flowers EP", + "Swamp Purple Sand Middle EP", + "Swamp Purple Sand Top EP", + "Swamp Purple Sand Bottom EP", + "Swamp Sliding Bridge Left EP", + "Swamp Sliding Bridge Right EP", + "Swamp Cyan Underwater Sliding Bridge EP", + "Swamp Rotating Bridge CCW EP", + "Swamp Rotating Bridge CW EP", + "Swamp Boat EP", + "Swamp Long Bridge Side EP", + "Swamp Purple Underwater Right EP", + "Swamp Purple Underwater Left EP", + "Treehouse Buoy EP", + "Treehouse Right Orange Bridge EP", + "Treehouse Burned House Beach EP", + "Mountainside Cloud Cycle EP", + "Mountainside Bush EP", + "Mountainside Apparent River EP", + "Mountaintop River Shape EP", + "Mountaintop Arch Black EP", + "Mountaintop Arch White Right EP", + "Mountaintop Arch White Left EP", + "Mountain Bottom Floor Yellow Bridge EP", + "Mountain Bottom Floor Blue Bridge EP", + "Mountain Floor 2 Pink Bridge EP", + "Caves Skylight EP", + "Challenge Water EP", + "Tunnels Theater Flowers EP", + "Boat Desert EP", + "Boat Shipwreck CCW Underside EP", + "Boat Shipwreck Green EP", + "Boat Shipwreck CW Underside EP", + "Boat Bunker Yellow Line EP", + "Boat Town Long Sewer EP", + "Boat Tutorial EP", + "Boat Tutorial Reflection EP", + "Boat Tutorial Moss EP", + "Boat Cargo Box EP", + + "Desert Obelisk Side 1", + "Desert Obelisk Side 2", + "Desert Obelisk Side 3", + "Desert Obelisk Side 4", + "Desert Obelisk Side 5", + "Monastery Obelisk Side 1", + "Monastery Obelisk Side 2", + "Monastery Obelisk Side 3", + "Monastery Obelisk Side 4", + "Monastery Obelisk Side 5", + "Monastery Obelisk Side 6", + "Treehouse Obelisk Side 1", + "Treehouse Obelisk Side 2", + "Treehouse Obelisk Side 3", + "Treehouse Obelisk Side 4", + "Treehouse Obelisk Side 5", + "Treehouse Obelisk Side 6", + "Mountainside Obelisk Side 1", + "Mountainside Obelisk Side 2", + "Mountainside Obelisk Side 3", + "Mountainside Obelisk Side 4", + "Mountainside Obelisk Side 5", + "Mountainside Obelisk Side 6", + "Quarry Obelisk Side 1", + "Quarry Obelisk Side 2", + "Quarry Obelisk Side 3", + "Quarry Obelisk Side 4", + "Quarry Obelisk Side 5", + "Town Obelisk Side 1", + "Town Obelisk Side 2", + "Town Obelisk Side 3", + "Town Obelisk Side 4", + "Town Obelisk Side 5", + "Town Obelisk Side 6", + + "Caves Mountain Shortcut Panel", + "Caves Swamp Shortcut Panel", + + "Caves Blue Tunnel Right First 4", + "Caves Blue Tunnel Left First 1", + "Caves Blue Tunnel Left Second 5", + "Caves Blue Tunnel Right Second 5", + "Caves Blue Tunnel Right Third 1", + "Caves Blue Tunnel Left Fourth 1", + "Caves Blue Tunnel Left Third 1", + + "Caves First Floor Middle", + "Caves First Floor Right", + "Caves First Floor Left", + "Caves First Floor Grounded", + "Caves Lone Pillar", + "Caves First Wooden Beam", + "Caves Second Wooden Beam", + "Caves Third Wooden Beam", + "Caves Fourth Wooden Beam", + "Caves Right Upstairs Left Row 8", + "Caves Right Upstairs Right Row 3", + "Caves Left Upstairs Single", + "Caves Left Upstairs Left Row 5", + + "Caves Challenge Entry Panel", + "Challenge Tunnels Entry Panel", + + "Tunnels Vault Box", + "Theater Challenge Video", + + "Tunnels Town Shortcut Panel", + + "Caves Skylight EP", + "Challenge Water EP", + "Tunnels Theater Flowers EP", + "Tutorial Gate EP", + + "Mountaintop Mountain Entry Panel", + + "Mountain Floor 1 Light Bridge Controller", + + "Mountain Floor 1 Right Row 5", + "Mountain Floor 1 Left Row 7", + "Mountain Floor 1 Back Row 3", + "Mountain Floor 1 Trash Pillar 2", + "Mountain Floor 2 Near Row 5", + "Mountain Floor 2 Far Row 6", + + "Mountain Floor 2 Light Bridge Controller Near", + "Mountain Floor 2 Light Bridge Controller Far", + + "Mountain Bottom Floor Yellow Bridge EP", + "Mountain Bottom Floor Blue Bridge EP", + "Mountain Floor 2 Pink Bridge EP", + + "Mountain Floor 2 Elevator Discard", + "Mountain Bottom Floor Giant Puzzle", + + "Mountain Bottom Floor Pillars Room Entry Left", + "Mountain Bottom Floor Pillars Room Entry Right", + + "Mountain Bottom Floor Caves Entry Panel", + + "Mountain Bottom Floor Left Pillar 4", + "Mountain Bottom Floor Right Pillar 4", + + "Challenge Vault Box", + "Theater Challenge Video", + "Mountain Bottom Floor Discard", +} + +OBELISK_SIDES = { + "Desert Obelisk Side 1", + "Desert Obelisk Side 2", + "Desert Obelisk Side 3", + "Desert Obelisk Side 4", + "Desert Obelisk Side 5", + "Monastery Obelisk Side 1", + "Monastery Obelisk Side 2", + "Monastery Obelisk Side 3", + "Monastery Obelisk Side 4", + "Monastery Obelisk Side 5", + "Monastery Obelisk Side 6", + "Treehouse Obelisk Side 1", + "Treehouse Obelisk Side 2", + "Treehouse Obelisk Side 3", + "Treehouse Obelisk Side 4", + "Treehouse Obelisk Side 5", + "Treehouse Obelisk Side 6", + "Mountainside Obelisk Side 1", + "Mountainside Obelisk Side 2", + "Mountainside Obelisk Side 3", + "Mountainside Obelisk Side 4", + "Mountainside Obelisk Side 5", + "Mountainside Obelisk Side 6", + "Quarry Obelisk Side 1", + "Quarry Obelisk Side 2", + "Quarry Obelisk Side 3", + "Quarry Obelisk Side 4", + "Quarry Obelisk Side 5", + "Town Obelisk Side 1", + "Town Obelisk Side 2", + "Town Obelisk Side 3", + "Town Obelisk Side 4", + "Town Obelisk Side 5", + "Town Obelisk Side 6", +} + +ALL_LOCATIONS_TO_ID = dict() + +AREA_LOCATION_GROUPS = dict() + + +def get_id(entity_hex: str) -> str: + """ + Calculates the location ID for any given location + """ + + return static_witness_logic.ENTITIES_BY_HEX[entity_hex]["id"] + + +def get_event_name(entity_hex: str) -> str: + """ + Returns the event name of any given panel. + """ + + action = " Opened" if static_witness_logic.ENTITIES_BY_HEX[entity_hex]["entityType"] == "Door" else " Solved" + + return static_witness_logic.ENTITIES_BY_HEX[entity_hex]["checkName"] + action + + +ALL_LOCATIONS_TO_IDS = { + panel_obj["checkName"]: get_id(chex) + for chex, panel_obj in static_witness_logic.ENTITIES_BY_HEX.items() + if panel_obj["id"] +} + +ALL_LOCATIONS_TO_IDS = dict( + sorted(ALL_LOCATIONS_TO_IDS.items(), key=lambda loc: loc[1]) +) + +for key, item in ALL_LOCATIONS_TO_IDS.items(): + ALL_LOCATIONS_TO_ID[key] = item + +for loc in ALL_LOCATIONS_TO_IDS: + area = static_witness_logic.ENTITIES_BY_NAME[loc]["area"]["name"] + AREA_LOCATION_GROUPS.setdefault(area, []).append(loc) diff --git a/worlds/witness/static_logic.py b/worlds/witness/data/static_logic.py similarity index 51% rename from worlds/witness/static_logic.py rename to worlds/witness/data/static_logic.py index 3efab491..94e6f7a3 100644 --- a/worlds/witness/static_logic.py +++ b/worlds/witness/data/static_logic.py @@ -1,56 +1,26 @@ -from dataclasses import dataclass -from enum import Enum +from functools import lru_cache from typing import Dict, List -from .utils import define_new_region, parse_lambda, lazy, get_items, get_sigma_normal_logic, get_sigma_expert_logic,\ - get_vanilla_logic - - -class ItemCategory(Enum): - SYMBOL = 0 - DOOR = 1 - LASER = 2 - USEFUL = 3 - FILLER = 4 - TRAP = 5 - JOKE = 6 - EVENT = 7 - - -CATEGORY_NAME_MAPPINGS: Dict[str, ItemCategory] = { - "Symbols:": ItemCategory.SYMBOL, - "Doors:": ItemCategory.DOOR, - "Lasers:": ItemCategory.LASER, - "Useful:": ItemCategory.USEFUL, - "Filler:": ItemCategory.FILLER, - "Traps:": ItemCategory.TRAP, - "Jokes:": ItemCategory.JOKE -} - - -@dataclass(frozen=True) -class ItemDefinition: - local_code: int - category: ItemCategory - - -@dataclass(frozen=True) -class ProgressiveItemDefinition(ItemDefinition): - child_item_names: List[str] - - -@dataclass(frozen=True) -class DoorItemDefinition(ItemDefinition): - panel_id_hexes: List[str] - - -@dataclass(frozen=True) -class WeightedItemDefinition(ItemDefinition): - weight: int +from .item_definition_classes import ( + CATEGORY_NAME_MAPPINGS, + DoorItemDefinition, + ItemCategory, + ItemDefinition, + ProgressiveItemDefinition, + WeightedItemDefinition, +) +from .utils import ( + define_new_region, + get_items, + get_sigma_expert_logic, + get_sigma_normal_logic, + get_vanilla_logic, + parse_lambda, +) class StaticWitnessLogicObj: - def read_logic_file(self, lines): + def read_logic_file(self, lines) -> None: """ Reads the logic file and does the initial population of data structures """ @@ -152,7 +122,7 @@ class StaticWitnessLogicObj: } if location_type == "Obelisk Side": - eps = set(list(required_panels)[0]) + eps = set(next(iter(required_panels))) eps -= {"Theater to Tunnels"} eps_ints = {int(h, 16) for h in eps} @@ -177,7 +147,7 @@ class StaticWitnessLogicObj: current_region["panels"].append(entity_hex) - def __init__(self, lines=None): + def __init__(self, lines=None) -> None: if lines is None: lines = get_sigma_normal_logic() @@ -199,102 +169,95 @@ class StaticWitnessLogicObj: self.read_logic_file(lines) -class StaticWitnessLogic: - # Item data parsed from WitnessItems.txt - all_items: Dict[str, ItemDefinition] = {} - _progressive_lookup: Dict[str, str] = {} - - ALL_REGIONS_BY_NAME = dict() - ALL_AREAS_BY_NAME = dict() - STATIC_CONNECTIONS_BY_REGION_NAME = dict() - - OBELISK_SIDE_ID_TO_EP_HEXES = dict() - - ENTITIES_BY_HEX = dict() - ENTITIES_BY_NAME = dict() - STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict() - - EP_TO_OBELISK_SIDE = dict() - - ENTITY_ID_TO_NAME = dict() - - @staticmethod - def parse_items(): - """ - Parses currently defined items from WitnessItems.txt - """ - - lines: List[str] = get_items() - current_category: ItemCategory = ItemCategory.SYMBOL - - for line in lines: - # Skip empty lines and comments. - if line == "" or line[0] == "#": - continue - - # If this line is a category header, update our cached category. - if line in CATEGORY_NAME_MAPPINGS.keys(): - current_category = CATEGORY_NAME_MAPPINGS[line] - continue - - line_split = line.split(" - ") - - item_code = int(line_split[0]) - item_name = line_split[1] - arguments: List[str] = line_split[2].split(",") if len(line_split) >= 3 else [] - - if current_category in [ItemCategory.DOOR, ItemCategory.LASER]: - # Map doors to IDs. - StaticWitnessLogic.all_items[item_name] = DoorItemDefinition(item_code, current_category, - arguments) - elif current_category == ItemCategory.TRAP or current_category == ItemCategory.FILLER: - # Read filler weights. - weight = int(arguments[0]) if len(arguments) >= 1 else 1 - StaticWitnessLogic.all_items[item_name] = WeightedItemDefinition(item_code, current_category, weight) - elif arguments: - # Progressive items. - StaticWitnessLogic.all_items[item_name] = ProgressiveItemDefinition(item_code, current_category, - arguments) - for child_item in arguments: - StaticWitnessLogic._progressive_lookup[child_item] = item_name - else: - StaticWitnessLogic.all_items[item_name] = ItemDefinition(item_code, current_category) - - @staticmethod - def get_parent_progressive_item(item_name: str): - """ - Returns the name of the item's progressive parent, if there is one, or the item's name if not. - """ - return StaticWitnessLogic._progressive_lookup.get(item_name, item_name) - - @lazy - def sigma_expert(self) -> StaticWitnessLogicObj: - return StaticWitnessLogicObj(get_sigma_expert_logic()) - - @lazy - def sigma_normal(self) -> StaticWitnessLogicObj: - return StaticWitnessLogicObj(get_sigma_normal_logic()) - - @lazy - def vanilla(self) -> StaticWitnessLogicObj: - return StaticWitnessLogicObj(get_vanilla_logic()) - - def __init__(self): - self.parse_items() - - self.ALL_REGIONS_BY_NAME.update(self.sigma_normal.ALL_REGIONS_BY_NAME) - self.ALL_AREAS_BY_NAME.update(self.sigma_normal.ALL_AREAS_BY_NAME) - self.STATIC_CONNECTIONS_BY_REGION_NAME.update(self.sigma_normal.STATIC_CONNECTIONS_BY_REGION_NAME) - - self.ENTITIES_BY_HEX.update(self.sigma_normal.ENTITIES_BY_HEX) - self.ENTITIES_BY_NAME.update(self.sigma_normal.ENTITIES_BY_NAME) - self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX.update(self.sigma_normal.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX) - - self.OBELISK_SIDE_ID_TO_EP_HEXES.update(self.sigma_normal.OBELISK_SIDE_ID_TO_EP_HEXES) - - self.EP_TO_OBELISK_SIDE.update(self.sigma_normal.EP_TO_OBELISK_SIDE) - - self.ENTITY_ID_TO_NAME.update(self.sigma_normal.ENTITY_ID_TO_NAME) +# Item data parsed from WitnessItems.txt +ALL_ITEMS: Dict[str, ItemDefinition] = {} +_progressive_lookup: Dict[str, str] = {} -StaticWitnessLogic() +def parse_items() -> None: + """ + Parses currently defined items from WitnessItems.txt + """ + + lines: List[str] = get_items() + current_category: ItemCategory = ItemCategory.SYMBOL + + for line in lines: + # Skip empty lines and comments. + if line == "" or line[0] == "#": + continue + + # If this line is a category header, update our cached category. + if line in CATEGORY_NAME_MAPPINGS.keys(): + current_category = CATEGORY_NAME_MAPPINGS[line] + continue + + line_split = line.split(" - ") + + item_code = int(line_split[0]) + item_name = line_split[1] + arguments: List[str] = line_split[2].split(",") if len(line_split) >= 3 else [] + + if current_category in [ItemCategory.DOOR, ItemCategory.LASER]: + # Map doors to IDs. + ALL_ITEMS[item_name] = DoorItemDefinition(item_code, current_category, arguments) + elif current_category == ItemCategory.TRAP or current_category == ItemCategory.FILLER: + # Read filler weights. + weight = int(arguments[0]) if len(arguments) >= 1 else 1 + ALL_ITEMS[item_name] = WeightedItemDefinition(item_code, current_category, weight) + elif arguments: + # Progressive items. + ALL_ITEMS[item_name] = ProgressiveItemDefinition(item_code, current_category, arguments) + for child_item in arguments: + _progressive_lookup[child_item] = item_name + else: + ALL_ITEMS[item_name] = ItemDefinition(item_code, current_category) + + +def get_parent_progressive_item(item_name: str) -> str: + """ + Returns the name of the item's progressive parent, if there is one, or the item's name if not. + """ + return _progressive_lookup.get(item_name, item_name) + + +@lru_cache +def get_vanilla() -> StaticWitnessLogicObj: + return StaticWitnessLogicObj(get_vanilla_logic()) + + +@lru_cache +def get_sigma_normal() -> StaticWitnessLogicObj: + return StaticWitnessLogicObj(get_sigma_normal_logic()) + + +@lru_cache +def get_sigma_expert() -> StaticWitnessLogicObj: + return StaticWitnessLogicObj(get_sigma_expert_logic()) + + +def __getattr__(name): + if name == "vanilla": + return get_vanilla() + elif name == "sigma_normal": + return get_sigma_normal() + elif name == "sigma_expert": + return get_sigma_expert() + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + + +parse_items() + +ALL_REGIONS_BY_NAME = get_sigma_normal().ALL_REGIONS_BY_NAME +ALL_AREAS_BY_NAME = get_sigma_normal().ALL_AREAS_BY_NAME +STATIC_CONNECTIONS_BY_REGION_NAME = get_sigma_normal().STATIC_CONNECTIONS_BY_REGION_NAME + +ENTITIES_BY_HEX = get_sigma_normal().ENTITIES_BY_HEX +ENTITIES_BY_NAME = get_sigma_normal().ENTITIES_BY_NAME +STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = get_sigma_normal().STATIC_DEPENDENT_REQUIREMENTS_BY_HEX + +OBELISK_SIDE_ID_TO_EP_HEXES = get_sigma_normal().OBELISK_SIDE_ID_TO_EP_HEXES + +EP_TO_OBELISK_SIDE = get_sigma_normal().EP_TO_OBELISK_SIDE + +ENTITY_ID_TO_NAME = get_sigma_normal().ENTITY_ID_TO_NAME diff --git a/worlds/witness/utils.py b/worlds/witness/data/utils.py similarity index 93% rename from worlds/witness/utils.py rename to worlds/witness/data/utils.py index 43e03947..bb89227c 100644 --- a/worlds/witness/utils.py +++ b/worlds/witness/data/utils.py @@ -1,11 +1,11 @@ from functools import lru_cache from math import floor -from typing import List, Collection, FrozenSet, Tuple, Dict, Any, Set from pkgutil import get_data from random import random +from typing import Any, Collection, Dict, FrozenSet, List, Set, Tuple -def weighted_sample(world_random: random, population: List, weights: List[float], k: int): +def weighted_sample(world_random: random, population: List, weights: List[float], k: int) -> List: positions = range(len(population)) indices = [] while True: @@ -95,25 +95,9 @@ def parse_lambda(lambda_string) -> FrozenSet[FrozenSet[str]]: return lambda_set -class lazy(object): - def __init__(self, func, name=None): - self.func = func - self.name = name if name is not None else func.__name__ - self.__doc__ = func.__doc__ - - def __get__(self, instance, class_): - if instance is None: - res = self.func(class_) - setattr(class_, self.name, res) - return res - res = self.func(instance) - setattr(instance, self.name, res) - return res - - @lru_cache(maxsize=None) def get_adjustment_file(adjustment_file: str) -> List[str]: - data = get_data(__name__, adjustment_file).decode('utf-8') + data = get_data(__name__, adjustment_file).decode("utf-8") return [line.strip() for line in data.split("\n")] diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py index 6ebf8eee..786b52d6 100644 --- a/worlds/witness/hints.py +++ b/worlds/witness/hints.py @@ -1,9 +1,11 @@ import logging from dataclasses import dataclass -from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional, Union -from BaseClasses import Item, ItemClassification, Location, LocationProgressType, CollectionState -from . import StaticWitnessLogic -from .utils import weighted_sample +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union + +from BaseClasses import CollectionState, Item, Location, LocationProgressType + +from .data import static_logic as static_witness_logic +from .data.utils import weighted_sample if TYPE_CHECKING: from . import WitnessWorld @@ -22,7 +24,7 @@ joke_hints = [ "Have you tried Clique?\nIt's certainly a lot less complicated than this game!", "Have you tried Dark Souls III?\nA tough game like this feels better when friends are helping you!", "Have you tried Donkey Kong Country 3?\nA legendary game from a golden age of platformers!", - "Have you tried DLC Quest?\nI know you all like parody games.\nI got way too many requests to make a randomizer for \"The Looker\".", + 'Have you tried DLC Quest?\nI know you all like parody games.\nI got way too many requests to make a randomizer for "The Looker".', "Have you tried Doom?\nI wonder if a smart fridge can connect to Archipelago.", "Have you tried Doom II?\nGot a good game on your hands? Just make it bigger and better.", "Have you tried Factorio?\nAlone in an unknown multiworld. Sound familiar?", @@ -62,9 +64,9 @@ joke_hints = [ "Have you tried Slay the Spire?\nExperience the thrill of combat without needing fast fingers!", "Have you tried Stardew Valley?\nThe Farming game that gave a damn. It's so easy to lose hours and days to it...", "Have you tried Subnautica?\nIf you like this game's lonely atmosphere, I would suggest you try it.", - "Have you tried Terraria?\nA prime example of a survival sandbox game that beats the \"Wide as an ocean, deep as a puddle\" allegations.", + 'Have you tried Terraria?\nA prime example of a survival sandbox game that beats the "Wide as an ocean, deep as a puddle" allegations.', "Have you tried Timespinner?\nEveryone who plays it ends up loving it!", - "Have you tried The Legend of Zelda?\nIn some sense, it was the starting point of \"adventure\" in video games.", + 'Have you tried The Legend of Zelda?\nIn some sense, it was the starting point of "adventure" in video games.', "Have you tried TUNC?\nWhat? No, I'm pretty sure I spelled that right.", "Have you tried TUNIC?\nRemember what discovering your first Environmental Puzzle was like?\nTUNIC will make you feel like that at least 5 times over.", "Have you tried Undertale?\nI hope I'm not the 10th person to ask you that. But it's, like, really good.", @@ -72,7 +74,7 @@ joke_hints = [ "Have you tried Wargroove?\nI'm glad that for every abandoned series, enough people are yearning for its return that one of them will know how to code.", "Have you tried The Witness?\nOh. I guess you already have. Thanks for playing!", "Have you tried Zillion?\nMe neither. But it looks fun. So, let's try something new together?", - "Have you tried Zork: Grand Inquisitor?\nThis 1997 game uses Z-Vision technology to simulate 3D environments.\nCome on, I know you wanna find out what \"Z-Vision\" is.", + 'Have you tried Zork: Grand Inquisitor?\nThis 1997 game uses Z-Vision technology to simulate 3D environments.\nCome on, I know you wanna find out what "Z-Vision" is.', "Quaternions break my brain", "Eclipse has nothing, but you should do it anyway.", @@ -136,10 +138,10 @@ joke_hints = [ "In the future, war will break out between obelisk_sides and individual EP players.\nWhich side are you on?", "Droplets: Low, High, Mid.\nAmbience: Mid, Low, Mid, High.", "Name a better game involving lines. I'll wait.", - "\"You have to draw a line in the sand.\"\n- Arin \"Egoraptor\" Hanson", + '"You have to draw a line in the sand."\n- Arin "Egoraptor" Hanson', "Have you tried?\nThe puzzles tend to get easier if you do.", "Sorry, I accidentally left my phone in the Jungle.\nAnd also all my fragile dishes.", - "Winner of the \"Most Irrelevant PR in AP History\" award!", + 'Winner of the "Most Irrelevant PR in AP History" award!', "I bet you wish this was a real hint :)", "\"This hint is an impostor.\"- Junk hint submitted by T1mshady.\n...wait, I'm not supposed to say that part?", "Wouldn't you like to know, weather buoy?", @@ -192,10 +194,10 @@ class WitnessLocationHint: hint_came_from_location: bool # If a hint gets added to a set twice, but once as an item hint and once as a location hint, those are the same - def __hash__(self): + def __hash__(self) -> int: return hash(self.location) - def __eq__(self, other): + def __eq__(self, other) -> bool: return self.location == other.location @@ -324,7 +326,7 @@ def get_priority_hint_locations(world: "WitnessWorld") -> List[str]: "Boat Shipwreck Green EP", "Quarry Stoneworks Control Room Left", ] - + # Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side if "0x33A20" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES: priority.append("Town Obelisk Side 6") # Theater Flowers EP @@ -338,7 +340,7 @@ def get_priority_hint_locations(world: "WitnessWorld") -> List[str]: return priority -def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint): +def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint) -> WitnessWordedHint: location_name = hint.location.name if hint.location.player != world.player: location_name += " (" + world.multiworld.get_player_name(hint.location.player) + ")" @@ -373,8 +375,8 @@ def hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Ite def hint_from_location(world: "WitnessWorld", location: str) -> Optional[WitnessLocationHint]: - location_obj = world.multiworld.get_location(location, world.player) - item_obj = world.multiworld.get_location(location, world.player).item + location_obj = world.get_location(location) + item_obj = location_obj.item item_name = item_obj.name if item_obj.player != world.player: item_name += " (" + world.multiworld.get_player_name(item_obj.player) + ")" @@ -382,7 +384,8 @@ def hint_from_location(world: "WitnessWorld", location: str) -> Optional[Witness return WitnessLocationHint(location_obj, True) -def get_items_and_locations_in_random_order(world: "WitnessWorld", own_itempool: List[Item]): +def get_items_and_locations_in_random_order(world: "WitnessWorld", + own_itempool: List[Item]) -> Tuple[List[str], List[str]]: prog_items_in_this_world = sorted( item.name for item in own_itempool if item.advancement and item.code and item.location @@ -455,7 +458,11 @@ def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itemp hints = [] # This is a way to reverse a Dict[a,List[b]] to a Dict[b,a] - area_reverse_lookup = {v: k for k, l in unhinted_locations_for_hinted_areas.items() for v in l} + area_reverse_lookup = { + unhinted_location: hinted_area + for hinted_area, unhinted_locations in unhinted_locations_for_hinted_areas.items() + for unhinted_location in unhinted_locations + } while len(hints) < hint_amount: if not prog_items_in_this_world and not locations_in_this_world and not hints_to_use_first: @@ -529,16 +536,16 @@ def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[st def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]], Dict[str, List[Item]]]: - potential_areas = list(StaticWitnessLogic.ALL_AREAS_BY_NAME.keys()) + potential_areas = list(static_witness_logic.ALL_AREAS_BY_NAME.keys()) locations_per_area = dict() items_per_area = dict() for area in potential_areas: regions = [ - world.regio.created_regions[region] - for region in StaticWitnessLogic.ALL_AREAS_BY_NAME[area]["regions"] - if region in world.regio.created_regions + world.player_regions.created_regions[region] + for region in static_witness_logic.ALL_AREAS_BY_NAME[area]["regions"] + if region in world.player_regions.created_regions ] locations = [location for region in regions for location in region.get_locations() if location.address] @@ -596,7 +603,7 @@ def word_area_hint(world: "WitnessWorld", hinted_area: str, corresponding_items: if local_lasers == total_progression: sentence_end = (" for this world." if player_count > 1 else ".") - hint_string += f"\nAll of them are lasers" + sentence_end + hint_string += "\nAll of them are lasers" + sentence_end elif player_count > 1: if local_progression and non_local_progression: @@ -663,7 +670,7 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int, already_hinted_locations |= { loc for loc in world.multiworld.get_reachable_locations(state, world.player) - if loc.address and StaticWitnessLogic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)" + if loc.address and static_witness_logic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)" } intended_location_hints = hint_amount - area_hints diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py index cd6d71f4..df8214ac 100644 --- a/worlds/witness/locations.py +++ b/worlds/witness/locations.py @@ -3,511 +3,24 @@ Defines constants for different types of locations in the game """ from typing import TYPE_CHECKING +from .data import static_locations as static_witness_locations +from .data import static_logic as static_witness_logic from .player_logic import WitnessPlayerLogic -from .static_logic import StaticWitnessLogic if TYPE_CHECKING: from . import WitnessWorld -ID_START = 158000 - - -class StaticWitnessLocations: - """ - Witness Location Constants that stay consistent across worlds - """ - - GENERAL_LOCATIONS = { - "Tutorial Front Left", - "Tutorial Back Left", - "Tutorial Back Right", - "Tutorial Patio Floor", - "Tutorial Gate Open", - - "Outside Tutorial Vault Box", - "Outside Tutorial Discard", - "Outside Tutorial Shed Row 5", - "Outside Tutorial Tree Row 9", - "Outside Tutorial Outpost Entry Panel", - "Outside Tutorial Outpost Exit Panel", - - "Glass Factory Discard", - "Glass Factory Back Wall 5", - "Glass Factory Front 3", - "Glass Factory Melting 3", - - "Symmetry Island Lower Panel", - "Symmetry Island Right 5", - "Symmetry Island Back 6", - "Symmetry Island Left 7", - "Symmetry Island Upper Panel", - "Symmetry Island Scenery Outlines 5", - "Symmetry Island Laser Yellow 3", - "Symmetry Island Laser Blue 3", - "Symmetry Island Laser Panel", - - "Orchard Apple Tree 5", - - "Desert Vault Box", - "Desert Discard", - "Desert Surface 8", - "Desert Light Room 3", - "Desert Pond Room 5", - "Desert Flood Room 6", - "Desert Elevator Room Hexagonal", - "Desert Elevator Room Bent 3", - "Desert Laser Panel", - - "Quarry Entry 1 Panel", - "Quarry Entry 2 Panel", - "Quarry Stoneworks Entry Left Panel", - "Quarry Stoneworks Entry Right Panel", - "Quarry Stoneworks Lower Row 6", - "Quarry Stoneworks Upper Row 8", - "Quarry Stoneworks Control Room Left", - "Quarry Stoneworks Control Room Right", - "Quarry Stoneworks Stairs Panel", - "Quarry Boathouse Intro Right", - "Quarry Boathouse Intro Left", - "Quarry Boathouse Front Row 5", - "Quarry Boathouse Back First Row 9", - "Quarry Boathouse Back Second Row 3", - "Quarry Discard", - "Quarry Laser Panel", - - "Shadows Intro 8", - "Shadows Far 8", - "Shadows Near 5", - "Shadows Laser Panel", - - "Keep Hedge Maze 1", - "Keep Hedge Maze 2", - "Keep Hedge Maze 3", - "Keep Hedge Maze 4", - "Keep Pressure Plates 1", - "Keep Pressure Plates 2", - "Keep Pressure Plates 3", - "Keep Pressure Plates 4", - "Keep Discard", - "Keep Laser Panel Hedges", - "Keep Laser Panel Pressure Plates", - - "Shipwreck Vault Box", - "Shipwreck Discard", - - "Monastery Outside 3", - "Monastery Inside 4", - "Monastery Laser Panel", - - "Town Cargo Box Entry Panel", - "Town Cargo Box Discard", - "Town Tall Hexagonal", - "Town Church Entry Panel", - "Town Church Lattice", - "Town Maze Panel", - "Town Rooftop Discard", - "Town Red Rooftop 5", - "Town Wooden Roof Lower Row 5", - "Town Wooden Rooftop", - "Windmill Entry Panel", - "Town RGB House Entry Panel", - "Town Laser Panel", - - "Town RGB House Upstairs Left", - "Town RGB House Upstairs Right", - "Town RGB House Sound Room Right", - - "Windmill Theater Entry Panel", - "Theater Exit Left Panel", - "Theater Exit Right Panel", - "Theater Tutorial Video", - "Theater Desert Video", - "Theater Jungle Video", - "Theater Shipwreck Video", - "Theater Mountain Video", - "Theater Discard", - - "Jungle Discard", - "Jungle First Row 3", - "Jungle Second Row 4", - "Jungle Popup Wall 6", - "Jungle Laser Panel", - - "Jungle Vault Box", - "Jungle Monastery Garden Shortcut Panel", - - "Bunker Entry Panel", - "Bunker Intro Left 5", - "Bunker Intro Back 4", - "Bunker Glass Room 3", - "Bunker UV Room 2", - "Bunker Laser Panel", - - "Swamp Entry Panel", - "Swamp Intro Front 6", - "Swamp Intro Back 8", - "Swamp Between Bridges Near Row 4", - "Swamp Cyan Underwater 5", - "Swamp Platform Row 4", - "Swamp Platform Shortcut Right Panel", - "Swamp Between Bridges Far Row 4", - "Swamp Red Underwater 4", - "Swamp Purple Underwater", - "Swamp Beyond Rotating Bridge 4", - "Swamp Blue Underwater 5", - "Swamp Laser Panel", - "Swamp Laser Shortcut Right Panel", - - "Treehouse First Door Panel", - "Treehouse Second Door Panel", - "Treehouse Third Door Panel", - "Treehouse Yellow Bridge 9", - "Treehouse First Purple Bridge 5", - "Treehouse Second Purple Bridge 7", - "Treehouse Green Bridge 7", - "Treehouse Green Bridge Discard", - "Treehouse Left Orange Bridge 15", - "Treehouse Laser Discard", - "Treehouse Right Orange Bridge 12", - "Treehouse Laser Panel", - "Treehouse Drawbridge Panel", - - "Mountainside Discard", - "Mountainside Vault Box", - "Mountaintop River Shape", - - "Tutorial First Hallway EP", - "Tutorial Cloud EP", - "Tutorial Patio Flowers EP", - "Tutorial Gate EP", - "Outside Tutorial Garden EP", - "Outside Tutorial Town Sewer EP", - "Outside Tutorial Path EP", - "Outside Tutorial Tractor EP", - "Mountainside Thundercloud EP", - "Glass Factory Vase EP", - "Symmetry Island Glass Factory Black Line Reflection EP", - "Symmetry Island Glass Factory Black Line EP", - "Desert Sand Snake EP", - "Desert Facade Right EP", - "Desert Facade Left EP", - "Desert Stairs Left EP", - "Desert Stairs Right EP", - "Desert Broken Wall Straight EP", - "Desert Broken Wall Bend EP", - "Desert Shore EP", - "Desert Island EP", - "Desert Pond Room Near Reflection EP", - "Desert Pond Room Far Reflection EP", - "Desert Flood Room EP", - "Desert Elevator EP", - "Quarry Shore EP", - "Quarry Entrance Pipe EP", - "Quarry Sand Pile EP", - "Quarry Rock Line EP", - "Quarry Rock Line Reflection EP", - "Quarry Railroad EP", - "Quarry Stoneworks Ramp EP", - "Quarry Stoneworks Lift EP", - "Quarry Boathouse Moving Ramp EP", - "Quarry Boathouse Hook EP", - "Shadows Quarry Stoneworks Rooftop Vent EP", - "Treehouse Beach Rock Shadow EP", - "Treehouse Beach Sand Shadow EP", - "Treehouse Beach Both Orange Bridges EP", - "Keep Red Flowers EP", - "Keep Purple Flowers EP", - "Shipwreck Circle Near EP", - "Shipwreck Circle Left EP", - "Shipwreck Circle Far EP", - "Shipwreck Stern EP", - "Shipwreck Rope Inner EP", - "Shipwreck Rope Outer EP", - "Shipwreck Couch EP", - "Keep Pressure Plates 1 EP", - "Keep Pressure Plates 2 EP", - "Keep Pressure Plates 3 EP", - "Keep Pressure Plates 4 Left Exit EP", - "Keep Pressure Plates 4 Right Exit EP", - "Keep Path EP", - "Keep Hedges EP", - "Monastery Facade Left Near EP", - "Monastery Facade Left Far Short EP", - "Monastery Facade Left Far Long EP", - "Monastery Facade Right Near EP", - "Monastery Facade Left Stairs EP", - "Monastery Facade Right Stairs EP", - "Monastery Grass Stairs EP", - "Monastery Left Shutter EP", - "Monastery Middle Shutter EP", - "Monastery Right Shutter EP", - "Windmill First Blade EP", - "Windmill Second Blade EP", - "Windmill Third Blade EP", - "Town Tower Underside Third EP", - "Town Tower Underside Fourth EP", - "Town Tower Underside First EP", - "Town Tower Underside Second EP", - "Town RGB House Red EP", - "Town RGB House Green EP", - "Town Maze Bridge Underside EP", - "Town Black Line Redirect EP", - "Town Black Line Church EP", - "Town Brown Bridge EP", - "Town Black Line Tower EP", - "Theater Eclipse EP", - "Theater Window EP", - "Theater Door EP", - "Theater Church EP", - "Jungle Long Arch Moss EP", - "Jungle Straight Left Moss EP", - "Jungle Pop-up Wall Moss EP", - "Jungle Short Arch Moss EP", - "Jungle Entrance EP", - "Jungle Tree Halo EP", - "Jungle Bamboo CCW EP", - "Jungle Bamboo CW EP", - "Jungle Green Leaf Moss EP", - "Monastery Garden Left EP", - "Monastery Garden Right EP", - "Monastery Wall EP", - "Bunker Tinted Door EP", - "Bunker Green Room Flowers EP", - "Swamp Purple Sand Middle EP", - "Swamp Purple Sand Top EP", - "Swamp Purple Sand Bottom EP", - "Swamp Sliding Bridge Left EP", - "Swamp Sliding Bridge Right EP", - "Swamp Cyan Underwater Sliding Bridge EP", - "Swamp Rotating Bridge CCW EP", - "Swamp Rotating Bridge CW EP", - "Swamp Boat EP", - "Swamp Long Bridge Side EP", - "Swamp Purple Underwater Right EP", - "Swamp Purple Underwater Left EP", - "Treehouse Buoy EP", - "Treehouse Right Orange Bridge EP", - "Treehouse Burned House Beach EP", - "Mountainside Cloud Cycle EP", - "Mountainside Bush EP", - "Mountainside Apparent River EP", - "Mountaintop River Shape EP", - "Mountaintop Arch Black EP", - "Mountaintop Arch White Right EP", - "Mountaintop Arch White Left EP", - "Mountain Bottom Floor Yellow Bridge EP", - "Mountain Bottom Floor Blue Bridge EP", - "Mountain Floor 2 Pink Bridge EP", - "Caves Skylight EP", - "Challenge Water EP", - "Tunnels Theater Flowers EP", - "Boat Desert EP", - "Boat Shipwreck CCW Underside EP", - "Boat Shipwreck Green EP", - "Boat Shipwreck CW Underside EP", - "Boat Bunker Yellow Line EP", - "Boat Town Long Sewer EP", - "Boat Tutorial EP", - "Boat Tutorial Reflection EP", - "Boat Tutorial Moss EP", - "Boat Cargo Box EP", - - "Desert Obelisk Side 1", - "Desert Obelisk Side 2", - "Desert Obelisk Side 3", - "Desert Obelisk Side 4", - "Desert Obelisk Side 5", - "Monastery Obelisk Side 1", - "Monastery Obelisk Side 2", - "Monastery Obelisk Side 3", - "Monastery Obelisk Side 4", - "Monastery Obelisk Side 5", - "Monastery Obelisk Side 6", - "Treehouse Obelisk Side 1", - "Treehouse Obelisk Side 2", - "Treehouse Obelisk Side 3", - "Treehouse Obelisk Side 4", - "Treehouse Obelisk Side 5", - "Treehouse Obelisk Side 6", - "Mountainside Obelisk Side 1", - "Mountainside Obelisk Side 2", - "Mountainside Obelisk Side 3", - "Mountainside Obelisk Side 4", - "Mountainside Obelisk Side 5", - "Mountainside Obelisk Side 6", - "Quarry Obelisk Side 1", - "Quarry Obelisk Side 2", - "Quarry Obelisk Side 3", - "Quarry Obelisk Side 4", - "Quarry Obelisk Side 5", - "Town Obelisk Side 1", - "Town Obelisk Side 2", - "Town Obelisk Side 3", - "Town Obelisk Side 4", - "Town Obelisk Side 5", - "Town Obelisk Side 6", - - "Caves Mountain Shortcut Panel", - "Caves Swamp Shortcut Panel", - - "Caves Blue Tunnel Right First 4", - "Caves Blue Tunnel Left First 1", - "Caves Blue Tunnel Left Second 5", - "Caves Blue Tunnel Right Second 5", - "Caves Blue Tunnel Right Third 1", - "Caves Blue Tunnel Left Fourth 1", - "Caves Blue Tunnel Left Third 1", - - "Caves First Floor Middle", - "Caves First Floor Right", - "Caves First Floor Left", - "Caves First Floor Grounded", - "Caves Lone Pillar", - "Caves First Wooden Beam", - "Caves Second Wooden Beam", - "Caves Third Wooden Beam", - "Caves Fourth Wooden Beam", - "Caves Right Upstairs Left Row 8", - "Caves Right Upstairs Right Row 3", - "Caves Left Upstairs Single", - "Caves Left Upstairs Left Row 5", - - "Caves Challenge Entry Panel", - "Challenge Tunnels Entry Panel", - - "Tunnels Vault Box", - "Theater Challenge Video", - - "Tunnels Town Shortcut Panel", - - "Caves Skylight EP", - "Challenge Water EP", - "Tunnels Theater Flowers EP", - "Tutorial Gate EP", - - "Mountaintop Mountain Entry Panel", - - "Mountain Floor 1 Light Bridge Controller", - - "Mountain Floor 1 Right Row 5", - "Mountain Floor 1 Left Row 7", - "Mountain Floor 1 Back Row 3", - "Mountain Floor 1 Trash Pillar 2", - "Mountain Floor 2 Near Row 5", - "Mountain Floor 2 Far Row 6", - - "Mountain Floor 2 Light Bridge Controller Near", - "Mountain Floor 2 Light Bridge Controller Far", - - "Mountain Bottom Floor Yellow Bridge EP", - "Mountain Bottom Floor Blue Bridge EP", - "Mountain Floor 2 Pink Bridge EP", - - "Mountain Floor 2 Elevator Discard", - "Mountain Bottom Floor Giant Puzzle", - - "Mountain Bottom Floor Pillars Room Entry Left", - "Mountain Bottom Floor Pillars Room Entry Right", - - "Mountain Bottom Floor Caves Entry Panel", - - "Mountain Bottom Floor Left Pillar 4", - "Mountain Bottom Floor Right Pillar 4", - - "Challenge Vault Box", - "Theater Challenge Video", - "Mountain Bottom Floor Discard", - } - - OBELISK_SIDES = { - "Desert Obelisk Side 1", - "Desert Obelisk Side 2", - "Desert Obelisk Side 3", - "Desert Obelisk Side 4", - "Desert Obelisk Side 5", - "Monastery Obelisk Side 1", - "Monastery Obelisk Side 2", - "Monastery Obelisk Side 3", - "Monastery Obelisk Side 4", - "Monastery Obelisk Side 5", - "Monastery Obelisk Side 6", - "Treehouse Obelisk Side 1", - "Treehouse Obelisk Side 2", - "Treehouse Obelisk Side 3", - "Treehouse Obelisk Side 4", - "Treehouse Obelisk Side 5", - "Treehouse Obelisk Side 6", - "Mountainside Obelisk Side 1", - "Mountainside Obelisk Side 2", - "Mountainside Obelisk Side 3", - "Mountainside Obelisk Side 4", - "Mountainside Obelisk Side 5", - "Mountainside Obelisk Side 6", - "Quarry Obelisk Side 1", - "Quarry Obelisk Side 2", - "Quarry Obelisk Side 3", - "Quarry Obelisk Side 4", - "Quarry Obelisk Side 5", - "Town Obelisk Side 1", - "Town Obelisk Side 2", - "Town Obelisk Side 3", - "Town Obelisk Side 4", - "Town Obelisk Side 5", - "Town Obelisk Side 6", - } - - ALL_LOCATIONS_TO_ID = dict() - - AREA_LOCATION_GROUPS = dict() - - @staticmethod - def get_id(chex: str): - """ - Calculates the location ID for any given location - """ - - return StaticWitnessLogic.ENTITIES_BY_HEX[chex]["id"] - - @staticmethod - def get_event_name(panel_hex: str): - """ - Returns the event name of any given panel. - """ - - action = " Opened" if StaticWitnessLogic.ENTITIES_BY_HEX[panel_hex]["entityType"] == "Door" else " Solved" - - return StaticWitnessLogic.ENTITIES_BY_HEX[panel_hex]["checkName"] + action - - def __init__(self): - all_loc_to_id = { - panel_obj["checkName"]: self.get_id(chex) - for chex, panel_obj in StaticWitnessLogic.ENTITIES_BY_HEX.items() - if panel_obj["id"] - } - - all_loc_to_id = dict( - sorted(all_loc_to_id.items(), key=lambda loc: loc[1]) - ) - - for key, item in all_loc_to_id.items(): - self.ALL_LOCATIONS_TO_ID[key] = item - - for loc in all_loc_to_id: - area = StaticWitnessLogic.ENTITIES_BY_NAME[loc]["area"]["name"] - self.AREA_LOCATION_GROUPS.setdefault(area, []).append(loc) - - class WitnessPlayerLocations: """ Class that defines locations for a single player """ - def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic): + def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic) -> None: """Defines locations AFTER logic changes due to options""" self.PANEL_TYPES_TO_SHUFFLE = {"General", "Laser"} - self.CHECK_LOCATIONS = StaticWitnessLocations.GENERAL_LOCATIONS.copy() + self.CHECK_LOCATIONS = static_witness_locations.GENERAL_LOCATIONS.copy() if world.options.shuffle_discarded_panels: self.PANEL_TYPES_TO_SHUFFLE.add("Discard") @@ -520,28 +33,28 @@ class WitnessPlayerLocations: elif world.options.shuffle_EPs == "obelisk_sides": self.PANEL_TYPES_TO_SHUFFLE.add("Obelisk Side") - for obelisk_loc in StaticWitnessLocations.OBELISK_SIDES: - obelisk_loc_hex = StaticWitnessLogic.ENTITIES_BY_NAME[obelisk_loc]["entity_hex"] + for obelisk_loc in static_witness_locations.OBELISK_SIDES: + obelisk_loc_hex = static_witness_logic.ENTITIES_BY_NAME[obelisk_loc]["entity_hex"] if player_logic.REQUIREMENTS_BY_HEX[obelisk_loc_hex] == frozenset({frozenset()}): self.CHECK_LOCATIONS.discard(obelisk_loc) self.CHECK_LOCATIONS = self.CHECK_LOCATIONS | player_logic.ADDED_CHECKS - self.CHECK_LOCATIONS.discard(StaticWitnessLogic.ENTITIES_BY_HEX[player_logic.VICTORY_LOCATION]["checkName"]) + self.CHECK_LOCATIONS.discard(static_witness_logic.ENTITIES_BY_HEX[player_logic.VICTORY_LOCATION]["checkName"]) self.CHECK_LOCATIONS = self.CHECK_LOCATIONS - { - StaticWitnessLogic.ENTITIES_BY_HEX[entity_hex]["checkName"] + static_witness_logic.ENTITIES_BY_HEX[entity_hex]["checkName"] for entity_hex in player_logic.COMPLETELY_DISABLED_ENTITIES | player_logic.PRECOMPLETED_LOCATIONS } self.CHECK_PANELHEX_TO_ID = { - StaticWitnessLogic.ENTITIES_BY_NAME[ch]["entity_hex"]: StaticWitnessLocations.ALL_LOCATIONS_TO_ID[ch] + static_witness_logic.ENTITIES_BY_NAME[ch]["entity_hex"]: static_witness_locations.ALL_LOCATIONS_TO_ID[ch] for ch in self.CHECK_LOCATIONS - if StaticWitnessLogic.ENTITIES_BY_NAME[ch]["entityType"] in self.PANEL_TYPES_TO_SHUFFLE + if static_witness_logic.ENTITIES_BY_NAME[ch]["entityType"] in self.PANEL_TYPES_TO_SHUFFLE } - dog_hex = StaticWitnessLogic.ENTITIES_BY_NAME["Town Pet the Dog"]["entity_hex"] - dog_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID["Town Pet the Dog"] + dog_hex = static_witness_logic.ENTITIES_BY_NAME["Town Pet the Dog"]["entity_hex"] + dog_id = static_witness_locations.ALL_LOCATIONS_TO_ID["Town Pet the Dog"] self.CHECK_PANELHEX_TO_ID[dog_hex] = dog_id self.CHECK_PANELHEX_TO_ID = dict( @@ -553,22 +66,19 @@ class WitnessPlayerLocations: } self.EVENT_LOCATION_TABLE = { - StaticWitnessLocations.get_event_name(panel_hex): None - for panel_hex in event_locations + static_witness_locations.get_event_name(entity_hex): None + for entity_hex in event_locations } check_dict = { - StaticWitnessLogic.ENTITIES_BY_HEX[location]["checkName"]: - StaticWitnessLocations.get_id(StaticWitnessLogic.ENTITIES_BY_HEX[location]["entity_hex"]) + static_witness_logic.ENTITIES_BY_HEX[location]["checkName"]: + static_witness_locations.get_id(static_witness_logic.ENTITIES_BY_HEX[location]["entity_hex"]) for location in self.CHECK_PANELHEX_TO_ID } self.CHECK_LOCATION_TABLE = {**self.EVENT_LOCATION_TABLE, **check_dict} - def add_location_late(self, entity_name: str): - entity_hex = StaticWitnessLogic.ENTITIES_BY_NAME[entity_name]["entity_hex"] + def add_location_late(self, entity_name: str) -> None: + entity_hex = static_witness_logic.ENTITIES_BY_NAME[entity_name]["entity_hex"] self.CHECK_LOCATION_TABLE[entity_hex] = entity_name - self.CHECK_PANELHEX_TO_ID[entity_hex] = StaticWitnessLocations.get_id(entity_hex) - - -StaticWitnessLocations() + self.CHECK_PANELHEX_TO_ID[entity_hex] = static_witness_locations.get_id(entity_hex) diff --git a/worlds/witness/options.py b/worlds/witness/options.py index b66308df..63f98fae 100644 --- a/worlds/witness/options.py +++ b/worlds/witness/options.py @@ -1,10 +1,11 @@ from dataclasses import dataclass -from schema import Schema, And, Optional +from schema import And, Schema -from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions, OptionDict +from Options import Choice, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, Toggle -from .static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLogic +from .data import static_logic as static_witness_logic +from .data.item_definition_classes import ItemCategory, WeightedItemDefinition class DisableNonRandomizedPuzzles(Toggle): @@ -232,12 +233,12 @@ class TrapWeights(OptionDict): display_name = "Trap Weights" schema = Schema({ trap_name: And(int, lambda n: n >= 0) - for trap_name, item_definition in StaticWitnessLogic.all_items.items() + 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 StaticWitnessLogic.all_items.items() + for trap_name, item_definition in static_witness_logic.ALL_ITEMS.items() if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP } @@ -315,7 +316,7 @@ class TheWitnessOptions(PerGameCommonOptions): shuffle_discarded_panels: ShuffleDiscardedPanels shuffle_vault_boxes: ShuffleVaultBoxes obelisk_keys: ObeliskKeys - shuffle_EPs: ShuffleEnvironmentalPuzzles + shuffle_EPs: ShuffleEnvironmentalPuzzles # noqa: N815 EP_difficulty: EnvironmentalPuzzlesDifficulty shuffle_postgame: ShufflePostgame victory_condition: VictoryCondition diff --git a/worlds/witness/items.py b/worlds/witness/player_items.py similarity index 66% rename from worlds/witness/items.py rename to worlds/witness/player_items.py index 6802fd2a..925b21ae 100644 --- a/worlds/witness/items.py +++ b/worlds/witness/player_items.py @@ -2,16 +2,23 @@ Defines progression, junk and event items for The Witness """ import copy +from typing import TYPE_CHECKING, Dict, List, Set -from dataclasses import dataclass -from typing import Optional, Dict, List, Set, TYPE_CHECKING +from BaseClasses import Item, ItemClassification, MultiWorld -from BaseClasses import Item, MultiWorld, ItemClassification -from .locations import ID_START, WitnessPlayerLocations +from .data import static_items as static_witness_items +from .data import static_logic as static_witness_logic +from .data.item_definition_classes import ( + DoorItemDefinition, + ItemCategory, + ItemData, + ItemDefinition, + ProgressiveItemDefinition, + WeightedItemDefinition, +) +from .data.utils import build_weighted_int_list +from .locations import WitnessPlayerLocations from .player_logic import WitnessPlayerLogic -from .static_logic import ItemDefinition, DoorItemDefinition, ProgressiveItemDefinition, ItemCategory, \ - StaticWitnessLogic, WeightedItemDefinition -from .utils import build_weighted_int_list if TYPE_CHECKING: from . import WitnessWorld @@ -19,17 +26,6 @@ if TYPE_CHECKING: NUM_ENERGY_UPGRADES = 4 -@dataclass() -class ItemData: - """ - ItemData for an item in The Witness - """ - ap_code: Optional[int] - definition: ItemDefinition - classification: ItemClassification - local_only: bool = False - - class WitnessItem(Item): """ Item from the game The Witness @@ -37,79 +33,30 @@ class WitnessItem(Item): game: str = "The Witness" -class StaticWitnessItems: - """ - Class that handles Witness items independent of world settings - """ - item_data: Dict[str, ItemData] = {} - item_groups: Dict[str, List[str]] = {} - - # Useful items that are treated specially at generation time and should not be automatically added to the player's - # item list during get_progression_items. - special_usefuls: List[str] = ["Puzzle Skip"] - - def __init__(self): - for item_name, definition in StaticWitnessLogic.all_items.items(): - ap_item_code = definition.local_code + ID_START - classification: ItemClassification = ItemClassification.filler - local_only: bool = False - - if definition.category is ItemCategory.SYMBOL: - classification = ItemClassification.progression - StaticWitnessItems.item_groups.setdefault("Symbols", []).append(item_name) - elif definition.category is ItemCategory.DOOR: - classification = ItemClassification.progression - StaticWitnessItems.item_groups.setdefault("Doors", []).append(item_name) - elif definition.category is ItemCategory.LASER: - classification = ItemClassification.progression_skip_balancing - StaticWitnessItems.item_groups.setdefault("Lasers", []).append(item_name) - elif definition.category is ItemCategory.USEFUL: - classification = ItemClassification.useful - elif definition.category is ItemCategory.FILLER: - if item_name in ["Energy Fill (Small)"]: - local_only = True - classification = ItemClassification.filler - elif definition.category is ItemCategory.TRAP: - classification = ItemClassification.trap - elif definition.category is ItemCategory.JOKE: - classification = ItemClassification.filler - - StaticWitnessItems.item_data[item_name] = ItemData(ap_item_code, definition, - classification, local_only) - - @staticmethod - def get_item_to_door_mappings() -> Dict[int, List[int]]: - output: Dict[int, List[int]] = {} - for item_name, item_data in {name: data for name, data in StaticWitnessItems.item_data.items() - if isinstance(data.definition, DoorItemDefinition)}.items(): - item = StaticWitnessItems.item_data[item_name] - output[item.ap_code] = [int(hex_string, 16) for hex_string in item_data.definition.panel_id_hexes] - return output - - class WitnessPlayerItems: """ Class that defines Items for a single world """ - def __init__(self, world: "WitnessWorld", logic: WitnessPlayerLogic, locat: WitnessPlayerLocations): + def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic, + player_locations: WitnessPlayerLocations) -> None: """Adds event items after logic changes due to options""" self._world: "WitnessWorld" = world self._multiworld: MultiWorld = world.multiworld self._player_id: int = world.player - self._logic: WitnessPlayerLogic = logic - self._locations: WitnessPlayerLocations = locat + self._logic: WitnessPlayerLogic = player_logic + self._locations: WitnessPlayerLocations = player_locations # Duplicate the static item data, then make any player-specific adjustments to classification. - self.item_data: Dict[str, ItemData] = copy.deepcopy(StaticWitnessItems.item_data) + self.item_data: Dict[str, ItemData] = copy.deepcopy(static_witness_items.ITEM_DATA) # Remove all progression items that aren't actually in the game. self.item_data = { name: data for (name, data) in self.item_data.items() if data.classification not in - {ItemClassification.progression, ItemClassification.progression_skip_balancing} - or name in logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME + {ItemClassification.progression, ItemClassification.progression_skip_balancing} + or name in player_logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME } # Downgrade door items @@ -138,7 +85,7 @@ class WitnessPlayerItems: # Add setting-specific useful items to the mandatory item list. for item_name, item_data in {name: data for (name, data) in self.item_data.items() if data.classification == ItemClassification.useful}.items(): - if item_name in StaticWitnessItems.special_usefuls: + if item_name in static_witness_items._special_usefuls: continue elif item_name == "Energy Capacity": self._mandatory_items[item_name] = NUM_ENERGY_UPGRADES @@ -149,7 +96,7 @@ class WitnessPlayerItems: # Add event items to the item definition list for later lookup. for event_location in self._locations.EVENT_LOCATION_TABLE: - location_name = logic.EVENT_ITEM_PAIRS[event_location] + location_name = player_logic.EVENT_ITEM_PAIRS[event_location] self.item_data[location_name] = ItemData(None, ItemDefinition(0, ItemCategory.EVENT), ItemClassification.progression, False) @@ -219,7 +166,7 @@ class WitnessPlayerItems: output.add("Triangles") # Replace progressive items with their parents. - output = {StaticWitnessLogic.get_parent_progressive_item(item) for item in output} + output = {static_witness_logic.get_parent_progressive_item(item) for item in output} # Remove items that are mentioned in any plando options. (Hopefully, in the future, plando will get resolved # before create_items so that we'll be able to check placed items instead of just removing all items mentioned @@ -227,16 +174,16 @@ class WitnessPlayerItems: for plando_setting in self._multiworld.plando_items[self._player_id]: if plando_setting.get("from_pool", True): for item_setting_key in [key for key in ["item", "items"] if key in plando_setting]: - if type(plando_setting[item_setting_key]) is str: + if isinstance(plando_setting[item_setting_key], str): output -= {plando_setting[item_setting_key]} - elif type(plando_setting[item_setting_key]) is dict: + elif isinstance(plando_setting[item_setting_key], dict): output -= {item for item, weight in plando_setting[item_setting_key].items() if weight} else: # Assume this is some other kind of iterable. for inner_item in plando_setting[item_setting_key]: - if type(inner_item) is str: + if isinstance(inner_item, str): output -= {inner_item} - elif type(inner_item) is dict: + elif isinstance(inner_item, dict): output -= {item for item, weight in inner_item.items() if weight} # Sort the output for consistency across versions if the implementation changes but the logic does not. @@ -257,7 +204,7 @@ class WitnessPlayerItems: """ Returns the item IDs of symbol items that were defined in the configuration file but are not in the pool. """ - return [data.ap_code for name, data in StaticWitnessItems.item_data.items() + return [data.ap_code for name, data in static_witness_items.ITEM_DATA.items() if name not in self.item_data.keys() and data.definition.category is ItemCategory.SYMBOL] def get_progressive_item_ids_in_pool(self) -> Dict[int, List[int]]: @@ -267,9 +214,8 @@ class WitnessPlayerItems: if isinstance(item.definition, ProgressiveItemDefinition): # Note: we need to reference the static table here rather than the player-specific one because the child # items were removed from the pool when we pruned out all progression items not in the settings. - output[item.ap_code] = [StaticWitnessItems.item_data[child_item].ap_code + output[item.ap_code] = [static_witness_items.ITEM_DATA[child_item].ap_code for child_item in item.definition.child_item_names] return output -StaticWitnessItems() diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py index 6bc263b9..01caee89 100644 --- a/worlds/witness/player_logic.py +++ b/worlds/witness/player_logic.py @@ -17,11 +17,13 @@ When the world has parsed its options, a second function is called to finalize t import copy from collections import defaultdict -from typing import cast, TYPE_CHECKING +from functools import lru_cache from logging import warning +from typing import TYPE_CHECKING, Dict, FrozenSet, List, Set, Tuple, cast -from .static_logic import StaticWitnessLogic, DoorItemDefinition, ItemCategory, ProgressiveItemDefinition -from .utils import * +from .data import static_logic as static_witness_logic +from .data import utils +from .data.item_definition_classes import DoorItemDefinition, ItemCategory, ProgressiveItemDefinition if TYPE_CHECKING: from . import WitnessWorld @@ -31,7 +33,7 @@ class WitnessPlayerLogic: """WITNESS LOGIC CLASS""" @lru_cache(maxsize=None) - def reduce_req_within_region(self, panel_hex: str) -> FrozenSet[FrozenSet[str]]: + def reduce_req_within_region(self, entity_hex: str) -> FrozenSet[FrozenSet[str]]: """ Panels in this game often only turn on when other panels are solved. Those other panels may have different item requirements. @@ -40,15 +42,15 @@ class WitnessPlayerLogic: Panels outside of the same region will still be checked manually. """ - if panel_hex in self.COMPLETELY_DISABLED_ENTITIES or panel_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES: + if entity_hex in self.COMPLETELY_DISABLED_ENTITIES or entity_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES: return frozenset() - entity_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel_hex] + entity_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity_hex] these_items = frozenset({frozenset()}) if entity_obj["id"]: - these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"] + these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex]["items"] these_items = frozenset({ subset.intersection(self.THEORETICAL_ITEMS_NO_MULTI) @@ -58,28 +60,28 @@ class WitnessPlayerLogic: for subset in these_items: self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(subset) - these_panels = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["panels"] + these_panels = self.DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex]["panels"] - if panel_hex in self.DOOR_ITEMS_BY_ID: - door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[panel_hex]}) + if entity_hex in self.DOOR_ITEMS_BY_ID: + door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[entity_hex]}) all_options: Set[FrozenSet[str]] = set() - for dependentItem in door_items: - self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(dependentItem) + for dependent_item in door_items: + self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(dependent_item) for items_option in these_items: - all_options.add(items_option.union(dependentItem)) + all_options.add(items_option.union(dependent_item)) # If this entity is not an EP, and it has an associated door item, ignore the original power dependencies - if StaticWitnessLogic.ENTITIES_BY_HEX[panel_hex]["entityType"] != "EP": + if static_witness_logic.ENTITIES_BY_HEX[entity_hex]["entityType"] != "EP": # 0x28A0D depends on another entity for *non-power* reasons -> This dependency needs to be preserved, # except in Expert, where that dependency doesn't exist, but now there *is* a power dependency. # In the future, it'd be wise to make a distinction between "power dependencies" and other dependencies. - if panel_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels): + if entity_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels): these_items = all_options # Another dependency that is not power-based: The Symmetry Island Upper Panel latches - elif panel_hex == "0x1C349": + elif entity_hex == "0x1C349": these_items = all_options else: @@ -107,9 +109,9 @@ class WitnessPlayerLogic: if option_entity in self.ALWAYS_EVENT_NAMES_BY_HEX: new_items = frozenset({frozenset([option_entity])}) - elif (panel_hex, option_entity) in self.CONDITIONAL_EVENTS: + elif (entity_hex, option_entity) in self.CONDITIONAL_EVENTS: new_items = frozenset({frozenset([option_entity])}) - self.USED_EVENT_NAMES_BY_HEX[option_entity] = self.CONDITIONAL_EVENTS[(panel_hex, option_entity)] + self.USED_EVENT_NAMES_BY_HEX[option_entity] = self.CONDITIONAL_EVENTS[(entity_hex, option_entity)] elif option_entity in {"7 Lasers", "11 Lasers", "7 Lasers + Redirect", "11 Lasers + Redirect", "PP2 Weirdness", "Theater to Tunnels"}: new_items = frozenset({frozenset([option_entity])}) @@ -121,36 +123,36 @@ class WitnessPlayerLogic: for possibility in new_items ) - dependent_items_for_option = dnf_and([dependent_items_for_option, new_items]) + dependent_items_for_option = utils.dnf_and([dependent_items_for_option, new_items]) for items_option in these_items: - for dependentItem in dependent_items_for_option: - all_options.add(items_option.union(dependentItem)) + for dependent_item in dependent_items_for_option: + all_options.add(items_option.union(dependent_item)) - return dnf_remove_redundancies(frozenset(all_options)) + return utils.dnf_remove_redundancies(frozenset(all_options)) - def make_single_adjustment(self, adj_type: str, line: str): - from . import StaticWitnessItems + def make_single_adjustment(self, adj_type: str, line: str) -> None: + from .data import static_items as static_witness_items """Makes a single logic adjustment based on additional logic file""" if adj_type == "Items": line_split = line.split(" - ") item_name = line_split[0] - if item_name not in StaticWitnessItems.item_data: - raise RuntimeError("Item \"" + item_name + "\" does not exist.") + if item_name not in static_witness_items.ITEM_DATA: + raise RuntimeError(f'Item "{item_name}" does not exist.') self.THEORETICAL_ITEMS.add(item_name) - if isinstance(StaticWitnessLogic.all_items[item_name], ProgressiveItemDefinition): + if isinstance(static_witness_logic.ALL_ITEMS[item_name], ProgressiveItemDefinition): self.THEORETICAL_ITEMS_NO_MULTI.update(cast(ProgressiveItemDefinition, - StaticWitnessLogic.all_items[item_name]).child_item_names) + static_witness_logic.ALL_ITEMS[item_name]).child_item_names) else: self.THEORETICAL_ITEMS_NO_MULTI.add(item_name) - if StaticWitnessLogic.all_items[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]: - panel_hexes = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name]).panel_id_hexes - for panel_hex in panel_hexes: - self.DOOR_ITEMS_BY_ID.setdefault(panel_hex, []).append(item_name) + if static_witness_logic.ALL_ITEMS[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]: + entity_hexes = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name]).panel_id_hexes + for entity_hex in entity_hexes: + self.DOOR_ITEMS_BY_ID.setdefault(entity_hex, []).append(item_name) return @@ -158,18 +160,18 @@ class WitnessPlayerLogic: item_name = line self.THEORETICAL_ITEMS.discard(item_name) - if isinstance(StaticWitnessLogic.all_items[item_name], ProgressiveItemDefinition): + if isinstance(static_witness_logic.ALL_ITEMS[item_name], ProgressiveItemDefinition): self.THEORETICAL_ITEMS_NO_MULTI.difference_update( - cast(ProgressiveItemDefinition, StaticWitnessLogic.all_items[item_name]).child_item_names + cast(ProgressiveItemDefinition, static_witness_logic.ALL_ITEMS[item_name]).child_item_names ) else: self.THEORETICAL_ITEMS_NO_MULTI.discard(item_name) - if StaticWitnessLogic.all_items[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]: - panel_hexes = cast(DoorItemDefinition, StaticWitnessLogic.all_items[item_name]).panel_id_hexes - for panel_hex in panel_hexes: - if panel_hex in self.DOOR_ITEMS_BY_ID and item_name in self.DOOR_ITEMS_BY_ID[panel_hex]: - self.DOOR_ITEMS_BY_ID[panel_hex].remove(item_name) + if static_witness_logic.ALL_ITEMS[item_name].category in [ItemCategory.DOOR, ItemCategory.LASER]: + entity_hexes = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name]).panel_id_hexes + for entity_hex in entity_hexes: + if entity_hex in self.DOOR_ITEMS_BY_ID and item_name in self.DOOR_ITEMS_BY_ID[entity_hex]: + self.DOOR_ITEMS_BY_ID[entity_hex].remove(item_name) if adj_type == "Starting Inventory": self.STARTING_INVENTORY.add(line) @@ -189,13 +191,13 @@ class WitnessPlayerLogic: line_split = line.split(" - ") requirement = { - "panels": parse_lambda(line_split[1]), + "panels": utils.parse_lambda(line_split[1]), } if len(line_split) > 2: - required_items = parse_lambda(line_split[2]) + required_items = utils.parse_lambda(line_split[2]) items_actually_in_the_game = [ - item_name for item_name, item_definition in StaticWitnessLogic.all_items.items() + item_name for item_name, item_definition in static_witness_logic.ALL_ITEMS.items() if item_definition.category is ItemCategory.SYMBOL ] required_items = frozenset( @@ -210,21 +212,21 @@ class WitnessPlayerLogic: return if adj_type == "Disabled Locations": - panel_hex = line[:7] + entity_hex = line[:7] - self.COMPLETELY_DISABLED_ENTITIES.add(panel_hex) + self.COMPLETELY_DISABLED_ENTITIES.add(entity_hex) return if adj_type == "Irrelevant Locations": - panel_hex = line[:7] + entity_hex = line[:7] - self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES.add(panel_hex) + self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES.add(entity_hex) return if adj_type == "Region Changes": - new_region_and_options = define_new_region(line + ":") + new_region_and_options = utils.define_new_region(line + ":") self.CONNECTIONS_BY_REGION_NAME[new_region_and_options[0]["name"]] = new_region_and_options[1] @@ -245,11 +247,11 @@ class WitnessPlayerLogic: (target_region, frozenset({frozenset(["TrueOneWay"])})) ) else: - new_lambda = connection[1] | parse_lambda(panel_set_string) + new_lambda = connection[1] | utils.parse_lambda(panel_set_string) self.CONNECTIONS_BY_REGION_NAME[source_region].add((target_region, new_lambda)) break else: # Execute if loop did not break. TIL this is a thing you can do! - new_conn = (target_region, parse_lambda(panel_set_string)) + new_conn = (target_region, utils.parse_lambda(panel_set_string)) self.CONNECTIONS_BY_REGION_NAME[source_region].add(new_conn) if adj_type == "Added Locations": @@ -258,7 +260,7 @@ class WitnessPlayerLogic: self.ADDED_CHECKS.add(line) @staticmethod - def handle_postgame(world: "WitnessWorld"): + def handle_postgame(world: "WitnessWorld") -> List[List[str]]: # In shuffle_postgame, panels that become accessible "after or at the same time as the goal" are disabled. # This has a lot of complicated considerations, which I'll try my best to explain. postgame_adjustments = [] @@ -285,29 +287,29 @@ class WitnessPlayerLogic: # Caves & Challenge should never have anything if doors are vanilla - definitionally "post-game" # This is technically imprecise, but it matches player expectations better. if not (early_caves or doors): - postgame_adjustments.append(get_caves_exclusion_list()) - postgame_adjustments.append(get_beyond_challenge_exclusion_list()) + postgame_adjustments.append(utils.get_caves_exclusion_list()) + postgame_adjustments.append(utils.get_beyond_challenge_exclusion_list()) # If Challenge is the goal, some panels on the way need to be left on, as well as Challenge Vault box itself if not victory == "challenge": - postgame_adjustments.append(get_path_to_challenge_exclusion_list()) - postgame_adjustments.append(get_challenge_vault_box_exclusion_list()) + postgame_adjustments.append(utils.get_path_to_challenge_exclusion_list()) + postgame_adjustments.append(utils.get_challenge_vault_box_exclusion_list()) # Challenge can only have something if the goal is not challenge or longbox itself. # In case of shortbox, it'd have to be a "reverse shortbox" situation where shortbox requires *more* lasers. # In that case, it'd also have to be a doors mode, but that's already covered by the previous block. if not (victory == "elevator" or reverse_shortbox_goal): - postgame_adjustments.append(get_beyond_challenge_exclusion_list()) + postgame_adjustments.append(utils.get_beyond_challenge_exclusion_list()) if not victory == "challenge": - postgame_adjustments.append(get_challenge_vault_box_exclusion_list()) + postgame_adjustments.append(utils.get_challenge_vault_box_exclusion_list()) # Mountain can't be reached if the goal is shortbox (or "reverse long box") if not mountain_enterable_from_top: - postgame_adjustments.append(get_mountain_upper_exclusion_list()) + postgame_adjustments.append(utils.get_mountain_upper_exclusion_list()) # Same goes for lower mountain, but that one *can* be reached in remote doors modes. if not doors: - postgame_adjustments.append(get_mountain_lower_exclusion_list()) + postgame_adjustments.append(utils.get_mountain_lower_exclusion_list()) # The Mountain Bottom Floor Discard is a bit complicated, so we handle it separately. ("it" == the Discard) # In Elevator Goal, it is definitionally in the post-game, unless remote doors is played. @@ -319,15 +321,15 @@ class WitnessPlayerLogic: # This has different consequences depending on whether remote doors is being played. # If doors are vanilla, Bottom Floor Discard locks a door to an area, which has to be disabled as well. if doors: - postgame_adjustments.append(get_bottom_floor_discard_exclusion_list()) + postgame_adjustments.append(utils.get_bottom_floor_discard_exclusion_list()) else: - postgame_adjustments.append(get_bottom_floor_discard_nondoors_exclusion_list()) + postgame_adjustments.append(utils.get_bottom_floor_discard_nondoors_exclusion_list()) # In Challenge goal + early_caves + vanilla doors, you could find something important on Bottom Floor Discard, # including the Caves Shortcuts themselves if playing "early_caves: start_inventory". # This is another thing that was deemed "unfun" more than fitting the actual definition of post-game. if victory == "challenge" and early_caves and not doors: - postgame_adjustments.append(get_bottom_floor_discard_nondoors_exclusion_list()) + postgame_adjustments.append(utils.get_bottom_floor_discard_nondoors_exclusion_list()) # If we have a proper short box goal, long box will never be activated first. if proper_shortbox_goal: @@ -335,7 +337,7 @@ class WitnessPlayerLogic: return postgame_adjustments - def make_options_adjustments(self, world: "WitnessWorld"): + def make_options_adjustments(self, world: "WitnessWorld") -> None: """Makes logic adjustments based on options""" adjustment_linesets_in_order = [] @@ -356,15 +358,15 @@ class WitnessPlayerLogic: # In disable_non_randomized, the discards are needed for alternate activation triggers, UNLESS both # (remote) doors and lasers are shuffled. if not world.options.disable_non_randomized_puzzles or (doors and lasers): - adjustment_linesets_in_order.append(get_discard_exclusion_list()) + adjustment_linesets_in_order.append(utils.get_discard_exclusion_list()) if doors: - adjustment_linesets_in_order.append(get_bottom_floor_discard_exclusion_list()) + adjustment_linesets_in_order.append(utils.get_bottom_floor_discard_exclusion_list()) if not world.options.shuffle_vault_boxes: - adjustment_linesets_in_order.append(get_vault_exclusion_list()) + adjustment_linesets_in_order.append(utils.get_vault_exclusion_list()) if not victory == "challenge": - adjustment_linesets_in_order.append(get_challenge_vault_box_exclusion_list()) + adjustment_linesets_in_order.append(utils.get_challenge_vault_box_exclusion_list()) # Victory Condition @@ -387,54 +389,54 @@ class WitnessPlayerLogic: ]) if world.options.disable_non_randomized_puzzles: - adjustment_linesets_in_order.append(get_disable_unrandomized_list()) + adjustment_linesets_in_order.append(utils.get_disable_unrandomized_list()) if world.options.shuffle_symbols: - adjustment_linesets_in_order.append(get_symbol_shuffle_list()) + adjustment_linesets_in_order.append(utils.get_symbol_shuffle_list()) if world.options.EP_difficulty == "normal": - adjustment_linesets_in_order.append(get_ep_easy()) + adjustment_linesets_in_order.append(utils.get_ep_easy()) elif world.options.EP_difficulty == "tedious": - adjustment_linesets_in_order.append(get_ep_no_eclipse()) + adjustment_linesets_in_order.append(utils.get_ep_no_eclipse()) if world.options.door_groupings == "regional": if world.options.shuffle_doors == "panels": - adjustment_linesets_in_order.append(get_simple_panels()) + adjustment_linesets_in_order.append(utils.get_simple_panels()) elif world.options.shuffle_doors == "doors": - adjustment_linesets_in_order.append(get_simple_doors()) + adjustment_linesets_in_order.append(utils.get_simple_doors()) elif world.options.shuffle_doors == "mixed": - adjustment_linesets_in_order.append(get_simple_doors()) - adjustment_linesets_in_order.append(get_simple_additional_panels()) + adjustment_linesets_in_order.append(utils.get_simple_doors()) + adjustment_linesets_in_order.append(utils.get_simple_additional_panels()) else: if world.options.shuffle_doors == "panels": - adjustment_linesets_in_order.append(get_complex_door_panels()) - adjustment_linesets_in_order.append(get_complex_additional_panels()) + adjustment_linesets_in_order.append(utils.get_complex_door_panels()) + adjustment_linesets_in_order.append(utils.get_complex_additional_panels()) elif world.options.shuffle_doors == "doors": - adjustment_linesets_in_order.append(get_complex_doors()) + adjustment_linesets_in_order.append(utils.get_complex_doors()) elif world.options.shuffle_doors == "mixed": - adjustment_linesets_in_order.append(get_complex_doors()) - adjustment_linesets_in_order.append(get_complex_additional_panels()) + adjustment_linesets_in_order.append(utils.get_complex_doors()) + adjustment_linesets_in_order.append(utils.get_complex_additional_panels()) if world.options.shuffle_boat: - adjustment_linesets_in_order.append(get_boat()) + adjustment_linesets_in_order.append(utils.get_boat()) if world.options.early_caves == "starting_inventory": - adjustment_linesets_in_order.append(get_early_caves_start_list()) + adjustment_linesets_in_order.append(utils.get_early_caves_start_list()) if world.options.early_caves == "add_to_pool" and not doors: - adjustment_linesets_in_order.append(get_early_caves_list()) + adjustment_linesets_in_order.append(utils.get_early_caves_list()) if world.options.elevators_come_to_you: - adjustment_linesets_in_order.append(get_elevators_come_to_you()) + adjustment_linesets_in_order.append(utils.get_elevators_come_to_you()) for item in self.YAML_ADDED_ITEMS: adjustment_linesets_in_order.append(["Items:", item]) if lasers: - adjustment_linesets_in_order.append(get_laser_shuffle()) + adjustment_linesets_in_order.append(utils.get_laser_shuffle()) if world.options.shuffle_EPs and world.options.obelisk_keys: - adjustment_linesets_in_order.append(get_obelisk_keys()) + adjustment_linesets_in_order.append(utils.get_obelisk_keys()) if world.options.shuffle_EPs == "obelisk_sides": ep_gen = ((ep_hex, ep_obj) for (ep_hex, ep_obj) in self.REFERENCE_LOGIC.ENTITIES_BY_HEX.items() @@ -446,10 +448,10 @@ class WitnessPlayerLogic: ep_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[ep_hex]["checkName"] self.ALWAYS_EVENT_NAMES_BY_HEX[ep_hex] = f"{obelisk_name} - {ep_name}" else: - adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_obelisks()[1:]) + adjustment_linesets_in_order.append(["Disabled Locations:"] + utils.get_ep_obelisks()[1:]) if not world.options.shuffle_EPs: - adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_all_individual()[1:]) + adjustment_linesets_in_order.append(["Disabled Locations:"] + utils.get_ep_all_individual()[1:]) for yaml_disabled_location in self.YAML_DISABLED_LOCATIONS: if yaml_disabled_location not in self.REFERENCE_LOGIC.ENTITIES_BY_NAME: @@ -480,7 +482,7 @@ class WitnessPlayerLogic: if entity_id in self.DOOR_ITEMS_BY_ID: del self.DOOR_ITEMS_BY_ID[entity_id] - def make_dependency_reduced_checklist(self): + def make_dependency_reduced_checklist(self) -> None: """ Turns dependent check set into semi-independent check set """ @@ -492,10 +494,10 @@ class WitnessPlayerLogic: for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI: if item not in self.THEORETICAL_ITEMS: - progressive_item_name = StaticWitnessLogic.get_parent_progressive_item(item) + progressive_item_name = static_witness_logic.get_parent_progressive_item(item) self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(progressive_item_name) child_items = cast(ProgressiveItemDefinition, - StaticWitnessLogic.all_items[progressive_item_name]).child_item_names + static_witness_logic.ALL_ITEMS[progressive_item_name]).child_item_names multi_list = [child_item for child_item in child_items if child_item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI] self.MULTI_AMOUNTS[item] = multi_list.index(item) + 1 @@ -520,24 +522,24 @@ class WitnessPlayerLogic: if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]: region_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]["name"] - entity_req = dnf_and([entity_req, frozenset({frozenset({region_name})})]) + entity_req = utils.dnf_and([entity_req, frozenset({frozenset({region_name})})]) individual_entity_requirements.append(entity_req) - overall_requirement |= dnf_and(individual_entity_requirements) + overall_requirement |= utils.dnf_and(individual_entity_requirements) new_connections.append((connection[0], overall_requirement)) self.CONNECTIONS_BY_REGION_NAME[region] = new_connections - def solvability_guaranteed(self, entity_hex: str): + def solvability_guaranteed(self, entity_hex: str) -> bool: return not ( entity_hex in self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY or entity_hex in self.COMPLETELY_DISABLED_ENTITIES or entity_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES ) - def determine_unrequired_entities(self, world: "WitnessWorld"): + def determine_unrequired_entities(self, world: "WitnessWorld") -> None: """Figure out which major items are actually useless in this world's settings""" # Gather quick references to relevant options @@ -596,7 +598,7 @@ class WitnessPlayerLogic: item_name for item_name, is_required in is_item_required_dict.items() if not is_required } - def make_event_item_pair(self, panel: str): + def make_event_item_pair(self, panel: str) -> Tuple[str, str]: """ Makes a pair of an event panel and its event item """ @@ -604,12 +606,12 @@ class WitnessPlayerLogic: name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel]["checkName"] + action if panel not in self.USED_EVENT_NAMES_BY_HEX: - warning("Panel \"" + name + "\" does not have an associated event name.") + warning(f'Panel "{name}" does not have an associated event name.') self.USED_EVENT_NAMES_BY_HEX[panel] = name + " Event" pair = (name, self.USED_EVENT_NAMES_BY_HEX[panel]) return pair - def make_event_panel_lists(self): + def make_event_panel_lists(self) -> None: self.ALWAYS_EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory" self.USED_EVENT_NAMES_BY_HEX.update(self.ALWAYS_EVENT_NAMES_BY_HEX) @@ -623,7 +625,7 @@ class WitnessPlayerLogic: pair = self.make_event_item_pair(panel) self.EVENT_ITEM_PAIRS[pair[0]] = pair[1] - def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_inv: Dict[str, int]): + def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_inv: Dict[str, int]) -> None: self.YAML_DISABLED_LOCATIONS = disabled_locations self.YAML_ADDED_ITEMS = start_inv @@ -646,11 +648,11 @@ class WitnessPlayerLogic: self.DIFFICULTY = world.options.puzzle_randomization if self.DIFFICULTY == "sigma_normal": - self.REFERENCE_LOGIC = StaticWitnessLogic.sigma_normal + self.REFERENCE_LOGIC = static_witness_logic.sigma_normal elif self.DIFFICULTY == "sigma_expert": - self.REFERENCE_LOGIC = StaticWitnessLogic.sigma_expert + self.REFERENCE_LOGIC = static_witness_logic.sigma_expert elif self.DIFFICULTY == "none": - self.REFERENCE_LOGIC = StaticWitnessLogic.vanilla + self.REFERENCE_LOGIC = static_witness_logic.vanilla self.CONNECTIONS_BY_REGION_NAME = copy.deepcopy(self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME) self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.deepcopy(self.REFERENCE_LOGIC.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX) diff --git a/worlds/witness/regions.py b/worlds/witness/regions.py index 350017c6..e1f0ddb2 100644 --- a/worlds/witness/regions.py +++ b/worlds/witness/regions.py @@ -2,26 +2,29 @@ Defines Region for The Witness, assigns locations to them, and connects them with the proper requirements """ -from typing import FrozenSet, TYPE_CHECKING, Dict, Tuple, List +from collections import defaultdict +from typing import TYPE_CHECKING, Dict, FrozenSet, List, Tuple from BaseClasses import Entrance, Region -from Utils import KeyedDefaultDict -from .static_logic import StaticWitnessLogic -from .locations import WitnessPlayerLocations, StaticWitnessLocations + +from worlds.generic.Rules import CollectionRule + +from .data import static_logic as static_witness_logic +from .locations import WitnessPlayerLocations, static_witness_locations from .player_logic import WitnessPlayerLogic if TYPE_CHECKING: from . import WitnessWorld -class WitnessRegions: +class WitnessPlayerRegions: """Class that defines Witness Regions""" - locat = None + player_locations = None logic = None @staticmethod - def make_lambda(item_requirement: FrozenSet[FrozenSet[str]], world: "WitnessWorld"): + def make_lambda(item_requirement: FrozenSet[FrozenSet[str]], world: "WitnessWorld") -> CollectionRule: from .rules import _meets_item_requirements """ @@ -82,7 +85,7 @@ class WitnessRegions: for dependent_region in mentioned_regions: world.multiworld.register_indirect_condition(regions_by_name[dependent_region], connection) - def create_regions(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic): + def create_regions(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic) -> None: """ Creates all the regions for The Witness """ @@ -94,16 +97,17 @@ class WitnessRegions: for region_name, region in self.reference_logic.ALL_REGIONS_BY_NAME.items(): locations_for_this_region = [ self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] for panel in region["panels"] - if self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] in self.locat.CHECK_LOCATION_TABLE + if self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] + in self.player_locations.CHECK_LOCATION_TABLE ] locations_for_this_region += [ - StaticWitnessLocations.get_event_name(panel) for panel in region["panels"] - if StaticWitnessLocations.get_event_name(panel) in self.locat.EVENT_LOCATION_TABLE + static_witness_locations.get_event_name(panel) for panel in region["panels"] + if static_witness_locations.get_event_name(panel) in self.player_locations.EVENT_LOCATION_TABLE ] all_locations = all_locations | set(locations_for_this_region) - new_region = create_region(world, region_name, self.locat, locations_for_this_region) + new_region = create_region(world, region_name, self.player_locations, locations_for_this_region) regions_by_name[region_name] = new_region @@ -133,16 +137,16 @@ class WitnessRegions: world.multiworld.regions += self.created_regions.values() - def __init__(self, locat: WitnessPlayerLocations, world: "WitnessWorld"): + def __init__(self, player_locations: WitnessPlayerLocations, world: "WitnessWorld") -> None: difficulty = world.options.puzzle_randomization if difficulty == "sigma_normal": - self.reference_logic = StaticWitnessLogic.sigma_normal + self.reference_logic = static_witness_logic.sigma_normal elif difficulty == "sigma_expert": - self.reference_logic = StaticWitnessLogic.sigma_expert + self.reference_logic = static_witness_logic.sigma_expert elif difficulty == "none": - self.reference_logic = StaticWitnessLogic.vanilla + self.reference_logic = static_witness_logic.vanilla - self.locat = locat - self.created_entrances: Dict[Tuple[str, str], List[Entrance]] = KeyedDefaultDict(lambda _: []) + self.player_locations = player_locations + self.created_entrances: Dict[Tuple[str, str], List[Entrance]] = defaultdict(lambda: []) self.created_regions: Dict[str, Region] = dict() diff --git a/worlds/witness/ruff.toml b/worlds/witness/ruff.toml new file mode 100644 index 00000000..d42361a4 --- /dev/null +++ b/worlds/witness/ruff.toml @@ -0,0 +1,11 @@ +line-length = 120 + +[lint] +select = ["E", "F", "W", "I", "N", "Q", "UP", "RUF", "ISC", "T20"] +ignore = ["RUF012", "RUF100"] + +[per-file-ignores] +# The way options definitions work right now, I am forced to break line length requirements. +"options.py" = ["E501"] +# The import list would just be so big if I imported every option individually in presets.py +"presets.py" = ["F403", "F405"] diff --git a/worlds/witness/rules.py b/worlds/witness/rules.py index 8636829a..6445545e 100644 --- a/worlds/witness/rules.py +++ b/worlds/witness/rules.py @@ -3,13 +3,16 @@ Defines the rules by which locations can be accessed, depending on the items received """ -from typing import TYPE_CHECKING, Callable, FrozenSet +from typing import TYPE_CHECKING, FrozenSet from BaseClasses import CollectionState -from .player_logic import WitnessPlayerLogic + +from worlds.generic.Rules import CollectionRule, set_rule + +from . import WitnessPlayerRegions +from .data import static_logic as static_witness_logic from .locations import WitnessPlayerLocations -from . import StaticWitnessLogic, WitnessRegions -from worlds.generic.Rules import set_rule +from .player_logic import WitnessPlayerLogic if TYPE_CHECKING: from . import WitnessWorld @@ -30,17 +33,17 @@ laser_hexes = [ def _has_laser(laser_hex: str, world: "WitnessWorld", player: int, - redirect_required: bool) -> Callable[[CollectionState], bool]: + redirect_required: bool) -> CollectionRule: if laser_hex == "0x012FB" and redirect_required: return lambda state: ( - _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat)(state) + _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.player_locations)(state) and state.has("Desert Laser Redirection", player) ) else: - return _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat) + return _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.player_locations) -def _has_lasers(amount: int, world: "WitnessWorld", redirect_required: bool) -> Callable[[CollectionState], bool]: +def _has_lasers(amount: int, world: "WitnessWorld", redirect_required: bool) -> CollectionRule: laser_lambdas = [] for laser_hex in laser_hexes: @@ -52,7 +55,7 @@ def _has_lasers(amount: int, world: "WitnessWorld", redirect_required: bool) -> def _can_solve_panel(panel: str, world: "WitnessWorld", player: int, player_logic: WitnessPlayerLogic, - locat: WitnessPlayerLocations) -> Callable[[CollectionState], bool]: + player_locations: WitnessPlayerLocations) -> CollectionRule: """ Determines whether a panel can be solved """ @@ -60,15 +63,16 @@ def _can_solve_panel(panel: str, world: "WitnessWorld", player: int, player_logi panel_obj = player_logic.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel] entity_name = panel_obj["checkName"] - if entity_name + " Solved" in locat.EVENT_LOCATION_TABLE: + if entity_name + " Solved" in player_locations.EVENT_LOCATION_TABLE: return lambda state: state.has(player_logic.EVENT_ITEM_PAIRS[entity_name + " Solved"], player) else: return make_lambda(panel, world) -def _can_move_either_direction(state: CollectionState, source: str, target: str, regio: WitnessRegions) -> bool: - entrance_forward = regio.created_entrances[source, target] - entrance_backward = regio.created_entrances[target, source] +def _can_move_either_direction(state: CollectionState, source: str, target: str, + player_regions: WitnessPlayerRegions) -> bool: + entrance_forward = player_regions.created_entrances[source, target] + entrance_backward = player_regions.created_entrances[target, source] return ( any(entrance.can_reach(state) for entrance in entrance_forward) @@ -81,49 +85,49 @@ def _can_do_expert_pp2(state: CollectionState, world: "WitnessWorld") -> bool: player = world.player hedge_2_access = ( - _can_move_either_direction(state, "Keep 2nd Maze", "Keep", world.regio) + _can_move_either_direction(state, "Keep 2nd Maze", "Keep", world.player_regions) ) hedge_3_access = ( - _can_move_either_direction(state, "Keep 3rd Maze", "Keep", world.regio) - or _can_move_either_direction(state, "Keep 3rd Maze", "Keep 2nd Maze", world.regio) - and hedge_2_access + _can_move_either_direction(state, "Keep 3rd Maze", "Keep", world.player_regions) + or _can_move_either_direction(state, "Keep 3rd Maze", "Keep 2nd Maze", world.player_regions) + and hedge_2_access ) hedge_4_access = ( - _can_move_either_direction(state, "Keep 4th Maze", "Keep", world.regio) - or _can_move_either_direction(state, "Keep 4th Maze", "Keep 3rd Maze", world.regio) - and hedge_3_access + _can_move_either_direction(state, "Keep 4th Maze", "Keep", world.player_regions) + or _can_move_either_direction(state, "Keep 4th Maze", "Keep 3rd Maze", world.player_regions) + and hedge_3_access ) hedge_access = ( - _can_move_either_direction(state, "Keep 4th Maze", "Keep Tower", world.regio) - and state.can_reach("Keep", "Region", player) - and hedge_4_access + _can_move_either_direction(state, "Keep 4th Maze", "Keep Tower", world.player_regions) + and state.can_reach("Keep", "Region", player) + and hedge_4_access ) backwards_to_fourth = ( - state.can_reach("Keep", "Region", player) - and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Keep Tower", world.regio) - and ( - _can_move_either_direction(state, "Keep", "Keep Tower", world.regio) - or hedge_access - ) + state.can_reach("Keep", "Region", player) + and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Keep Tower", world.player_regions) + and ( + _can_move_either_direction(state, "Keep", "Keep Tower", world.player_regions) + or hedge_access + ) ) shadows_shortcut = ( - state.can_reach("Main Island", "Region", player) - and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Shadows", world.regio) + state.can_reach("Main Island", "Region", player) + and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Shadows", world.player_regions) ) backwards_access = ( - _can_move_either_direction(state, "Keep 3rd Pressure Plate", "Keep 4th Pressure Plate", world.regio) - and (backwards_to_fourth or shadows_shortcut) + _can_move_either_direction(state, "Keep 3rd Pressure Plate", "Keep 4th Pressure Plate", world.player_regions) + and (backwards_to_fourth or shadows_shortcut) ) front_access = ( - _can_move_either_direction(state, "Keep 2nd Pressure Plate", "Keep", world.regio) - and state.can_reach("Keep", "Region", player) + _can_move_either_direction(state, "Keep 2nd Pressure Plate", "Keep", world.player_regions) + and state.can_reach("Keep", "Region", player) ) return front_access and backwards_access @@ -131,27 +135,27 @@ def _can_do_expert_pp2(state: CollectionState, world: "WitnessWorld") -> bool: def _can_do_theater_to_tunnels(state: CollectionState, world: "WitnessWorld") -> bool: direct_access = ( - _can_move_either_direction(state, "Tunnels", "Windmill Interior", world.regio) - and _can_move_either_direction(state, "Theater", "Windmill Interior", world.regio) + _can_move_either_direction(state, "Tunnels", "Windmill Interior", world.player_regions) + and _can_move_either_direction(state, "Theater", "Windmill Interior", world.player_regions) ) theater_from_town = ( - _can_move_either_direction(state, "Town", "Windmill Interior", world.regio) - and _can_move_either_direction(state, "Theater", "Windmill Interior", world.regio) - or _can_move_either_direction(state, "Town", "Theater", world.regio) + _can_move_either_direction(state, "Town", "Windmill Interior", world.player_regions) + and _can_move_either_direction(state, "Theater", "Windmill Interior", world.player_regions) + or _can_move_either_direction(state, "Town", "Theater", world.player_regions) ) tunnels_from_town = ( - _can_move_either_direction(state, "Tunnels", "Windmill Interior", world.regio) - and _can_move_either_direction(state, "Town", "Windmill Interior", world.regio) - or _can_move_either_direction(state, "Tunnels", "Town", world.regio) + _can_move_either_direction(state, "Tunnels", "Windmill Interior", world.player_regions) + and _can_move_either_direction(state, "Town", "Windmill Interior", world.player_regions) + or _can_move_either_direction(state, "Tunnels", "Town", world.player_regions) ) return direct_access or theater_from_town and tunnels_from_town def _has_item(item: str, world: "WitnessWorld", player: int, - player_logic: WitnessPlayerLogic, locat: WitnessPlayerLocations) -> Callable[[CollectionState], bool]: + player_logic: WitnessPlayerLogic, player_locations: WitnessPlayerLocations) -> CollectionRule: if item in player_logic.REFERENCE_LOGIC.ALL_REGIONS_BY_NAME: return lambda state: state.can_reach(item, "Region", player) if item == "7 Lasers": @@ -171,21 +175,21 @@ def _has_item(item: str, world: "WitnessWorld", player: int, elif item == "Theater to Tunnels": return lambda state: _can_do_theater_to_tunnels(state, world) if item in player_logic.USED_EVENT_NAMES_BY_HEX: - return _can_solve_panel(item, world, player, player_logic, locat) + return _can_solve_panel(item, world, player, player_logic, player_locations) - prog_item = StaticWitnessLogic.get_parent_progressive_item(item) + prog_item = static_witness_logic.get_parent_progressive_item(item) return lambda state: state.has(prog_item, player, player_logic.MULTI_AMOUNTS[item]) def _meets_item_requirements(requirements: FrozenSet[FrozenSet[str]], - world: "WitnessWorld") -> Callable[[CollectionState], bool]: + world: "WitnessWorld") -> CollectionRule: """ Checks whether item and panel requirements are met for a panel """ lambda_conversion = [ - [_has_item(item, world, world.player, world.player_logic, world.locat) for item in subset] + [_has_item(item, world, world.player, world.player_logic, world.player_locations) for item in subset] for subset in requirements ] @@ -195,7 +199,7 @@ def _meets_item_requirements(requirements: FrozenSet[FrozenSet[str]], ) -def make_lambda(entity_hex: str, world: "WitnessWorld") -> Callable[[CollectionState], bool]: +def make_lambda(entity_hex: str, world: "WitnessWorld") -> CollectionRule: """ Lambdas are created in a for loop so values need to be captured """ @@ -204,15 +208,15 @@ def make_lambda(entity_hex: str, world: "WitnessWorld") -> Callable[[CollectionS return _meets_item_requirements(entity_req, world) -def set_rules(world: "WitnessWorld"): +def set_rules(world: "WitnessWorld") -> None: """ Sets all rules for all locations """ - for location in world.locat.CHECK_LOCATION_TABLE: + for location in world.player_locations.CHECK_LOCATION_TABLE: real_location = location - if location in world.locat.EVENT_LOCATION_TABLE: + if location in world.player_locations.EVENT_LOCATION_TABLE: real_location = location[:-7] associated_entity = world.player_logic.REFERENCE_LOGIC.ENTITIES_BY_NAME[real_location] @@ -220,8 +224,8 @@ def set_rules(world: "WitnessWorld"): rule = make_lambda(entity_hex, world) - location = world.multiworld.get_location(location, world.player) + location = world.get_location(location) set_rule(location, rule) - world.multiworld.completion_condition[world.player] = lambda state: state.has('Victory', world.player) + world.multiworld.completion_condition[world.player] = lambda state: state.has("Victory", world.player)