Archipelago/worlds/witness/static_logic.py

301 lines
10 KiB
Python
Raw Normal View History

from dataclasses import dataclass
from enum import Enum
from typing import Dict, List
from .utils import define_new_region, parse_lambda, lazy, get_items, get_sigma_normal_logic, get_sigma_expert_logic,\
get_vanilla_logic
class ItemCategory(Enum):
SYMBOL = 0
DOOR = 1
LASER = 2
USEFUL = 3
FILLER = 4
TRAP = 5
JOKE = 6
EVENT = 7
CATEGORY_NAME_MAPPINGS: Dict[str, ItemCategory] = {
"Symbols:": ItemCategory.SYMBOL,
"Doors:": ItemCategory.DOOR,
"Lasers:": ItemCategory.LASER,
"Useful:": ItemCategory.USEFUL,
"Filler:": ItemCategory.FILLER,
"Traps:": ItemCategory.TRAP,
"Jokes:": ItemCategory.JOKE
}
@dataclass(frozen=True)
class ItemDefinition:
local_code: int
category: ItemCategory
@dataclass(frozen=True)
class ProgressiveItemDefinition(ItemDefinition):
child_item_names: List[str]
@dataclass(frozen=True)
class DoorItemDefinition(ItemDefinition):
panel_id_hexes: List[str]
@dataclass(frozen=True)
class WeightedItemDefinition(ItemDefinition):
weight: int
2022-10-09 02:13:52 +00:00
class StaticWitnessLogicObj:
def read_logic_file(self, lines):
"""
Reads the logic file and does the initial population of data structures
"""
2022-10-09 02:13:52 +00:00
2023-06-25 00:00:56 +00:00
current_region = dict()
current_area = {
"name": "Misc",
"regions": [],
}
self.ALL_AREAS_BY_NAME["Misc"] = current_area
2023-06-25 00:00:56 +00:00
for line in lines:
if line == "" or line[0] == "#":
2023-06-25 00:00:56 +00:00
continue
2023-06-25 00:00:56 +00:00
if line[-1] == ":":
new_region_and_connections = define_new_region(line)
current_region = new_region_and_connections[0]
region_name = current_region["name"]
self.ALL_REGIONS_BY_NAME[region_name] = current_region
self.STATIC_CONNECTIONS_BY_REGION_NAME[region_name] = new_region_and_connections[1]
current_area["regions"].append(region_name)
continue
if line[0] == "=":
area_name = line[2:-2]
current_area = {
"name": area_name,
"regions": [],
}
self.ALL_AREAS_BY_NAME[area_name] = current_area
2023-06-25 00:00:56 +00:00
continue
2023-06-25 00:00:56 +00:00
line_split = line.split(" - ")
2023-06-25 00:00:56 +00:00
location_id = line_split.pop(0)
entity_name_full = line_split.pop(0)
entity_hex = entity_name_full[0:7]
entity_name = entity_name_full[9:-1]
2023-06-25 00:00:56 +00:00
required_panel_lambda = line_split.pop(0)
full_entity_name = current_region["shortName"] + " " + entity_name
2023-06-25 00:00:56 +00:00
if location_id == "Door" or location_id == "Laser":
self.ENTITIES_BY_HEX[entity_hex] = {
"checkName": full_entity_name,
"entity_hex": entity_hex,
"region": None,
2023-06-25 00:00:56 +00:00
"id": None,
"entityType": location_id,
"area": current_area,
}
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
2023-06-25 00:00:56 +00:00
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = {
2023-06-25 00:00:56 +00:00
"panels": parse_lambda(required_panel_lambda)
}
# 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"]["panels"].append(entity_hex)
2023-06-25 00:00:56 +00:00
continue
required_item_lambda = line_split.pop(0)
laser_names = {
"Laser",
"Laser Hedges",
"Laser Pressure Plates",
}
is_vault_or_video = "Vault" in entity_name or "Video" in entity_name
2023-06-25 00:00:56 +00:00
if "Discard" in entity_name:
2023-06-25 00:00:56 +00:00
location_type = "Discard"
elif is_vault_or_video or entity_name == "Tutorial Gate Close":
2023-06-25 00:00:56 +00:00
location_type = "Vault"
elif entity_name in laser_names:
2023-06-25 00:00:56 +00:00
location_type = "Laser"
elif "Obelisk Side" in entity_name:
2023-06-25 00:00:56 +00:00
location_type = "Obelisk Side"
elif "EP" in entity_name:
2023-06-25 00:00:56 +00:00
location_type = "EP"
else:
location_type = "General"
required_items = parse_lambda(required_item_lambda)
required_panels = parse_lambda(required_panel_lambda)
required_items = frozenset(required_items)
requirement = {
"panels": required_panels,
"items": required_items
}
if location_type == "Obelisk Side":
eps = set(list(required_panels)[0])
eps -= {"Theater to Tunnels"}
eps_ints = {int(h, 16) for h in eps}
self.OBELISK_SIDE_ID_TO_EP_HEXES[int(entity_hex, 16)] = eps_ints
2023-06-25 00:00:56 +00:00
for ep_hex in eps:
self.EP_TO_OBELISK_SIDE[ep_hex] = entity_hex
2023-06-25 00:00:56 +00:00
self.ENTITIES_BY_HEX[entity_hex] = {
"checkName": full_entity_name,
"entity_hex": entity_hex,
2023-06-25 00:00:56 +00:00
"region": current_region,
"id": int(location_id),
"entityType": location_type,
"area": current_area,
2023-06-25 00:00:56 +00:00
}
self.ENTITY_ID_TO_NAME[entity_hex] = full_entity_name
2023-06-25 00:00:56 +00:00
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
2023-06-25 00:00:56 +00:00
current_region["panels"].append(entity_hex)
def __init__(self, lines=None):
if lines is None:
lines = get_sigma_normal_logic()
2022-10-09 02:13:52 +00:00
# All regions with a list of panels in them and the connections to other regions, before logic adjustments
self.ALL_REGIONS_BY_NAME = dict()
self.ALL_AREAS_BY_NAME = dict()
2022-10-09 02:13:52 +00:00
self.STATIC_CONNECTIONS_BY_REGION_NAME = dict()
self.ENTITIES_BY_HEX = dict()
self.ENTITIES_BY_NAME = dict()
2022-10-09 02:13:52 +00:00
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict()
self.OBELISK_SIDE_ID_TO_EP_HEXES = dict()
self.EP_TO_OBELISK_SIDE = dict()
self.ENTITY_ID_TO_NAME = dict()
self.read_logic_file(lines)
2022-10-09 02:13:52 +00:00
class StaticWitnessLogic:
# Item data parsed from WitnessItems.txt
all_items: Dict[str, ItemDefinition] = {}
_progressive_lookup: Dict[str, str] = {}
2022-10-09 02:13:52 +00:00
ALL_REGIONS_BY_NAME = dict()
ALL_AREAS_BY_NAME = dict()
2022-10-09 02:13:52 +00:00
STATIC_CONNECTIONS_BY_REGION_NAME = dict()
OBELISK_SIDE_ID_TO_EP_HEXES = dict()
ENTITIES_BY_HEX = dict()
ENTITIES_BY_NAME = dict()
2022-10-09 02:13:52 +00:00
STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict()
EP_TO_OBELISK_SIDE = dict()
ENTITY_ID_TO_NAME = dict()
@staticmethod
def parse_items():
2022-10-09 02:13:52 +00:00
"""
Parses currently defined items from WitnessItems.txt
"""
lines: List[str] = get_items()
current_category: ItemCategory = ItemCategory.SYMBOL
2023-06-25 00:00:56 +00:00
for line in lines:
# Skip empty lines and comments.
if line == "" or line[0] == "#":
2023-06-25 00:00:56 +00:00
continue
# If this line is a category header, update our cached category.
if line in CATEGORY_NAME_MAPPINGS.keys():
current_category = CATEGORY_NAME_MAPPINGS[line]
2023-06-25 00:00:56 +00:00
continue
line_split = line.split(" - ")
item_code = int(line_split[0])
item_name = line_split[1]
arguments: List[str] = line_split[2].split(",") if len(line_split) >= 3 else []
if current_category in [ItemCategory.DOOR, ItemCategory.LASER]:
# Map doors to IDs.
StaticWitnessLogic.all_items[item_name] = DoorItemDefinition(item_code, current_category,
arguments)
elif current_category == ItemCategory.TRAP or current_category == ItemCategory.FILLER:
# Read filler weights.
weight = int(arguments[0]) if len(arguments) >= 1 else 1
StaticWitnessLogic.all_items[item_name] = WeightedItemDefinition(item_code, current_category, weight)
elif arguments:
# Progressive items.
StaticWitnessLogic.all_items[item_name] = ProgressiveItemDefinition(item_code, current_category,
arguments)
for child_item in arguments:
StaticWitnessLogic._progressive_lookup[child_item] = item_name
2023-06-25 00:00:56 +00:00
else:
StaticWitnessLogic.all_items[item_name] = ItemDefinition(item_code, current_category)
@staticmethod
def get_parent_progressive_item(item_name: str):
"""
Returns the name of the item's progressive parent, if there is one, or the item's name if not.
"""
return StaticWitnessLogic._progressive_lookup.get(item_name, item_name)
2022-10-09 02:13:52 +00:00
@lazy
def sigma_expert(self) -> StaticWitnessLogicObj:
return StaticWitnessLogicObj(get_sigma_expert_logic())
2022-10-09 02:13:52 +00:00
@lazy
def sigma_normal(self) -> StaticWitnessLogicObj:
return StaticWitnessLogicObj(get_sigma_normal_logic())
2022-10-09 02:13:52 +00:00
@lazy
def vanilla(self) -> StaticWitnessLogicObj:
return StaticWitnessLogicObj(get_vanilla_logic())
def __init__(self):
self.parse_items()
2022-10-09 02:13:52 +00:00
self.ALL_REGIONS_BY_NAME.update(self.sigma_normal.ALL_REGIONS_BY_NAME)
self.ALL_AREAS_BY_NAME.update(self.sigma_normal.ALL_AREAS_BY_NAME)
2022-10-09 02:13:52 +00:00
self.STATIC_CONNECTIONS_BY_REGION_NAME.update(self.sigma_normal.STATIC_CONNECTIONS_BY_REGION_NAME)
self.ENTITIES_BY_HEX.update(self.sigma_normal.ENTITIES_BY_HEX)
self.ENTITIES_BY_NAME.update(self.sigma_normal.ENTITIES_BY_NAME)
2022-10-09 02:13:52 +00:00
self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX.update(self.sigma_normal.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX)
self.OBELISK_SIDE_ID_TO_EP_HEXES.update(self.sigma_normal.OBELISK_SIDE_ID_TO_EP_HEXES)
self.EP_TO_OBELISK_SIDE.update(self.sigma_normal.EP_TO_OBELISK_SIDE)
self.ENTITY_ID_TO_NAME.update(self.sigma_normal.ENTITY_ID_TO_NAME)
StaticWitnessLogic()