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:
NewSoupVi
2024-07-02 23:59:26 +02:00
committed by GitHub
parent b6925c593e
commit 93617fa546
12 changed files with 299 additions and 269 deletions

View File

@@ -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()