mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
The Witness: Change Regions, Areas and Connections from Dict[str, Any] to dataclasses&NamedTuples (#4415)
* Change Regions, Areas and Connections to dataclasses/NamedTuples * Move to new file * we do a little renaming * Purge the 'lambda' naming in favor of 'rule' or 'WitnessRule' * missed one * unnecessary change * omega oops * NOOOOOOOO * Merge error * mypy thing
This commit is contained in:
33
worlds/witness/data/definition_classes.py
Normal file
33
worlds/witness/data/definition_classes.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import FrozenSet, List, NamedTuple
|
||||||
|
|
||||||
|
# A WitnessRule is just an or-chain of and-conditions.
|
||||||
|
# It represents the set of all options that could fulfill this requirement.
|
||||||
|
# E.g. if something requires "Dots or (Shapers and Stars)", it'd be represented as: {{"Dots"}, {"Shapers, "Stars"}}
|
||||||
|
# {} is an unusable requirement.
|
||||||
|
# {{}} is an always usable requirement.
|
||||||
|
WitnessRule = FrozenSet[FrozenSet[str]]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AreaDefinition:
|
||||||
|
name: str
|
||||||
|
regions: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RegionDefinition:
|
||||||
|
name: str
|
||||||
|
short_name: str
|
||||||
|
area: AreaDefinition
|
||||||
|
logical_entities: List[str] = field(default_factory=list)
|
||||||
|
physical_entities: List[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionDefinition(NamedTuple):
|
||||||
|
target_region: str
|
||||||
|
traversal_rule: WitnessRule
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_be_traversed(self) -> bool:
|
||||||
|
return bool(self.traversal_rule)
|
@@ -486,5 +486,5 @@ for key, item in ALL_LOCATIONS_TO_IDS.items():
|
|||||||
ALL_LOCATIONS_TO_ID[key] = item
|
ALL_LOCATIONS_TO_ID[key] = item
|
||||||
|
|
||||||
for loc in ALL_LOCATIONS_TO_IDS:
|
for loc in ALL_LOCATIONS_TO_IDS:
|
||||||
area = static_witness_logic.ENTITIES_BY_NAME[loc]["area"]["name"]
|
area = static_witness_logic.ENTITIES_BY_NAME[loc]["area"].name
|
||||||
AREA_LOCATION_GROUPS.setdefault(area, set()).add(loc)
|
AREA_LOCATION_GROUPS.setdefault(area, set()).add(loc)
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
from typing import Any, Dict, List, Optional, Set, Tuple
|
from typing import Any, Dict, FrozenSet, List, Optional, Set
|
||||||
|
|
||||||
from Utils import cache_argsless
|
from Utils import cache_argsless
|
||||||
|
|
||||||
|
from .definition_classes import AreaDefinition, ConnectionDefinition, RegionDefinition, WitnessRule
|
||||||
from .item_definition_classes import (
|
from .item_definition_classes import (
|
||||||
CATEGORY_NAME_MAPPINGS,
|
CATEGORY_NAME_MAPPINGS,
|
||||||
DoorItemDefinition,
|
DoorItemDefinition,
|
||||||
@@ -13,7 +14,6 @@ from .item_definition_classes import (
|
|||||||
)
|
)
|
||||||
from .settings.easter_eggs import EASTER_EGGS
|
from .settings.easter_eggs import EASTER_EGGS
|
||||||
from .utils import (
|
from .utils import (
|
||||||
WitnessRule,
|
|
||||||
define_new_region,
|
define_new_region,
|
||||||
get_items,
|
get_items,
|
||||||
get_sigma_expert_logic,
|
get_sigma_expert_logic,
|
||||||
@@ -21,7 +21,7 @@ from .utils import (
|
|||||||
get_umbra_variety_logic,
|
get_umbra_variety_logic,
|
||||||
get_vanilla_logic,
|
get_vanilla_logic,
|
||||||
logical_or_witness_rules,
|
logical_or_witness_rules,
|
||||||
parse_lambda,
|
parse_witness_rule,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -31,10 +31,10 @@ class StaticWitnessLogicObj:
|
|||||||
lines = get_sigma_normal_logic()
|
lines = get_sigma_normal_logic()
|
||||||
|
|
||||||
# All regions with a list of panels in them and the connections to other regions, before logic adjustments
|
# All regions with a list of panels in them and the connections to other regions, before logic adjustments
|
||||||
self.ALL_REGIONS_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
self.ALL_REGIONS_BY_NAME: Dict[str, RegionDefinition] = {}
|
||||||
self.ALL_AREAS_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
self.ALL_AREAS_BY_NAME: Dict[str, AreaDefinition] = {}
|
||||||
self.CONNECTIONS_WITH_DUPLICATES: Dict[str, Dict[str, Set[WitnessRule]]] = defaultdict(lambda: defaultdict(set))
|
self.CONNECTIONS_WITH_DUPLICATES: Dict[str, List[ConnectionDefinition]] = defaultdict(list)
|
||||||
self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = {}
|
self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, List[ConnectionDefinition]] = {}
|
||||||
|
|
||||||
self.ENTITIES_BY_HEX: Dict[str, Dict[str, Any]] = {}
|
self.ENTITIES_BY_HEX: Dict[str, Dict[str, Any]] = {}
|
||||||
self.ENTITIES_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
self.ENTITIES_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
||||||
@@ -55,15 +55,15 @@ class StaticWitnessLogicObj:
|
|||||||
area_counts: Dict[str, int] = Counter()
|
area_counts: Dict[str, int] = Counter()
|
||||||
for region_name, entity_amount in EASTER_EGGS.items():
|
for region_name, entity_amount in EASTER_EGGS.items():
|
||||||
region_object = self.ALL_REGIONS_BY_NAME[region_name]
|
region_object = self.ALL_REGIONS_BY_NAME[region_name]
|
||||||
correct_area = region_object["area"]
|
correct_area = region_object.area
|
||||||
|
|
||||||
for _ in range(entity_amount):
|
for _ in range(entity_amount):
|
||||||
location_id = 160200 + egg_counter
|
location_id = 160200 + egg_counter
|
||||||
entity_hex = hex(0xEE000 + egg_counter)
|
entity_hex = hex(0xEE000 + egg_counter)
|
||||||
egg_counter += 1
|
egg_counter += 1
|
||||||
|
|
||||||
area_counts[correct_area["name"]] += 1
|
area_counts[correct_area.name] += 1
|
||||||
full_entity_name = f"{correct_area['name']} Easter Egg {area_counts[correct_area['name']]}"
|
full_entity_name = f"{correct_area.name} Easter Egg {area_counts[correct_area.name]}"
|
||||||
|
|
||||||
self.ENTITIES_BY_HEX[entity_hex] = {
|
self.ENTITIES_BY_HEX[entity_hex] = {
|
||||||
"checkName": full_entity_name,
|
"checkName": full_entity_name,
|
||||||
@@ -81,11 +81,11 @@ class StaticWitnessLogicObj:
|
|||||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||||
"entities": frozenset({frozenset({})})
|
"entities": frozenset({frozenset({})})
|
||||||
}
|
}
|
||||||
region_object["entities"].append(entity_hex)
|
region_object.logical_entities.append(entity_hex)
|
||||||
region_object["physical_entities"].append(entity_hex)
|
region_object.physical_entities.append(entity_hex)
|
||||||
|
|
||||||
easter_egg_region = self.ALL_REGIONS_BY_NAME["Easter Eggs"]
|
easter_egg_region = self.ALL_REGIONS_BY_NAME["Easter Eggs"]
|
||||||
easter_egg_area = easter_egg_region["area"]
|
easter_egg_area = easter_egg_region.area
|
||||||
for i in range(sum(EASTER_EGGS.values())):
|
for i in range(sum(EASTER_EGGS.values())):
|
||||||
location_id = 160000 + i
|
location_id = 160000 + i
|
||||||
entity_hex = hex(0xEE200 + i)
|
entity_hex = hex(0xEE200 + i)
|
||||||
@@ -111,19 +111,15 @@ class StaticWitnessLogicObj:
|
|||||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||||
"entities": frozenset({frozenset({})})
|
"entities": frozenset({frozenset({})})
|
||||||
}
|
}
|
||||||
easter_egg_region["entities"].append(entity_hex)
|
easter_egg_region.logical_entities.append(entity_hex)
|
||||||
easter_egg_region["physical_entities"].append(entity_hex)
|
easter_egg_region.physical_entities.append(entity_hex)
|
||||||
|
|
||||||
def read_logic_file(self, lines: List[str]) -> None:
|
def read_logic_file(self, lines: List[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Reads the logic file and does the initial population of data structures
|
Reads the logic file and does the initial population of data structures
|
||||||
"""
|
"""
|
||||||
|
current_area = AreaDefinition("Misc")
|
||||||
current_region = {}
|
current_region = RegionDefinition("Fake", "Fake", current_area) # Unused, but makes PyCharm & mypy shut up
|
||||||
current_area: Dict[str, Any] = {
|
|
||||||
"name": "Misc",
|
|
||||||
"regions": [],
|
|
||||||
}
|
|
||||||
self.ALL_AREAS_BY_NAME["Misc"] = current_area
|
self.ALL_AREAS_BY_NAME["Misc"] = current_area
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
@@ -133,19 +129,16 @@ class StaticWitnessLogicObj:
|
|||||||
if line[-1] == ":":
|
if line[-1] == ":":
|
||||||
new_region_and_connections = define_new_region(line, current_area)
|
new_region_and_connections = define_new_region(line, current_area)
|
||||||
current_region = new_region_and_connections[0]
|
current_region = new_region_and_connections[0]
|
||||||
region_name = current_region["name"]
|
region_name = current_region.name
|
||||||
self.ALL_REGIONS_BY_NAME[region_name] = current_region
|
self.ALL_REGIONS_BY_NAME[region_name] = current_region
|
||||||
for connection in new_region_and_connections[1]:
|
for connection in new_region_and_connections[1]:
|
||||||
self.CONNECTIONS_WITH_DUPLICATES[region_name][connection[0]].add(connection[1])
|
self.CONNECTIONS_WITH_DUPLICATES[region_name].append(connection)
|
||||||
current_area["regions"].append(region_name)
|
current_area.regions.append(region_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if line[0] == "=":
|
if line[0] == "=":
|
||||||
area_name = line[2:-2]
|
area_name = line[2:-2]
|
||||||
current_area = {
|
current_area = AreaDefinition(area_name, [])
|
||||||
"name": area_name,
|
|
||||||
"regions": [],
|
|
||||||
}
|
|
||||||
self.ALL_AREAS_BY_NAME[area_name] = current_area
|
self.ALL_AREAS_BY_NAME[area_name] = current_area
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -158,9 +151,9 @@ class StaticWitnessLogicObj:
|
|||||||
entity_hex = entity_name_full[0:7]
|
entity_hex = entity_name_full[0:7]
|
||||||
entity_name = entity_name_full[9:-1]
|
entity_name = entity_name_full[9:-1]
|
||||||
|
|
||||||
required_panel_lambda = line_split.pop(0)
|
entity_requirement_string = line_split.pop(0)
|
||||||
|
|
||||||
full_entity_name = current_region["shortName"] + " " + entity_name
|
full_entity_name = current_region.short_name + " " + entity_name
|
||||||
|
|
||||||
if location_id == "Door" or location_id == "Laser":
|
if location_id == "Door" or location_id == "Laser":
|
||||||
self.ENTITIES_BY_HEX[entity_hex] = {
|
self.ENTITIES_BY_HEX[entity_hex] = {
|
||||||
@@ -177,18 +170,18 @@ class StaticWitnessLogicObj:
|
|||||||
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
|
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
|
||||||
|
|
||||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||||
"entities": parse_lambda(required_panel_lambda)
|
"entities": parse_witness_rule(entity_requirement_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Lasers and Doors exist in a region, but don't have a regional *requirement*
|
# Lasers and Doors exist in a region, but don't have a regional *requirement*
|
||||||
# If a laser is activated, you don't need to physically walk up to it for it to count
|
# If a laser is activated, you don't need to physically walk up to it for it to count
|
||||||
# As such, logically, they behave more as if they were part of the "Entry" region
|
# As such, logically, they behave more as if they were part of the "Entry" region
|
||||||
self.ALL_REGIONS_BY_NAME["Entry"]["entities"].append(entity_hex)
|
self.ALL_REGIONS_BY_NAME["Entry"].logical_entities.append(entity_hex)
|
||||||
# However, it will also be important to keep track of their physical location for postgame purposes.
|
# However, it will also be important to keep track of their physical location for postgame purposes.
|
||||||
current_region["physical_entities"].append(entity_hex)
|
current_region.physical_entities.append(entity_hex)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
required_item_lambda = line_split.pop(0)
|
item_requirement_string = line_split.pop(0)
|
||||||
|
|
||||||
laser_names = {
|
laser_names = {
|
||||||
"Laser",
|
"Laser",
|
||||||
@@ -224,18 +217,18 @@ class StaticWitnessLogicObj:
|
|||||||
entity_type = "Panel"
|
entity_type = "Panel"
|
||||||
location_type = "General"
|
location_type = "General"
|
||||||
|
|
||||||
required_items = parse_lambda(required_item_lambda)
|
required_items = parse_witness_rule(item_requirement_string)
|
||||||
required_panels = parse_lambda(required_panel_lambda)
|
required_entities = parse_witness_rule(entity_requirement_string)
|
||||||
|
|
||||||
required_items = frozenset(required_items)
|
required_items = frozenset(required_items)
|
||||||
|
|
||||||
requirement = {
|
requirement = {
|
||||||
"entities": required_panels,
|
"entities": required_entities,
|
||||||
"items": required_items
|
"items": required_items
|
||||||
}
|
}
|
||||||
|
|
||||||
if entity_type == "Obelisk Side":
|
if entity_type == "Obelisk Side":
|
||||||
eps = set(next(iter(required_panels)))
|
eps = set(next(iter(required_entities)))
|
||||||
eps -= {"Theater to Tunnels"}
|
eps -= {"Theater to Tunnels"}
|
||||||
|
|
||||||
eps_ints = {int(h, 16) for h in eps}
|
eps_ints = {int(h, 16) for h in eps}
|
||||||
@@ -260,39 +253,43 @@ class StaticWitnessLogicObj:
|
|||||||
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
|
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
|
||||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = requirement
|
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = requirement
|
||||||
|
|
||||||
current_region["entities"].append(entity_hex)
|
current_region.logical_entities.append(entity_hex)
|
||||||
current_region["physical_entities"].append(entity_hex)
|
current_region.physical_entities.append(entity_hex)
|
||||||
|
|
||||||
self.add_easter_eggs()
|
self.add_easter_eggs()
|
||||||
|
|
||||||
def reverse_connection(self, source_region: str, connection: Tuple[str, Set[WitnessRule]]) -> None:
|
def reverse_connection(self, source_region: str, connection: ConnectionDefinition) -> None:
|
||||||
target = connection[0]
|
|
||||||
traversal_options = connection[1]
|
|
||||||
|
|
||||||
# Reverse this connection with all its possibilities, except the ones marked as "OneWay".
|
# Reverse this connection with all its possibilities, except the ones marked as "OneWay".
|
||||||
for requirement in traversal_options:
|
remaining_options: Set[FrozenSet[str]] = set()
|
||||||
remaining_options = set()
|
for sub_option in connection.traversal_rule:
|
||||||
for option in requirement:
|
if not any(req == "TrueOneWay" for req in sub_option):
|
||||||
if not any(req == "TrueOneWay" for req in option):
|
remaining_options.add(sub_option)
|
||||||
remaining_options.add(option)
|
|
||||||
|
|
||||||
if remaining_options:
|
reversed_connection = ConnectionDefinition(source_region, frozenset(remaining_options))
|
||||||
self.CONNECTIONS_WITH_DUPLICATES[target][source_region].add(frozenset(remaining_options))
|
if reversed_connection.can_be_traversed:
|
||||||
|
self.CONNECTIONS_WITH_DUPLICATES[connection.target_region].append(reversed_connection)
|
||||||
|
|
||||||
def reverse_connections(self) -> None:
|
def reverse_connections(self) -> None:
|
||||||
# Iterate all connections
|
# Iterate all connections
|
||||||
for region_name, connections in list(self.CONNECTIONS_WITH_DUPLICATES.items()):
|
for region_name, connections in list(self.CONNECTIONS_WITH_DUPLICATES.items()):
|
||||||
for connection in connections.items():
|
for connection in connections:
|
||||||
self.reverse_connection(region_name, connection)
|
self.reverse_connection(region_name, connection)
|
||||||
|
|
||||||
def combine_connections(self) -> None:
|
def combine_connections(self) -> None:
|
||||||
# All regions need to be present, and this dict is copied later - Thus, defaultdict is not the correct choice.
|
# All regions need to be present, and this dict is copied later - Thus, defaultdict is not the correct choice.
|
||||||
self.STATIC_CONNECTIONS_BY_REGION_NAME = {region_name: set() for region_name in self.ALL_REGIONS_BY_NAME}
|
self.STATIC_CONNECTIONS_BY_REGION_NAME = {region_name: [] for region_name in self.ALL_REGIONS_BY_NAME}
|
||||||
|
|
||||||
for source, connections in self.CONNECTIONS_WITH_DUPLICATES.items():
|
for source, connections in self.CONNECTIONS_WITH_DUPLICATES.items():
|
||||||
for target, requirement in connections.items():
|
# Organize rules by target region
|
||||||
combined_req = logical_or_witness_rules(requirement)
|
traversal_options_by_target_region = defaultdict(list)
|
||||||
self.STATIC_CONNECTIONS_BY_REGION_NAME[source].add((target, combined_req))
|
for target_region, traversal_option in connections:
|
||||||
|
traversal_options_by_target_region[target_region].append(traversal_option)
|
||||||
|
|
||||||
|
# Combine connections to the same target region into one connection
|
||||||
|
for target, traversal_rules in traversal_options_by_target_region.items():
|
||||||
|
combined_rule = logical_or_witness_rules(traversal_rules)
|
||||||
|
combined_connection = ConnectionDefinition(target, combined_rule)
|
||||||
|
self.STATIC_CONNECTIONS_BY_REGION_NAME[source].append(combined_connection)
|
||||||
|
|
||||||
|
|
||||||
# Item data parsed from WitnessItems.txt
|
# Item data parsed from WitnessItems.txt
|
||||||
|
@@ -2,17 +2,12 @@ from datetime import date
|
|||||||
from math import floor
|
from math import floor
|
||||||
from pkgutil import get_data
|
from pkgutil import get_data
|
||||||
from random import Random
|
from random import Random
|
||||||
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar
|
from typing import Collection, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar
|
||||||
|
|
||||||
|
from .definition_classes import AreaDefinition, ConnectionDefinition, RegionDefinition, WitnessRule
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
# A WitnessRule is just an or-chain of and-conditions.
|
|
||||||
# It represents the set of all options that could fulfill this requirement.
|
|
||||||
# E.g. if something requires "Dots or (Shapers and Stars)", it'd be represented as: {{"Dots"}, {"Shapers, "Stars"}}
|
|
||||||
# {} is an unusable requirement.
|
|
||||||
# {{}} is an always usable requirement.
|
|
||||||
WitnessRule = FrozenSet[FrozenSet[str]]
|
|
||||||
|
|
||||||
|
|
||||||
def cast_not_none(value: Optional[T]) -> T:
|
def cast_not_none(value: Optional[T]) -> T:
|
||||||
assert value is not None
|
assert value is not None
|
||||||
@@ -62,7 +57,7 @@ def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]:
|
|||||||
return rounded_output
|
return rounded_output
|
||||||
|
|
||||||
|
|
||||||
def define_new_region(region_string: str, area: dict[str, Any]) -> Tuple[Dict[str, Any], Set[Tuple[str, WitnessRule]]]:
|
def define_new_region(region_string: str, area: AreaDefinition) -> Tuple[RegionDefinition, List[ConnectionDefinition]]:
|
||||||
"""
|
"""
|
||||||
Returns a region object by parsing a line in the logic file
|
Returns a region object by parsing a line in the logic file
|
||||||
"""
|
"""
|
||||||
@@ -77,35 +72,28 @@ def define_new_region(region_string: str, area: dict[str, Any]) -> Tuple[Dict[st
|
|||||||
region_name = region_name_split[0]
|
region_name = region_name_split[0]
|
||||||
region_name_simple = region_name_split[1][:-1]
|
region_name_simple = region_name_split[1][:-1]
|
||||||
|
|
||||||
options = set()
|
options = []
|
||||||
|
|
||||||
for _ in range(len(line_split) // 2):
|
for _ in range(len(line_split) // 2):
|
||||||
connected_region = line_split.pop(0)
|
connected_region = line_split.pop(0)
|
||||||
corresponding_lambda = line_split.pop(0)
|
traversal_rule_string = line_split.pop(0)
|
||||||
|
|
||||||
options.add(
|
options.append(ConnectionDefinition(connected_region, parse_witness_rule(traversal_rule_string)))
|
||||||
(connected_region, parse_lambda(corresponding_lambda))
|
|
||||||
)
|
region_obj = RegionDefinition(region_name, region_name_simple, area)
|
||||||
|
|
||||||
region_obj = {
|
|
||||||
"name": region_name,
|
|
||||||
"shortName": region_name_simple,
|
|
||||||
"entities": [],
|
|
||||||
"physical_entities": [],
|
|
||||||
"area": area,
|
|
||||||
}
|
|
||||||
return region_obj, options
|
return region_obj, options
|
||||||
|
|
||||||
|
|
||||||
def parse_lambda(lambda_string: str) -> WitnessRule:
|
def parse_witness_rule(rule_string: str) -> WitnessRule:
|
||||||
"""
|
"""
|
||||||
Turns a lambda String literal like this: a | b & c
|
Turns a rule string literal like this: a | b & c
|
||||||
into a set of sets like this: {{a}, {b, c}}
|
into a set of sets (called "WitnessRule") like this: {{a}, {b, c}}
|
||||||
The lambda has to be in DNF.
|
The rule string has to be in DNF.
|
||||||
"""
|
"""
|
||||||
if lambda_string == "True":
|
if rule_string == "True":
|
||||||
return frozenset([frozenset()])
|
return frozenset([frozenset()])
|
||||||
split_ands = set(lambda_string.split(" | "))
|
split_ands = set(rule_string.split(" | "))
|
||||||
return frozenset({frozenset(a.split(" & ")) for a in split_ands})
|
return frozenset({frozenset(a.split(" & ")) for a in split_ands})
|
||||||
|
|
||||||
|
|
||||||
|
@@ -129,7 +129,7 @@ class EntityHuntPicker:
|
|||||||
|
|
||||||
eligible_panels_by_area = defaultdict(set)
|
eligible_panels_by_area = defaultdict(set)
|
||||||
for eligible_panel in all_eligible_panels:
|
for eligible_panel in all_eligible_panels:
|
||||||
associated_area = static_witness_logic.ENTITIES_BY_HEX[eligible_panel]["area"]["name"]
|
associated_area = static_witness_logic.ENTITIES_BY_HEX[eligible_panel]["area"].name
|
||||||
eligible_panels_by_area[associated_area].add(eligible_panel)
|
eligible_panels_by_area[associated_area].add(eligible_panel)
|
||||||
|
|
||||||
return all_eligible_panels, eligible_panels_by_area
|
return all_eligible_panels, eligible_panels_by_area
|
||||||
|
@@ -18,7 +18,7 @@ if __name__ == "__main__":
|
|||||||
for entity_id, entity_object in static_witness_logic.ENTITIES_BY_HEX.items():
|
for entity_id, entity_object in static_witness_logic.ENTITIES_BY_HEX.items():
|
||||||
location_id = entity_object["id"]
|
location_id = entity_object["id"]
|
||||||
|
|
||||||
area = entity_object["area"]["name"]
|
area = entity_object["area"].name
|
||||||
area_to_entity_ids[area].append(entity_id)
|
area_to_entity_ids[area].append(entity_id)
|
||||||
|
|
||||||
if location_id is None:
|
if location_id is None:
|
||||||
|
@@ -464,7 +464,7 @@ 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]]]:
|
def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]], Dict[str, List[Item]]]:
|
||||||
potential_areas = list(static_witness_logic.ALL_AREAS_BY_NAME.keys())
|
potential_areas = list(static_witness_logic.ALL_AREAS_BY_NAME.values())
|
||||||
|
|
||||||
locations_per_area = {}
|
locations_per_area = {}
|
||||||
items_per_area = {}
|
items_per_area = {}
|
||||||
@@ -472,14 +472,14 @@ def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]]
|
|||||||
for area in potential_areas:
|
for area in potential_areas:
|
||||||
regions = [
|
regions = [
|
||||||
world.get_region(region)
|
world.get_region(region)
|
||||||
for region in static_witness_logic.ALL_AREAS_BY_NAME[area]["regions"]
|
for region in area.regions
|
||||||
if region in world.player_regions.created_region_names
|
if region in world.player_regions.created_region_names
|
||||||
]
|
]
|
||||||
locations = [location for region in regions for location in region.get_locations() if not location.is_event]
|
locations = [location for region in regions for location in region.get_locations() if not location.is_event]
|
||||||
|
|
||||||
if locations:
|
if locations:
|
||||||
locations_per_area[area] = locations
|
locations_per_area[area.name] = locations
|
||||||
items_per_area[area] = [location.item for location in locations]
|
items_per_area[area.name] = [location.item for location in locations]
|
||||||
|
|
||||||
return locations_per_area, items_per_area
|
return locations_per_area, items_per_area
|
||||||
|
|
||||||
@@ -516,7 +516,7 @@ def word_area_hint(world: "WitnessWorld", hinted_area: str, area_items: List[Ite
|
|||||||
hunt_panels = None
|
hunt_panels = None
|
||||||
if world.options.victory_condition == "panel_hunt" and hinted_area != "Easter Eggs":
|
if world.options.victory_condition == "panel_hunt" and hinted_area != "Easter Eggs":
|
||||||
hunt_panels = sum(
|
hunt_panels = sum(
|
||||||
static_witness_logic.ENTITIES_BY_HEX[hunt_entity]["area"]["name"] == hinted_area
|
static_witness_logic.ENTITIES_BY_HEX[hunt_entity]["area"].name == hinted_area
|
||||||
for hunt_entity in world.player_logic.HUNT_ENTITIES
|
for hunt_entity in world.player_logic.HUNT_ENTITIES
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -620,7 +620,7 @@ def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int,
|
|||||||
|
|
||||||
already_hinted_locations |= {
|
already_hinted_locations |= {
|
||||||
loc for loc in world.multiworld.get_reachable_locations(state, world.player)
|
loc for loc in world.multiworld.get_reachable_locations(state, world.player)
|
||||||
if loc.address and static_witness_logic.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
|
intended_location_hints = hint_amount - area_hints
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from schema import And, Schema
|
from schema import And, Schema
|
||||||
|
@@ -20,10 +20,10 @@ from collections import defaultdict
|
|||||||
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, cast
|
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, cast
|
||||||
|
|
||||||
from .data import static_logic as static_witness_logic
|
from .data import static_logic as static_witness_logic
|
||||||
|
from .data.definition_classes import ConnectionDefinition, WitnessRule
|
||||||
from .data.item_definition_classes import DoorItemDefinition, ItemCategory, ProgressiveItemDefinition
|
from .data.item_definition_classes import DoorItemDefinition, ItemCategory, ProgressiveItemDefinition
|
||||||
from .data.static_logic import StaticWitnessLogicObj
|
from .data.static_logic import StaticWitnessLogicObj
|
||||||
from .data.utils import (
|
from .data.utils import (
|
||||||
WitnessRule,
|
|
||||||
get_boat,
|
get_boat,
|
||||||
get_caves_except_path_to_challenge_exclusion_list,
|
get_caves_except_path_to_challenge_exclusion_list,
|
||||||
get_complex_additional_panels,
|
get_complex_additional_panels,
|
||||||
@@ -47,7 +47,7 @@ from .data.utils import (
|
|||||||
get_vault_exclusion_list,
|
get_vault_exclusion_list,
|
||||||
logical_and_witness_rules,
|
logical_and_witness_rules,
|
||||||
logical_or_witness_rules,
|
logical_or_witness_rules,
|
||||||
parse_lambda,
|
parse_witness_rule,
|
||||||
)
|
)
|
||||||
from .entity_hunt import EntityHuntPicker
|
from .entity_hunt import EntityHuntPicker
|
||||||
|
|
||||||
@@ -97,10 +97,10 @@ class WitnessPlayerLogic:
|
|||||||
elif self.DIFFICULTY == "none":
|
elif self.DIFFICULTY == "none":
|
||||||
self.REFERENCE_LOGIC = static_witness_logic.vanilla
|
self.REFERENCE_LOGIC = static_witness_logic.vanilla
|
||||||
|
|
||||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy(
|
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL: Dict[str, List[ConnectionDefinition]] = copy.deepcopy(
|
||||||
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
||||||
)
|
)
|
||||||
self.CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = copy.deepcopy(
|
self.CONNECTIONS_BY_REGION_NAME: Dict[str, List[ConnectionDefinition]] = copy.deepcopy(
|
||||||
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME
|
||||||
)
|
)
|
||||||
self.DEPENDENT_REQUIREMENTS_BY_HEX: Dict[str, Dict[str, WitnessRule]] = copy.deepcopy(
|
self.DEPENDENT_REQUIREMENTS_BY_HEX: Dict[str, Dict[str, WitnessRule]] = copy.deepcopy(
|
||||||
@@ -178,7 +178,7 @@ class WitnessPlayerLogic:
|
|||||||
|
|
||||||
entity_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity_hex]
|
entity_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity_hex]
|
||||||
|
|
||||||
if entity_obj["region"] is not None and entity_obj["region"]["name"] in self.UNREACHABLE_REGIONS:
|
if entity_obj["region"] is not None and entity_obj["region"].name in self.UNREACHABLE_REGIONS:
|
||||||
return frozenset()
|
return frozenset()
|
||||||
|
|
||||||
# For the requirement of an entity, we consider two things:
|
# For the requirement of an entity, we consider two things:
|
||||||
@@ -270,7 +270,7 @@ class WitnessPlayerLogic:
|
|||||||
new_items = theoretical_new_items
|
new_items = theoretical_new_items
|
||||||
if dep_obj["region"] and entity_obj["region"] != dep_obj["region"]:
|
if dep_obj["region"] and entity_obj["region"] != dep_obj["region"]:
|
||||||
new_items = frozenset(
|
new_items = frozenset(
|
||||||
frozenset(possibility | {dep_obj["region"]["name"]})
|
frozenset(possibility | {dep_obj["region"].name})
|
||||||
for possibility in new_items
|
for possibility in new_items
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -359,11 +359,11 @@ class WitnessPlayerLogic:
|
|||||||
line_split = line.split(" - ")
|
line_split = line.split(" - ")
|
||||||
|
|
||||||
requirement = {
|
requirement = {
|
||||||
"entities": parse_lambda(line_split[1]),
|
"entities": parse_witness_rule(line_split[1]),
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(line_split) > 2:
|
if len(line_split) > 2:
|
||||||
required_items = parse_lambda(line_split[2])
|
required_items = parse_witness_rule(line_split[2])
|
||||||
items_actually_in_the_game = [
|
items_actually_in_the_game = [
|
||||||
item_name for item_name, item_definition in static_witness_logic.ALL_ITEMS.items()
|
item_name for item_name, item_definition in static_witness_logic.ALL_ITEMS.items()
|
||||||
if item_definition.category is ItemCategory.SYMBOL
|
if item_definition.category is ItemCategory.SYMBOL
|
||||||
@@ -394,26 +394,31 @@ class WitnessPlayerLogic:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if adj_type == "New Connections":
|
if adj_type == "New Connections":
|
||||||
|
# This adjustment type does not actually reverse the connection if it could be reversed.
|
||||||
|
# If needed, this might be added later
|
||||||
line_split = line.split(" - ")
|
line_split = line.split(" - ")
|
||||||
source_region = line_split[0]
|
source_region = line_split[0]
|
||||||
target_region = line_split[1]
|
target_region = line_split[1]
|
||||||
panel_set_string = line_split[2]
|
panel_set_string = line_split[2]
|
||||||
|
|
||||||
for connection in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region]:
|
for connection in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region]:
|
||||||
if connection[0] == target_region:
|
if connection.target_region == target_region:
|
||||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].remove(connection)
|
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].remove(connection)
|
||||||
|
|
||||||
if panel_set_string == "TrueOneWay":
|
if panel_set_string == "TrueOneWay":
|
||||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add(
|
# This means the connection can be completely replaced
|
||||||
(target_region, frozenset({frozenset(["TrueOneWay"])}))
|
only_connection = ConnectionDefinition(target_region, frozenset({frozenset(["TrueOneWay"])}))
|
||||||
)
|
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(only_connection)
|
||||||
else:
|
else:
|
||||||
new_lambda = logical_or_witness_rules([connection[1], parse_lambda(panel_set_string)])
|
combined_rule = logical_or_witness_rules(
|
||||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add((target_region, new_lambda))
|
[connection.traversal_rule, parse_witness_rule(panel_set_string)]
|
||||||
|
)
|
||||||
|
combined_connection = ConnectionDefinition(target_region, combined_rule)
|
||||||
|
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(combined_connection)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
new_conn = (target_region, parse_lambda(panel_set_string))
|
new_connection = ConnectionDefinition(target_region, parse_witness_rule(panel_set_string))
|
||||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add(new_conn)
|
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(new_connection)
|
||||||
|
|
||||||
if adj_type == "Added Locations":
|
if adj_type == "Added Locations":
|
||||||
if "0x" in line:
|
if "0x" in line:
|
||||||
@@ -558,7 +563,7 @@ class WitnessPlayerLogic:
|
|||||||
|
|
||||||
self.AVAILABLE_EASTER_EGGS_PER_REGION = defaultdict(int)
|
self.AVAILABLE_EASTER_EGGS_PER_REGION = defaultdict(int)
|
||||||
for entity_hex in self.AVAILABLE_EASTER_EGGS:
|
for entity_hex in self.AVAILABLE_EASTER_EGGS:
|
||||||
region_name = static_witness_logic.ENTITIES_BY_HEX[entity_hex]["region"]["name"]
|
region_name = static_witness_logic.ENTITIES_BY_HEX[entity_hex]["region"].name
|
||||||
self.AVAILABLE_EASTER_EGGS_PER_REGION[region_name] += 1
|
self.AVAILABLE_EASTER_EGGS_PER_REGION[region_name] += 1
|
||||||
|
|
||||||
eggs_per_check, logically_required_eggs_per_check = world.options.easter_egg_hunt.get_step_and_logical_step()
|
eggs_per_check, logically_required_eggs_per_check = world.options.easter_egg_hunt.get_step_and_logical_step()
|
||||||
@@ -796,7 +801,7 @@ class WitnessPlayerLogic:
|
|||||||
next_region = regions_to_check.pop()
|
next_region = regions_to_check.pop()
|
||||||
|
|
||||||
for region_exit in self.CONNECTIONS_BY_REGION_NAME[next_region]:
|
for region_exit in self.CONNECTIONS_BY_REGION_NAME[next_region]:
|
||||||
target = region_exit[0]
|
target = region_exit.target_region
|
||||||
|
|
||||||
if target in reachable_regions:
|
if target in reachable_regions:
|
||||||
continue
|
continue
|
||||||
@@ -844,7 +849,7 @@ class WitnessPlayerLogic:
|
|||||||
|
|
||||||
# First, entities in unreachable regions are obviously themselves unreachable.
|
# First, entities in unreachable regions are obviously themselves unreachable.
|
||||||
for region in new_unreachable_regions:
|
for region in new_unreachable_regions:
|
||||||
for entity in static_witness_logic.ALL_REGIONS_BY_NAME[region]["physical_entities"]:
|
for entity in static_witness_logic.ALL_REGIONS_BY_NAME[region].physical_entities:
|
||||||
# Never disable the Victory Location.
|
# Never disable the Victory Location.
|
||||||
if entity == self.VICTORY_LOCATION:
|
if entity == self.VICTORY_LOCATION:
|
||||||
continue
|
continue
|
||||||
@@ -879,11 +884,11 @@ class WitnessPlayerLogic:
|
|||||||
if not new_unreachable_regions and not newly_discovered_disabled_entities:
|
if not new_unreachable_regions and not newly_discovered_disabled_entities:
|
||||||
return
|
return
|
||||||
|
|
||||||
def reduce_connection_requirement(self, connection: Tuple[str, WitnessRule]) -> WitnessRule:
|
def reduce_connection_requirement(self, connection: ConnectionDefinition) -> ConnectionDefinition:
|
||||||
all_possibilities = []
|
all_possibilities = []
|
||||||
|
|
||||||
# Check each traversal option individually
|
# Check each traversal option individually
|
||||||
for option in connection[1]:
|
for option in connection.traversal_rule:
|
||||||
individual_entity_requirements: List[WitnessRule] = []
|
individual_entity_requirements: List[WitnessRule] = []
|
||||||
for entity in option:
|
for entity in option:
|
||||||
# If a connection requires solving a disabled entity, it is not valid.
|
# If a connection requires solving a disabled entity, it is not valid.
|
||||||
@@ -901,7 +906,7 @@ class WitnessPlayerLogic:
|
|||||||
entity_req = self.get_entity_requirement(entity)
|
entity_req = self.get_entity_requirement(entity)
|
||||||
|
|
||||||
if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]:
|
if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]:
|
||||||
region_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"]["name"]
|
region_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[entity]["region"].name
|
||||||
entity_req = logical_and_witness_rules([entity_req, frozenset({frozenset({region_name})})])
|
entity_req = logical_and_witness_rules([entity_req, frozenset({frozenset({region_name})})])
|
||||||
|
|
||||||
individual_entity_requirements.append(entity_req)
|
individual_entity_requirements.append(entity_req)
|
||||||
@@ -909,7 +914,7 @@ class WitnessPlayerLogic:
|
|||||||
# Merge all possible requirements into one DNF condition.
|
# Merge all possible requirements into one DNF condition.
|
||||||
all_possibilities.append(logical_and_witness_rules(individual_entity_requirements))
|
all_possibilities.append(logical_and_witness_rules(individual_entity_requirements))
|
||||||
|
|
||||||
return logical_or_witness_rules(all_possibilities)
|
return ConnectionDefinition(connection.target_region, logical_or_witness_rules(all_possibilities))
|
||||||
|
|
||||||
def make_dependency_reduced_checklist(self) -> None:
|
def make_dependency_reduced_checklist(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -942,14 +947,14 @@ class WitnessPlayerLogic:
|
|||||||
|
|
||||||
# Make independent region connection requirements based on the entities they require
|
# Make independent region connection requirements based on the entities they require
|
||||||
for region, connections in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL.items():
|
for region, connections in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL.items():
|
||||||
new_connections = set()
|
new_connections = []
|
||||||
|
|
||||||
for connection in connections:
|
for connection in connections:
|
||||||
overall_requirement = self.reduce_connection_requirement(connection)
|
reduced_connection = self.reduce_connection_requirement(connection)
|
||||||
|
|
||||||
# If there is a way to use this connection, add it.
|
# If there is a way to use this connection, add it.
|
||||||
if overall_requirement:
|
if reduced_connection.can_be_traversed:
|
||||||
new_connections.add((connection[0], overall_requirement))
|
new_connections.append(reduced_connection)
|
||||||
|
|
||||||
self.CONNECTIONS_BY_REGION_NAME[region] = new_connections
|
self.CONNECTIONS_BY_REGION_NAME[region] = new_connections
|
||||||
|
|
||||||
|
@@ -10,8 +10,9 @@ from BaseClasses import Entrance, Region
|
|||||||
from worlds.generic.Rules import CollectionRule
|
from worlds.generic.Rules import CollectionRule
|
||||||
|
|
||||||
from .data import static_logic as static_witness_logic
|
from .data import static_logic as static_witness_logic
|
||||||
|
from .data.definition_classes import WitnessRule
|
||||||
from .data.static_logic import StaticWitnessLogicObj
|
from .data.static_logic import StaticWitnessLogicObj
|
||||||
from .data.utils import WitnessRule, optimize_witness_rule
|
from .data.utils import optimize_witness_rule
|
||||||
from .locations import WitnessPlayerLocations
|
from .locations import WitnessPlayerLocations
|
||||||
from .player_logic import WitnessPlayerLogic
|
from .player_logic import WitnessPlayerLogic
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ class WitnessPlayerRegions:
|
|||||||
if k not in player_logic.UNREACHABLE_REGIONS
|
if k not in player_logic.UNREACHABLE_REGIONS
|
||||||
}
|
}
|
||||||
|
|
||||||
event_locations_per_region = defaultdict(dict)
|
event_locations_per_region: Dict[str, Dict[str, int]] = defaultdict(dict)
|
||||||
|
|
||||||
for event_location, event_item_and_entity in player_logic.EVENT_ITEM_PAIRS.items():
|
for event_location, event_item_and_entity in player_logic.EVENT_ITEM_PAIRS.items():
|
||||||
entity_or_region = event_item_and_entity[1]
|
entity_or_region = event_item_and_entity[1]
|
||||||
@@ -126,13 +127,13 @@ class WitnessPlayerRegions:
|
|||||||
if region is None:
|
if region is None:
|
||||||
region_name = "Entry"
|
region_name = "Entry"
|
||||||
else:
|
else:
|
||||||
region_name = region["name"]
|
region_name = region.name
|
||||||
order = self.reference_logic.ENTITIES_BY_HEX[entity_or_region]["order"]
|
order = self.reference_logic.ENTITIES_BY_HEX[entity_or_region]["order"]
|
||||||
event_locations_per_region[region_name][event_location] = order
|
event_locations_per_region[region_name][event_location] = order
|
||||||
|
|
||||||
for region_name, region in regions_to_create.items():
|
for region_name, region in regions_to_create.items():
|
||||||
location_entities_for_this_region = [
|
location_entities_for_this_region = [
|
||||||
self.reference_logic.ENTITIES_BY_HEX[entity] for entity in region["entities"]
|
self.reference_logic.ENTITIES_BY_HEX[entity] for entity in region.logical_entities
|
||||||
]
|
]
|
||||||
locations_for_this_region = {
|
locations_for_this_region = {
|
||||||
entity["checkName"]: entity["order"] for entity in location_entities_for_this_region
|
entity["checkName"]: entity["order"] for entity in location_entities_for_this_region
|
||||||
|
@@ -10,7 +10,7 @@ from BaseClasses import CollectionState
|
|||||||
from worlds.generic.Rules import CollectionRule, set_rule
|
from worlds.generic.Rules import CollectionRule, set_rule
|
||||||
|
|
||||||
from .data import static_logic as static_witness_logic
|
from .data import static_logic as static_witness_logic
|
||||||
from .data.utils import WitnessRule
|
from .data.definition_classes import WitnessRule
|
||||||
from .player_logic import WitnessPlayerLogic
|
from .player_logic import WitnessPlayerLogic
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
Reference in New Issue
Block a user