mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01: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
|
||||
|
||||
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)
|
||||
|
@@ -1,8 +1,9 @@
|
||||
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 .definition_classes import AreaDefinition, ConnectionDefinition, RegionDefinition, WitnessRule
|
||||
from .item_definition_classes import (
|
||||
CATEGORY_NAME_MAPPINGS,
|
||||
DoorItemDefinition,
|
||||
@@ -13,7 +14,6 @@ from .item_definition_classes import (
|
||||
)
|
||||
from .settings.easter_eggs import EASTER_EGGS
|
||||
from .utils import (
|
||||
WitnessRule,
|
||||
define_new_region,
|
||||
get_items,
|
||||
get_sigma_expert_logic,
|
||||
@@ -21,7 +21,7 @@ from .utils import (
|
||||
get_umbra_variety_logic,
|
||||
get_vanilla_logic,
|
||||
logical_or_witness_rules,
|
||||
parse_lambda,
|
||||
parse_witness_rule,
|
||||
)
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ class StaticWitnessLogicObj:
|
||||
lines = get_sigma_normal_logic()
|
||||
|
||||
# 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_AREAS_BY_NAME: Dict[str, Dict[str, Any]] = {}
|
||||
self.CONNECTIONS_WITH_DUPLICATES: Dict[str, Dict[str, Set[WitnessRule]]] = defaultdict(lambda: defaultdict(set))
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, Set[Tuple[str, WitnessRule]]] = {}
|
||||
self.ALL_REGIONS_BY_NAME: Dict[str, RegionDefinition] = {}
|
||||
self.ALL_AREAS_BY_NAME: Dict[str, AreaDefinition] = {}
|
||||
self.CONNECTIONS_WITH_DUPLICATES: Dict[str, List[ConnectionDefinition]] = defaultdict(list)
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME: Dict[str, List[ConnectionDefinition]] = {}
|
||||
|
||||
self.ENTITIES_BY_HEX: 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()
|
||||
for region_name, entity_amount in EASTER_EGGS.items():
|
||||
region_object = self.ALL_REGIONS_BY_NAME[region_name]
|
||||
correct_area = region_object["area"]
|
||||
correct_area = region_object.area
|
||||
|
||||
for _ in range(entity_amount):
|
||||
location_id = 160200 + egg_counter
|
||||
entity_hex = hex(0xEE000 + egg_counter)
|
||||
egg_counter += 1
|
||||
|
||||
area_counts[correct_area["name"]] += 1
|
||||
full_entity_name = f"{correct_area['name']} Easter Egg {area_counts[correct_area['name']]}"
|
||||
area_counts[correct_area.name] += 1
|
||||
full_entity_name = f"{correct_area.name} Easter Egg {area_counts[correct_area.name]}"
|
||||
|
||||
self.ENTITIES_BY_HEX[entity_hex] = {
|
||||
"checkName": full_entity_name,
|
||||
@@ -81,11 +81,11 @@ class StaticWitnessLogicObj:
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||
"entities": frozenset({frozenset({})})
|
||||
}
|
||||
region_object["entities"].append(entity_hex)
|
||||
region_object["physical_entities"].append(entity_hex)
|
||||
region_object.logical_entities.append(entity_hex)
|
||||
region_object.physical_entities.append(entity_hex)
|
||||
|
||||
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())):
|
||||
location_id = 160000 + i
|
||||
entity_hex = hex(0xEE200 + i)
|
||||
@@ -111,19 +111,15 @@ class StaticWitnessLogicObj:
|
||||
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
|
||||
"entities": frozenset({frozenset({})})
|
||||
}
|
||||
easter_egg_region["entities"].append(entity_hex)
|
||||
easter_egg_region["physical_entities"].append(entity_hex)
|
||||
easter_egg_region.logical_entities.append(entity_hex)
|
||||
easter_egg_region.physical_entities.append(entity_hex)
|
||||
|
||||
def read_logic_file(self, lines: List[str]) -> None:
|
||||
"""
|
||||
Reads the logic file and does the initial population of data structures
|
||||
"""
|
||||
|
||||
current_region = {}
|
||||
current_area: Dict[str, Any] = {
|
||||
"name": "Misc",
|
||||
"regions": [],
|
||||
}
|
||||
current_area = AreaDefinition("Misc")
|
||||
current_region = RegionDefinition("Fake", "Fake", current_area) # Unused, but makes PyCharm & mypy shut up
|
||||
self.ALL_AREAS_BY_NAME["Misc"] = current_area
|
||||
|
||||
for line in lines:
|
||||
@@ -133,19 +129,16 @@ class StaticWitnessLogicObj:
|
||||
if line[-1] == ":":
|
||||
new_region_and_connections = define_new_region(line, current_area)
|
||||
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
|
||||
for connection in new_region_and_connections[1]:
|
||||
self.CONNECTIONS_WITH_DUPLICATES[region_name][connection[0]].add(connection[1])
|
||||
current_area["regions"].append(region_name)
|
||||
self.CONNECTIONS_WITH_DUPLICATES[region_name].append(connection)
|
||||
current_area.regions.append(region_name)
|
||||
continue
|
||||
|
||||
if line[0] == "=":
|
||||
area_name = line[2:-2]
|
||||
current_area = {
|
||||
"name": area_name,
|
||||
"regions": [],
|
||||
}
|
||||
current_area = AreaDefinition(area_name, [])
|
||||
self.ALL_AREAS_BY_NAME[area_name] = current_area
|
||||
continue
|
||||
|
||||
@@ -158,9 +151,9 @@ class StaticWitnessLogicObj:
|
||||
entity_hex = entity_name_full[0:7]
|
||||
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":
|
||||
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.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*
|
||||
# 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
|
||||
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.
|
||||
current_region["physical_entities"].append(entity_hex)
|
||||
current_region.physical_entities.append(entity_hex)
|
||||
continue
|
||||
|
||||
required_item_lambda = line_split.pop(0)
|
||||
item_requirement_string = line_split.pop(0)
|
||||
|
||||
laser_names = {
|
||||
"Laser",
|
||||
@@ -224,18 +217,18 @@ class StaticWitnessLogicObj:
|
||||
entity_type = "Panel"
|
||||
location_type = "General"
|
||||
|
||||
required_items = parse_lambda(required_item_lambda)
|
||||
required_panels = parse_lambda(required_panel_lambda)
|
||||
required_items = parse_witness_rule(item_requirement_string)
|
||||
required_entities = parse_witness_rule(entity_requirement_string)
|
||||
|
||||
required_items = frozenset(required_items)
|
||||
|
||||
requirement = {
|
||||
"entities": required_panels,
|
||||
"entities": required_entities,
|
||||
"items": required_items
|
||||
}
|
||||
|
||||
if entity_type == "Obelisk Side":
|
||||
eps = set(next(iter(required_panels)))
|
||||
eps = set(next(iter(required_entities)))
|
||||
eps -= {"Theater to Tunnels"}
|
||||
|
||||
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.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = requirement
|
||||
|
||||
current_region["entities"].append(entity_hex)
|
||||
current_region["physical_entities"].append(entity_hex)
|
||||
current_region.logical_entities.append(entity_hex)
|
||||
current_region.physical_entities.append(entity_hex)
|
||||
|
||||
self.add_easter_eggs()
|
||||
|
||||
def reverse_connection(self, source_region: str, connection: Tuple[str, Set[WitnessRule]]) -> None:
|
||||
target = connection[0]
|
||||
traversal_options = connection[1]
|
||||
|
||||
def reverse_connection(self, source_region: str, connection: ConnectionDefinition) -> None:
|
||||
# Reverse this connection with all its possibilities, except the ones marked as "OneWay".
|
||||
for requirement in traversal_options:
|
||||
remaining_options = set()
|
||||
for option in requirement:
|
||||
if not any(req == "TrueOneWay" for req in option):
|
||||
remaining_options.add(option)
|
||||
remaining_options: Set[FrozenSet[str]] = set()
|
||||
for sub_option in connection.traversal_rule:
|
||||
if not any(req == "TrueOneWay" for req in sub_option):
|
||||
remaining_options.add(sub_option)
|
||||
|
||||
if remaining_options:
|
||||
self.CONNECTIONS_WITH_DUPLICATES[target][source_region].add(frozenset(remaining_options))
|
||||
reversed_connection = ConnectionDefinition(source_region, frozenset(remaining_options))
|
||||
if reversed_connection.can_be_traversed:
|
||||
self.CONNECTIONS_WITH_DUPLICATES[connection.target_region].append(reversed_connection)
|
||||
|
||||
def reverse_connections(self) -> None:
|
||||
# Iterate all connections
|
||||
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)
|
||||
|
||||
def combine_connections(self) -> None:
|
||||
# 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 target, requirement in connections.items():
|
||||
combined_req = logical_or_witness_rules(requirement)
|
||||
self.STATIC_CONNECTIONS_BY_REGION_NAME[source].add((target, combined_req))
|
||||
# Organize rules by target region
|
||||
traversal_options_by_target_region = defaultdict(list)
|
||||
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
|
||||
|
@@ -2,17 +2,12 @@ from datetime import date
|
||||
from math import floor
|
||||
from pkgutil import get_data
|
||||
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")
|
||||
|
||||
# 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:
|
||||
assert value is not None
|
||||
@@ -62,7 +57,7 @@ def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]:
|
||||
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
|
||||
"""
|
||||
@@ -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_simple = region_name_split[1][:-1]
|
||||
|
||||
options = set()
|
||||
options = []
|
||||
|
||||
for _ in range(len(line_split) // 2):
|
||||
connected_region = line_split.pop(0)
|
||||
corresponding_lambda = line_split.pop(0)
|
||||
traversal_rule_string = line_split.pop(0)
|
||||
|
||||
options.add(
|
||||
(connected_region, parse_lambda(corresponding_lambda))
|
||||
)
|
||||
options.append(ConnectionDefinition(connected_region, parse_witness_rule(traversal_rule_string)))
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
into a set of sets like this: {{a}, {b, c}}
|
||||
The lambda has to be in DNF.
|
||||
Turns a rule string literal like this: a | b & c
|
||||
into a set of sets (called "WitnessRule") like this: {{a}, {b, c}}
|
||||
The rule string has to be in DNF.
|
||||
"""
|
||||
if lambda_string == "True":
|
||||
if rule_string == "True":
|
||||
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})
|
||||
|
||||
|
||||
|
@@ -129,7 +129,7 @@ class EntityHuntPicker:
|
||||
|
||||
eligible_panels_by_area = defaultdict(set)
|
||||
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)
|
||||
|
||||
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():
|
||||
location_id = entity_object["id"]
|
||||
|
||||
area = entity_object["area"]["name"]
|
||||
area = entity_object["area"].name
|
||||
area_to_entity_ids[area].append(entity_id)
|
||||
|
||||
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]]]:
|
||||
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 = {}
|
||||
items_per_area = {}
|
||||
@@ -472,14 +472,14 @@ def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]]
|
||||
for area in potential_areas:
|
||||
regions = [
|
||||
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
|
||||
]
|
||||
locations = [location for region in regions for location in region.get_locations() if not location.is_event]
|
||||
|
||||
if locations:
|
||||
locations_per_area[area] = locations
|
||||
items_per_area[area] = [location.item for location in locations]
|
||||
locations_per_area[area.name] = locations
|
||||
items_per_area[area.name] = [location.item for location in locations]
|
||||
|
||||
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
|
||||
if world.options.victory_condition == "panel_hunt" and hinted_area != "Easter Eggs":
|
||||
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
|
||||
)
|
||||
|
||||
@@ -620,7 +620,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 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
|
||||
|
@@ -1,5 +1,4 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Tuple
|
||||
|
||||
from schema import And, Schema
|
||||
|
@@ -20,10 +20,10 @@ from collections import defaultdict
|
||||
from typing import TYPE_CHECKING, Dict, List, Set, Tuple, cast
|
||||
|
||||
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.static_logic import StaticWitnessLogicObj
|
||||
from .data.utils import (
|
||||
WitnessRule,
|
||||
get_boat,
|
||||
get_caves_except_path_to_challenge_exclusion_list,
|
||||
get_complex_additional_panels,
|
||||
@@ -47,7 +47,7 @@ from .data.utils import (
|
||||
get_vault_exclusion_list,
|
||||
logical_and_witness_rules,
|
||||
logical_or_witness_rules,
|
||||
parse_lambda,
|
||||
parse_witness_rule,
|
||||
)
|
||||
from .entity_hunt import EntityHuntPicker
|
||||
|
||||
@@ -97,10 +97,10 @@ class WitnessPlayerLogic:
|
||||
elif self.DIFFICULTY == "none":
|
||||
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.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.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]
|
||||
|
||||
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()
|
||||
|
||||
# For the requirement of an entity, we consider two things:
|
||||
@@ -270,7 +270,7 @@ class WitnessPlayerLogic:
|
||||
new_items = theoretical_new_items
|
||||
if dep_obj["region"] and entity_obj["region"] != dep_obj["region"]:
|
||||
new_items = frozenset(
|
||||
frozenset(possibility | {dep_obj["region"]["name"]})
|
||||
frozenset(possibility | {dep_obj["region"].name})
|
||||
for possibility in new_items
|
||||
)
|
||||
|
||||
@@ -359,11 +359,11 @@ class WitnessPlayerLogic:
|
||||
line_split = line.split(" - ")
|
||||
|
||||
requirement = {
|
||||
"entities": parse_lambda(line_split[1]),
|
||||
"entities": parse_witness_rule(line_split[1]),
|
||||
}
|
||||
|
||||
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 = [
|
||||
item_name for item_name, item_definition in static_witness_logic.ALL_ITEMS.items()
|
||||
if item_definition.category is ItemCategory.SYMBOL
|
||||
@@ -394,26 +394,31 @@ class WitnessPlayerLogic:
|
||||
return
|
||||
|
||||
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(" - ")
|
||||
source_region = line_split[0]
|
||||
target_region = line_split[1]
|
||||
panel_set_string = line_split[2]
|
||||
|
||||
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)
|
||||
|
||||
if panel_set_string == "TrueOneWay":
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add(
|
||||
(target_region, frozenset({frozenset(["TrueOneWay"])}))
|
||||
)
|
||||
# This means the connection can be completely replaced
|
||||
only_connection = ConnectionDefinition(target_region, frozenset({frozenset(["TrueOneWay"])}))
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(only_connection)
|
||||
else:
|
||||
new_lambda = logical_or_witness_rules([connection[1], parse_lambda(panel_set_string)])
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add((target_region, new_lambda))
|
||||
combined_rule = logical_or_witness_rules(
|
||||
[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
|
||||
else:
|
||||
new_conn = (target_region, parse_lambda(panel_set_string))
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].add(new_conn)
|
||||
new_connection = ConnectionDefinition(target_region, parse_witness_rule(panel_set_string))
|
||||
self.CONNECTIONS_BY_REGION_NAME_THEORETICAL[source_region].append(new_connection)
|
||||
|
||||
if adj_type == "Added Locations":
|
||||
if "0x" in line:
|
||||
@@ -558,7 +563,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
self.AVAILABLE_EASTER_EGGS_PER_REGION = defaultdict(int)
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
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:
|
||||
continue
|
||||
@@ -844,7 +849,7 @@ class WitnessPlayerLogic:
|
||||
|
||||
# First, entities in unreachable regions are obviously themselves unreachable.
|
||||
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.
|
||||
if entity == self.VICTORY_LOCATION:
|
||||
continue
|
||||
@@ -879,11 +884,11 @@ class WitnessPlayerLogic:
|
||||
if not new_unreachable_regions and not newly_discovered_disabled_entities:
|
||||
return
|
||||
|
||||
def reduce_connection_requirement(self, connection: Tuple[str, WitnessRule]) -> WitnessRule:
|
||||
def reduce_connection_requirement(self, connection: ConnectionDefinition) -> ConnectionDefinition:
|
||||
all_possibilities = []
|
||||
|
||||
# Check each traversal option individually
|
||||
for option in connection[1]:
|
||||
for option in connection.traversal_rule:
|
||||
individual_entity_requirements: List[WitnessRule] = []
|
||||
for entity in option:
|
||||
# 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)
|
||||
|
||||
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})})])
|
||||
|
||||
individual_entity_requirements.append(entity_req)
|
||||
@@ -909,7 +914,7 @@ class WitnessPlayerLogic:
|
||||
# Merge all possible requirements into one DNF condition.
|
||||
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:
|
||||
"""
|
||||
@@ -942,14 +947,14 @@ class WitnessPlayerLogic:
|
||||
|
||||
# Make independent region connection requirements based on the entities they require
|
||||
for region, connections in self.CONNECTIONS_BY_REGION_NAME_THEORETICAL.items():
|
||||
new_connections = set()
|
||||
new_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 overall_requirement:
|
||||
new_connections.add((connection[0], overall_requirement))
|
||||
if reduced_connection.can_be_traversed:
|
||||
new_connections.append(reduced_connection)
|
||||
|
||||
self.CONNECTIONS_BY_REGION_NAME[region] = new_connections
|
||||
|
||||
|
@@ -10,8 +10,9 @@ from BaseClasses import Entrance, Region
|
||||
from worlds.generic.Rules import CollectionRule
|
||||
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.definition_classes import WitnessRule
|
||||
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 .player_logic import WitnessPlayerLogic
|
||||
|
||||
@@ -114,7 +115,7 @@ class WitnessPlayerRegions:
|
||||
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():
|
||||
entity_or_region = event_item_and_entity[1]
|
||||
@@ -126,13 +127,13 @@ class WitnessPlayerRegions:
|
||||
if region is None:
|
||||
region_name = "Entry"
|
||||
else:
|
||||
region_name = region["name"]
|
||||
region_name = region.name
|
||||
order = self.reference_logic.ENTITIES_BY_HEX[entity_or_region]["order"]
|
||||
event_locations_per_region[region_name][event_location] = order
|
||||
|
||||
for region_name, region in regions_to_create.items():
|
||||
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 = {
|
||||
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 .data import static_logic as static_witness_logic
|
||||
from .data.utils import WitnessRule
|
||||
from .data.definition_classes import WitnessRule
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
Reference in New Issue
Block a user