The Witness: mypy compliance (#3112)
* Make witness apworld mostly pass mypy * Fix all remaining mypy errors except the core ones * I'm a goofy stupid poopoo head * Two more fixes * ruff after merge * Mypy for new stuff * Oops * Stricter ruff rules (that I already comply with :3) * Deprecated ruff thing * wait no i lied * lol super nevermind * I can actually be slightly more specific * lint
This commit is contained in:
@@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Dict, List, Set, Tuple, cast
|
||||
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.item_definition_classes import DoorItemDefinition, ItemCategory, ProgressiveItemDefinition
|
||||
from .data.static_logic import StaticWitnessLogicObj
|
||||
from .data.utils import (
|
||||
WitnessRule,
|
||||
define_new_region,
|
||||
@@ -58,6 +59,95 @@ if TYPE_CHECKING:
|
||||
class WitnessPlayerLogic:
|
||||
"""WITNESS LOGIC CLASS"""
|
||||
|
||||
VICTORY_LOCATION: str
|
||||
|
||||
def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_inv: Dict[str, int]) -> None:
|
||||
self.YAML_DISABLED_LOCATIONS: Set[str] = disabled_locations
|
||||
self.YAML_ADDED_ITEMS: Dict[str, int] = start_inv
|
||||
|
||||
self.EVENT_PANELS_FROM_PANELS: Set[str] = set()
|
||||
self.EVENT_PANELS_FROM_REGIONS: Set[str] = set()
|
||||
|
||||
self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES: Set[str] = set()
|
||||
|
||||
self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY: Set[str] = set()
|
||||
|
||||
self.UNREACHABLE_REGIONS: Set[str] = set()
|
||||
|
||||
self.THEORETICAL_ITEMS: Set[str] = set()
|
||||
self.THEORETICAL_ITEMS_NO_MULTI: Set[str] = set()
|
||||
self.MULTI_AMOUNTS: Dict[str, int] = defaultdict(lambda: 1)
|
||||
self.MULTI_LISTS: Dict[str, List[str]] = {}
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI: Set[str] = set()
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME: Set[str] = set()
|
||||
self.DOOR_ITEMS_BY_ID: Dict[str, List[str]] = {}
|
||||
self.STARTING_INVENTORY: Set[str] = set()
|
||||
|
||||
self.DIFFICULTY = world.options.puzzle_randomization
|
||||
|
||||
self.REFERENCE_LOGIC: StaticWitnessLogicObj
|
||||
if self.DIFFICULTY == "sigma_expert":
|
||||
self.REFERENCE_LOGIC = static_witness_logic.sigma_expert
|
||||
elif self.DIFFICULTY == "none":
|
||||
self.REFERENCE_LOGIC = static_witness_logic.vanilla
|
||||
else:
|
||||
self.REFERENCE_LOGIC = static_witness_logic.sigma_normal
|
||||
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy(
|
||||
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
||||
)
|
||||
self.CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy(
|
||||
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
||||
)
|
||||
self.DEPENDENT_REQUIREMENTS_BY_HEX: Dict[str, Dict[str, WitnessRule]] = copy.deepcopy(
|
||||
self.REFERENCE_LOGIC.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX
|
||||
)
|
||||
self.REQUIREMENTS_BY_HEX: Dict[str, WitnessRule] = {}
|
||||
|
||||
self.EVENT_ITEM_PAIRS: Dict[str, str] = {}
|
||||
self.COMPLETELY_DISABLED_ENTITIES: Set[str] = set()
|
||||
self.DISABLE_EVERYTHING_BEHIND: Set[str] = set()
|
||||
self.PRECOMPLETED_LOCATIONS: Set[str] = set()
|
||||
self.EXCLUDED_LOCATIONS: Set[str] = set()
|
||||
self.ADDED_CHECKS: Set[str] = set()
|
||||
self.VICTORY_LOCATION = "0x0356B"
|
||||
|
||||
self.ALWAYS_EVENT_NAMES_BY_HEX = {
|
||||
"0x00509": "+1 Laser (Symmetry Laser)",
|
||||
"0x012FB": "+1 Laser (Desert Laser)",
|
||||
"0x09F98": "Desert Laser Redirection",
|
||||
"0x01539": "+1 Laser (Quarry Laser)",
|
||||
"0x181B3": "+1 Laser (Shadows Laser)",
|
||||
"0x014BB": "+1 Laser (Keep Laser)",
|
||||
"0x17C65": "+1 Laser (Monastery Laser)",
|
||||
"0x032F9": "+1 Laser (Town Laser)",
|
||||
"0x00274": "+1 Laser (Jungle Laser)",
|
||||
"0x0C2B2": "+1 Laser (Bunker Laser)",
|
||||
"0x00BF6": "+1 Laser (Swamp Laser)",
|
||||
"0x028A4": "+1 Laser (Treehouse Laser)",
|
||||
"0x17C34": "Mountain Entry",
|
||||
"0xFFF00": "Bottom Floor Discard Turns On",
|
||||
}
|
||||
|
||||
self.USED_EVENT_NAMES_BY_HEX: Dict[str, str] = {}
|
||||
self.CONDITIONAL_EVENTS: Dict[Tuple[str, str], str] = {}
|
||||
|
||||
# The basic requirements to solve each entity come from StaticWitnessLogic.
|
||||
# However, for any given world, the options (e.g. which item shuffles are enabled) affect the requirements.
|
||||
self.make_options_adjustments(world)
|
||||
self.determine_unrequired_entities(world)
|
||||
self.find_unsolvable_entities(world)
|
||||
|
||||
# After we have adjusted the raw requirements, we perform a dependency reduction for the entity requirements.
|
||||
# This will make the access conditions way faster, instead of recursively checking dependent entities each time.
|
||||
self.make_dependency_reduced_checklist()
|
||||
|
||||
# Finalize which items actually exist in the MultiWorld and which get grouped into progressive items.
|
||||
self.finalize_items()
|
||||
|
||||
# Create event-item pairs for specific panels in the game.
|
||||
self.make_event_panel_lists()
|
||||
|
||||
def reduce_req_within_region(self, entity_hex: str) -> WitnessRule:
|
||||
"""
|
||||
Panels in this game often only turn on when other panels are solved.
|
||||
@@ -77,9 +167,9 @@ class WitnessPlayerLogic:
|
||||
|
||||
# For the requirement of an entity, we consider two things:
|
||||
# 1. Any items this entity needs (e.g. Symbols or Door Items)
|
||||
these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex].get("items", frozenset({frozenset()}))
|
||||
these_items: WitnessRule = self.DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex].get("items", frozenset({frozenset()}))
|
||||
# 2. Any entities that this entity depends on (e.g. one panel powering on the next panel in a set)
|
||||
these_panels = self.DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex]["entities"]
|
||||
these_panels: WitnessRule = self.DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex]["entities"]
|
||||
|
||||
# Remove any items that don't actually exist in the settings (e.g. Symbol Shuffle turned off)
|
||||
these_items = frozenset({
|
||||
@@ -91,47 +181,49 @@ class WitnessPlayerLogic:
|
||||
for subset in these_items:
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(subset)
|
||||
|
||||
# If this entity is opened by a door item that exists in the itempool, add that item to its requirements.
|
||||
# Also, remove any original power requirements this entity might have had.
|
||||
# Handle door entities (door shuffle)
|
||||
if entity_hex in self.DOOR_ITEMS_BY_ID:
|
||||
# If this entity is opened by a door item that exists in the itempool, add that item to its requirements.
|
||||
door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[entity_hex]})
|
||||
|
||||
for dependent_item in door_items:
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(dependent_item)
|
||||
|
||||
all_options = logical_and_witness_rules([door_items, these_items])
|
||||
these_items = logical_and_witness_rules([door_items, these_items])
|
||||
|
||||
# If this entity is not an EP, and it has an associated door item, ignore the original power dependencies
|
||||
if static_witness_logic.ENTITIES_BY_HEX[entity_hex]["entityType"] != "EP":
|
||||
# A door entity is opened by its door item instead of previous entities powering it.
|
||||
# That means we need to ignore any dependent requirements.
|
||||
# However, there are some entities that depend on other entities because of an environmental reason.
|
||||
# Those requirements need to be preserved even in door shuffle.
|
||||
entity_dependencies_need_to_be_preserved = (
|
||||
# EPs keep all their entity dependencies
|
||||
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 entity_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels):
|
||||
these_items = all_options
|
||||
|
||||
or entity_hex == "0x28A0D" and not any("0x28998" in option for option in these_panels)
|
||||
# Another dependency that is not power-based: The Symmetry Island Upper Panel latches
|
||||
elif entity_hex == "0x1C349":
|
||||
these_items = all_options
|
||||
or entity_hex == "0x1C349"
|
||||
)
|
||||
|
||||
else:
|
||||
return frozenset(all_options)
|
||||
|
||||
else:
|
||||
these_items = all_options
|
||||
# If this is not one of those special cases, solving this door entity only needs its own item requirement.
|
||||
# Dependent entities from these_panels are ignored, and we just return these_items directly.
|
||||
if not entity_dependencies_need_to_be_preserved:
|
||||
return these_items
|
||||
|
||||
# Now that we have item requirements and entity dependencies, it's time for the dependency reduction.
|
||||
|
||||
# For each entity that this entity depends on (e.g. a panel turning on another panel),
|
||||
# Add that entities requirements to this entity.
|
||||
# If there are multiple options, consider each, and then or-chain them.
|
||||
all_options = list()
|
||||
all_options = []
|
||||
|
||||
for option in these_panels:
|
||||
dependent_items_for_option = frozenset({frozenset()})
|
||||
dependent_items_for_option: WitnessRule = frozenset({frozenset()})
|
||||
|
||||
# For each entity in this option, resolve it to its actual requirement.
|
||||
for option_entity in option:
|
||||
dep_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX.get(option_entity)
|
||||
dep_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX.get(option_entity, {})
|
||||
|
||||
if option_entity in {"7 Lasers", "11 Lasers", "7 Lasers + Redirect", "11 Lasers + Redirect",
|
||||
"PP2 Weirdness", "Theater to Tunnels"}:
|
||||
@@ -525,13 +617,16 @@ class WitnessPlayerLogic:
|
||||
current_adjustment_type = line[:-1]
|
||||
continue
|
||||
|
||||
if current_adjustment_type is None:
|
||||
raise ValueError(f"Adjustment lineset {adjustment_lineset} is malformed")
|
||||
|
||||
self.make_single_adjustment(current_adjustment_type, line)
|
||||
|
||||
for entity_id in self.COMPLETELY_DISABLED_ENTITIES:
|
||||
if entity_id in self.DOOR_ITEMS_BY_ID:
|
||||
del self.DOOR_ITEMS_BY_ID[entity_id]
|
||||
|
||||
def discover_reachable_regions(self):
|
||||
def discover_reachable_regions(self) -> Set[str]:
|
||||
"""
|
||||
Some options disable panels or remove specific items.
|
||||
This can make entire regions completely unreachable, because all their incoming connections are invalid.
|
||||
@@ -640,7 +735,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
# Check each traversal option individually
|
||||
for option in connection[1]:
|
||||
individual_entity_requirements = []
|
||||
individual_entity_requirements: List[WitnessRule] = []
|
||||
for entity in option:
|
||||
# If a connection requires solving a disabled entity, it is not valid.
|
||||
if not self.solvability_guaranteed(entity) or entity in self.DISABLE_EVERYTHING_BEHIND:
|
||||
@@ -664,7 +759,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
return logical_or_witness_rules(all_possibilities)
|
||||
|
||||
def make_dependency_reduced_checklist(self):
|
||||
def make_dependency_reduced_checklist(self) -> None:
|
||||
"""
|
||||
Every entity has a requirement. This requirement may involve other entities.
|
||||
Example: Solving a panel powers a cable, and that cable turns on the next panel.
|
||||
@@ -679,12 +774,12 @@ class WitnessPlayerLogic:
|
||||
|
||||
# Requirements are cached per entity. However, we might redo the whole reduction process multiple times.
|
||||
# So, we first clear this cache.
|
||||
self.REQUIREMENTS_BY_HEX = dict()
|
||||
self.REQUIREMENTS_BY_HEX = {}
|
||||
|
||||
# We also clear any data structures that we might have filled in a previous dependency reduction
|
||||
self.REQUIREMENTS_BY_HEX = dict()
|
||||
self.USED_EVENT_NAMES_BY_HEX = dict()
|
||||
self.CONNECTIONS_BY_REGION_NAME = dict()
|
||||
self.REQUIREMENTS_BY_HEX = {}
|
||||
self.USED_EVENT_NAMES_BY_HEX = {}
|
||||
self.CONNECTIONS_BY_REGION_NAME = {}
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI = set()
|
||||
|
||||
# Make independent requirements for entities
|
||||
@@ -695,22 +790,18 @@ class WitnessPlayerLogic:
|
||||
|
||||
# Make independent region connection requirements based on the entities they require
|
||||
for region, connections in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL.items():
|
||||
self.CONNECTIONS_BY_REGION_NAME[region] = []
|
||||
|
||||
new_connections = []
|
||||
new_connections = set()
|
||||
|
||||
for connection in connections:
|
||||
overall_requirement = self.reduce_connection_requirement(connection)
|
||||
|
||||
# If there is a way to use this connection, add it.
|
||||
if overall_requirement:
|
||||
new_connections.append((connection[0], overall_requirement))
|
||||
new_connections.add((connection[0], overall_requirement))
|
||||
|
||||
# If there are any usable outgoing connections from this region, add them.
|
||||
if new_connections:
|
||||
self.CONNECTIONS_BY_REGION_NAME[region] = new_connections
|
||||
self.CONNECTIONS_BY_REGION_NAME[region] = new_connections
|
||||
|
||||
def finalize_items(self):
|
||||
def finalize_items(self) -> None:
|
||||
"""
|
||||
Finalise which items are used in the world, and handle their progressive versions.
|
||||
"""
|
||||
@@ -808,8 +899,7 @@ class WitnessPlayerLogic:
|
||||
if entity_hex not in self.USED_EVENT_NAMES_BY_HEX:
|
||||
warning(f'Entity "{name}" does not have an associated event name.')
|
||||
self.USED_EVENT_NAMES_BY_HEX[entity_hex] = name + " Event"
|
||||
pair = (name, self.USED_EVENT_NAMES_BY_HEX[entity_hex])
|
||||
return pair
|
||||
return (name, self.USED_EVENT_NAMES_BY_HEX[entity_hex])
|
||||
|
||||
def make_event_panel_lists(self) -> None:
|
||||
"""
|
||||
@@ -828,85 +918,3 @@ class WitnessPlayerLogic:
|
||||
for panel in self.USED_EVENT_NAMES_BY_HEX:
|
||||
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]) -> None:
|
||||
self.YAML_DISABLED_LOCATIONS = disabled_locations
|
||||
self.YAML_ADDED_ITEMS = start_inv
|
||||
|
||||
self.EVENT_PANELS_FROM_PANELS = set()
|
||||
self.EVENT_PANELS_FROM_REGIONS = set()
|
||||
|
||||
self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES = set()
|
||||
|
||||
self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY = set()
|
||||
|
||||
self.UNREACHABLE_REGIONS = set()
|
||||
|
||||
self.THEORETICAL_ITEMS = set()
|
||||
self.THEORETICAL_ITEMS_NO_MULTI = set()
|
||||
self.MULTI_AMOUNTS = defaultdict(lambda: 1)
|
||||
self.MULTI_LISTS = dict()
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI = set()
|
||||
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = set()
|
||||
self.DOOR_ITEMS_BY_ID: Dict[str, List[str]] = {}
|
||||
self.STARTING_INVENTORY = set()
|
||||
|
||||
self.DIFFICULTY = world.options.puzzle_randomization
|
||||
|
||||
if self.DIFFICULTY == "sigma_normal":
|
||||
self.REFERENCE_LOGIC = static_witness_logic.sigma_normal
|
||||
elif self.DIFFICULTY == "sigma_expert":
|
||||
self.REFERENCE_LOGIC = static_witness_logic.sigma_expert
|
||||
elif self.DIFFICULTY == "none":
|
||||
self.REFERENCE_LOGIC = static_witness_logic.vanilla
|
||||
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL = copy.deepcopy(
|
||||
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
||||
)
|
||||
self.CONNECTIONS_BY_REGION_NAME = dict()
|
||||
self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.deepcopy(self.REFERENCE_LOGIC.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX)
|
||||
self.REQUIREMENTS_BY_HEX = dict()
|
||||
|
||||
self.EVENT_ITEM_PAIRS = dict()
|
||||
self.COMPLETELY_DISABLED_ENTITIES = set()
|
||||
self.DISABLE_EVERYTHING_BEHIND = set()
|
||||
self.PRECOMPLETED_LOCATIONS = set()
|
||||
self.EXCLUDED_LOCATIONS = set()
|
||||
self.ADDED_CHECKS = set()
|
||||
self.VICTORY_LOCATION: str
|
||||
|
||||
self.ALWAYS_EVENT_NAMES_BY_HEX = {
|
||||
"0x00509": "+1 Laser (Symmetry Laser)",
|
||||
"0x012FB": "+1 Laser (Desert Laser)",
|
||||
"0x09F98": "Desert Laser Redirection",
|
||||
"0x01539": "+1 Laser (Quarry Laser)",
|
||||
"0x181B3": "+1 Laser (Shadows Laser)",
|
||||
"0x014BB": "+1 Laser (Keep Laser)",
|
||||
"0x17C65": "+1 Laser (Monastery Laser)",
|
||||
"0x032F9": "+1 Laser (Town Laser)",
|
||||
"0x00274": "+1 Laser (Jungle Laser)",
|
||||
"0x0C2B2": "+1 Laser (Bunker Laser)",
|
||||
"0x00BF6": "+1 Laser (Swamp Laser)",
|
||||
"0x028A4": "+1 Laser (Treehouse Laser)",
|
||||
"0x17C34": "Mountain Entry",
|
||||
"0xFFF00": "Bottom Floor Discard Turns On",
|
||||
}
|
||||
|
||||
self.USED_EVENT_NAMES_BY_HEX = {}
|
||||
self.CONDITIONAL_EVENTS = {}
|
||||
|
||||
# The basic requirements to solve each entity come from StaticWitnessLogic.
|
||||
# However, for any given world, the options (e.g. which item shuffles are enabled) affect the requirements.
|
||||
self.make_options_adjustments(world)
|
||||
self.determine_unrequired_entities(world)
|
||||
self.find_unsolvable_entities(world)
|
||||
|
||||
# After we have adjusted the raw requirements, we perform a dependency reduction for the entity requirements.
|
||||
# This will make the access conditions way faster, instead of recursively checking dependent entities each time.
|
||||
self.make_dependency_reduced_checklist()
|
||||
|
||||
# Finalize which items actually exist in the MultiWorld and which get grouped into progressive items.
|
||||
self.finalize_items()
|
||||
|
||||
# Create event-item pairs for specific panels in the game.
|
||||
self.make_event_panel_lists()
|
||||
|
||||
Reference in New Issue
Block a user