The Witness: The big dumb refactor (#3007)
This commit is contained in:
parent
5d4ed00452
commit
401a6d9a42
worlds/witness
__init__.py
data
WitnessItems.txtWitnessLogic.txtWitnessLogicExpert.txtWitnessLogicVanilla.txt__init__.pyitem_definition_classes.py
hints.pylocations.pyoptions.pyplayer_items.pyplayer_logic.pyregions.pyruff.tomlrules.pysettings
Audio_Logs.txt
static_items.pystatic_locations.pystatic_logic.pyutils.pyDoor_Shuffle
Boat.txtComplex_Additional_Panels.txtComplex_Door_Panels.txtComplex_Doors.txtElevators_Come_To_You.txtObelisk_Keys.txtSimple_Additional_Panels.txtSimple_Doors.txtSimple_Panels.txt
EP_Shuffle
Early_Caves.txtEarly_Caves_Start.txtExclusions
Laser_Shuffle.txtPostgame
Beyond_Challenge.txtBottom_Floor_Discard.txtBottom_Floor_Discard_NonDoors.txtCaves.txtChallenge_Vault_Box.txtMountain_Lower.txtMountain_Upper.txtPath_To_Challenge.txt
Symbol_Shuffle.txt
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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()
|
|
@ -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)
|
|
@ -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
|
|
@ -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")]
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"]
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue