| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  | from collections import Counter, defaultdict | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  | from typing import Any, Dict, FrozenSet, List, Optional, Set | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 03:40:47 +02:00
										 |  |  | from Utils import cache_argsless | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  | from .definition_classes import AreaDefinition, ConnectionDefinition, RegionDefinition, WitnessRule | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | from .item_definition_classes import ( | 
					
						
							|  |  |  |     CATEGORY_NAME_MAPPINGS, | 
					
						
							|  |  |  |     DoorItemDefinition, | 
					
						
							|  |  |  |     ItemCategory, | 
					
						
							|  |  |  |     ItemDefinition, | 
					
						
							|  |  |  |     ProgressiveItemDefinition, | 
					
						
							|  |  |  |     WeightedItemDefinition, | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  | from .settings.easter_eggs import EASTER_EGGS | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | from .utils import ( | 
					
						
							|  |  |  |     define_new_region, | 
					
						
							|  |  |  |     get_items, | 
					
						
							|  |  |  |     get_sigma_expert_logic, | 
					
						
							|  |  |  |     get_sigma_normal_logic, | 
					
						
							| 
									
										
										
										
											2024-09-05 17:10:09 +02:00
										 |  |  |     get_umbra_variety_logic, | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |     get_vanilla_logic, | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |     logical_or_witness_rules, | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |     parse_witness_rule, | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | ) | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | class StaticWitnessLogicObj: | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |     def __init__(self, lines: Optional[List[str]] = None) -> None: | 
					
						
							|  |  |  |         if lines is None: | 
					
						
							|  |  |  |             lines = get_sigma_normal_logic() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # All regions with a list of panels in them and the connections to other regions, before logic adjustments | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |         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]] = {} | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self.ENTITIES_BY_HEX: Dict[str, Dict[str, Any]] = {} | 
					
						
							|  |  |  |         self.ENTITIES_BY_NAME: Dict[str, Dict[str, Any]] = {} | 
					
						
							|  |  |  |         self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX: Dict[str, Dict[str, WitnessRule]] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.OBELISK_SIDE_ID_TO_EP_HEXES: Dict[int, Set[int]] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.EP_TO_OBELISK_SIDE: Dict[str, str] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.ENTITY_ID_TO_NAME: Dict[str, str] = {} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.read_logic_file(lines) | 
					
						
							|  |  |  |         self.reverse_connections() | 
					
						
							|  |  |  |         self.combine_connections() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  |     def add_easter_eggs(self) -> None: | 
					
						
							|  |  |  |         egg_counter = 0 | 
					
						
							|  |  |  |         area_counts: Dict[str, int] = Counter() | 
					
						
							|  |  |  |         for region_name, entity_amount in EASTER_EGGS.items(): | 
					
						
							|  |  |  |             region_object = self.ALL_REGIONS_BY_NAME[region_name] | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |             correct_area = region_object.area | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             for _ in range(entity_amount): | 
					
						
							|  |  |  |                 location_id = 160200 + egg_counter | 
					
						
							|  |  |  |                 entity_hex = hex(0xEE000 + egg_counter) | 
					
						
							|  |  |  |                 egg_counter += 1 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                 area_counts[correct_area.name] += 1 | 
					
						
							|  |  |  |                 full_entity_name = f"{correct_area.name} Easter Egg {area_counts[correct_area.name]}" | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 self.ENTITIES_BY_HEX[entity_hex] = { | 
					
						
							|  |  |  |                     "checkName": full_entity_name, | 
					
						
							|  |  |  |                     "entity_hex": entity_hex, | 
					
						
							|  |  |  |                     "region": region_object, | 
					
						
							|  |  |  |                     "id": int(location_id), | 
					
						
							|  |  |  |                     "entityType": "Easter Egg", | 
					
						
							|  |  |  |                     "locationType": "Easter Egg", | 
					
						
							|  |  |  |                     "area": correct_area, | 
					
						
							|  |  |  |                     "order": len(self.ENTITIES_BY_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] = { | 
					
						
							|  |  |  |                     "entities": frozenset({frozenset({})}) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                 region_object.logical_entities.append(entity_hex) | 
					
						
							|  |  |  |                 region_object.physical_entities.append(entity_hex) | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         easter_egg_region = self.ALL_REGIONS_BY_NAME["Easter Eggs"] | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |         easter_egg_area = easter_egg_region.area | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  |         for i in range(sum(EASTER_EGGS.values())): | 
					
						
							|  |  |  |             location_id = 160000 + i | 
					
						
							|  |  |  |             entity_hex = hex(0xEE200 + i) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if i == 0: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             full_entity_name = f"{i + 1} Easter Eggs Collected" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             self.ENTITIES_BY_HEX[entity_hex] = { | 
					
						
							|  |  |  |                 "checkName": full_entity_name, | 
					
						
							|  |  |  |                 "entity_hex": entity_hex, | 
					
						
							|  |  |  |                 "region": easter_egg_region, | 
					
						
							|  |  |  |                 "id": int(location_id), | 
					
						
							|  |  |  |                 "entityType": "Easter Egg Total", | 
					
						
							|  |  |  |                 "locationType": "Easter Egg Total", | 
					
						
							|  |  |  |                 "area": easter_egg_area, | 
					
						
							|  |  |  |                 "order": len(self.ENTITIES_BY_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] = { | 
					
						
							|  |  |  |                 "entities": frozenset({frozenset({})}) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |             easter_egg_region.logical_entities.append(entity_hex) | 
					
						
							|  |  |  |             easter_egg_region.physical_entities.append(entity_hex) | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |     def read_logic_file(self, lines: List[str]) -> None: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Reads the logic file and does the initial population of data structures | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |         current_area = AreaDefinition("Misc") | 
					
						
							|  |  |  |         current_region = RegionDefinition("Fake", "Fake", current_area)  # Unused, but makes PyCharm & mypy shut up | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |         self.ALL_AREAS_BY_NAME["Misc"] = current_area | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |         for line in lines: | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  |             if line == "" or line[0] == "#": | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |             if line[-1] == ":": | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  |                 new_region_and_connections = define_new_region(line, current_area) | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 current_region = new_region_and_connections[0] | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                 region_name = current_region.name | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 self.ALL_REGIONS_BY_NAME[region_name] = current_region | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |                 for connection in new_region_and_connections[1]: | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                     self.CONNECTIONS_WITH_DUPLICATES[region_name].append(connection) | 
					
						
							|  |  |  |                 current_area.regions.append(region_name) | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if line[0] == "=": | 
					
						
							|  |  |  |                 area_name = line[2:-2] | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                 current_area = AreaDefinition(area_name, []) | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |                 self.ALL_AREAS_BY_NAME[area_name] = current_area | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |             line_split = line.split(" - ") | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |             location_id = line_split.pop(0) | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             entity_name_full = line_split.pop(0) | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             entity_hex = entity_name_full[0:7] | 
					
						
							|  |  |  |             entity_name = entity_name_full[9:-1] | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |             entity_requirement_string = line_split.pop(0) | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |             full_entity_name = current_region.short_name + " " + entity_name | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |             if location_id == "Door" or location_id == "Laser": | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |                 self.ENTITIES_BY_HEX[entity_hex] = { | 
					
						
							|  |  |  |                     "checkName": full_entity_name, | 
					
						
							|  |  |  |                     "entity_hex": entity_hex, | 
					
						
							|  |  |  |                     "region": None, | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                     "id": None, | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |                     "entityType": location_id, | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |                     "locationType": None, | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |                     "area": current_area, | 
					
						
							| 
									
										
										
										
											2024-12-30 00:50:39 +01:00
										 |  |  |                     "order": len(self.ENTITIES_BY_HEX), | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |                 self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex] | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |                 self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX[entity_hex] = { | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                     "entities": parse_witness_rule(entity_requirement_string) | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |                 # 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 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                 self.ALL_REGIONS_BY_NAME["Entry"].logical_entities.append(entity_hex) | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |                 # However, it will also be important to keep track of their physical location for postgame purposes. | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                 current_region.physical_entities.append(entity_hex) | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |             item_requirement_string = line_split.pop(0) | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             laser_names = { | 
					
						
							|  |  |  |                 "Laser", | 
					
						
							|  |  |  |                 "Laser Hedges", | 
					
						
							|  |  |  |                 "Laser Pressure Plates", | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             if "Discard" in entity_name: | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |                 entity_type = "Panel" | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 location_type = "Discard" | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |             elif "Vault" in entity_name: | 
					
						
							|  |  |  |                 entity_type = "Panel" | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 location_type = "Vault" | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             elif entity_name in laser_names: | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |                 entity_type = "Laser" | 
					
						
							|  |  |  |                 location_type = None | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             elif "Obelisk Side" in entity_name: | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |                 entity_type = "Obelisk Side" | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 location_type = "Obelisk Side" | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |             elif "Obelisk" in entity_name: | 
					
						
							|  |  |  |                 entity_type = "Obelisk" | 
					
						
							|  |  |  |                 location_type = None | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             elif "EP" in entity_name: | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |                 entity_type = "EP" | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 location_type = "EP" | 
					
						
							| 
									
										
										
										
											2024-08-24 02:08:04 +02:00
										 |  |  |             elif "Pet the Dog" in entity_name: | 
					
						
							|  |  |  |                 entity_type = "Event" | 
					
						
							|  |  |  |                 location_type = "Good Boi" | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |             elif entity_hex.startswith("0xFF"): | 
					
						
							|  |  |  |                 entity_type = "Event" | 
					
						
							|  |  |  |                 location_type = None | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |                 entity_type = "Panel" | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 location_type = "General" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |             required_items = parse_witness_rule(item_requirement_string) | 
					
						
							|  |  |  |             required_entities = parse_witness_rule(entity_requirement_string) | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             required_items = frozenset(required_items) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             requirement = { | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                 "entities": required_entities, | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 "items": required_items | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |             if entity_type == "Obelisk Side": | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |                 eps = set(next(iter(required_entities))) | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 eps -= {"Theater to Tunnels"} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 eps_ints = {int(h, 16) for h in eps} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |                 self.OBELISK_SIDE_ID_TO_EP_HEXES[int(entity_hex, 16)] = eps_ints | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 for ep_hex in eps: | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |                     self.EP_TO_OBELISK_SIDE[ep_hex] = entity_hex | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             self.ENTITIES_BY_HEX[entity_hex] = { | 
					
						
							|  |  |  |                 "checkName": full_entity_name, | 
					
						
							|  |  |  |                 "entity_hex": entity_hex, | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |                 "region": current_region, | 
					
						
							|  |  |  |                 "id": int(location_id), | 
					
						
							| 
									
										
										
										
											2024-08-20 01:16:35 +02:00
										 |  |  |                 "entityType": entity_type, | 
					
						
							|  |  |  |                 "locationType": location_type, | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |                 "area": current_area, | 
					
						
							| 
									
										
										
										
											2024-12-30 00:50:39 +01:00
										 |  |  |                 "order": len(self.ENTITIES_BY_HEX), | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             self.ENTITY_ID_TO_NAME[entity_hex] = full_entity_name | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01: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 02:00:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |             current_region.logical_entities.append(entity_hex) | 
					
						
							|  |  |  |             current_region.physical_entities.append(entity_hex) | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-08 01:44:06 +01:00
										 |  |  |         self.add_easter_eggs() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |     def reverse_connection(self, source_region: str, connection: ConnectionDefinition) -> None: | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |         # Reverse this connection with all its possibilities, except the ones marked as "OneWay". | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |         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) | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |         reversed_connection = ConnectionDefinition(source_region, frozenset(remaining_options)) | 
					
						
							|  |  |  |         if reversed_connection.can_be_traversed: | 
					
						
							|  |  |  |             self.CONNECTIONS_WITH_DUPLICATES[connection.target_region].append(reversed_connection) | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |     def reverse_connections(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |         # Iterate all connections | 
					
						
							|  |  |  |         for region_name, connections in list(self.CONNECTIONS_WITH_DUPLICATES.items()): | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |             for connection in connections: | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |                 self.reverse_connection(region_name, connection) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |     def combine_connections(self) -> None: | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  |         # All regions need to be present, and this dict is copied later - Thus, defaultdict is not the correct choice. | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |         self.STATIC_CONNECTIONS_BY_REGION_NAME = {region_name: [] for region_name in self.ALL_REGIONS_BY_NAME} | 
					
						
							| 
									
										
										
										
											2024-06-01 23:11:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for source, connections in self.CONNECTIONS_WITH_DUPLICATES.items(): | 
					
						
							| 
									
										
										
										
											2025-03-13 23:59:09 +01:00
										 |  |  |             # 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) | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | # Item data parsed from WitnessItems.txt | 
					
						
							|  |  |  | ALL_ITEMS: Dict[str, ItemDefinition] = {} | 
					
						
							|  |  |  | _progressive_lookup: Dict[str, str] = {} | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | def parse_items() -> None: | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Parses currently defined items from WitnessItems.txt | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |     lines: List[str] = get_items() | 
					
						
							|  |  |  |     current_category: ItemCategory = ItemCategory.SYMBOL | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |     for line in lines: | 
					
						
							|  |  |  |         # Skip empty lines and comments. | 
					
						
							|  |  |  |         if line == "" or line[0] == "#": | 
					
						
							|  |  |  |             continue | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |         # If this line is a category header, update our cached category. | 
					
						
							|  |  |  |         if line in CATEGORY_NAME_MAPPINGS.keys(): | 
					
						
							|  |  |  |             current_category = CATEGORY_NAME_MAPPINGS[line] | 
					
						
							|  |  |  |             continue | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |         line_split = line.split(" - ") | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |         item_code = int(line_split[0]) | 
					
						
							|  |  |  |         item_name = line_split[1] | 
					
						
							|  |  |  |         arguments: List[str] = line_split[2].split(",") if len(line_split) >= 3 else [] | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |         if current_category in [ItemCategory.DOOR, ItemCategory.LASER]: | 
					
						
							|  |  |  |             # Map doors to IDs. | 
					
						
							|  |  |  |             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 | 
					
						
							|  |  |  |             ALL_ITEMS[item_name] = WeightedItemDefinition(item_code, current_category, weight) | 
					
						
							|  |  |  |         elif arguments: | 
					
						
							|  |  |  |             # Progressive items. | 
					
						
							|  |  |  |             ALL_ITEMS[item_name] = ProgressiveItemDefinition(item_code, current_category, arguments) | 
					
						
							|  |  |  |             for child_item in arguments: | 
					
						
							|  |  |  |                 _progressive_lookup[child_item] = item_name | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             ALL_ITEMS[item_name] = ItemDefinition(item_code, current_category) | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | def get_parent_progressive_item(item_name: str) -> str: | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Returns the name of the item's progressive parent, if there is one, or the item's name if not. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     return _progressive_lookup.get(item_name, item_name) | 
					
						
							| 
									
										
										
										
											2023-06-25 02:00:56 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-18 20:02:57 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 03:40:47 +02:00
										 |  |  | @cache_argsless | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | def get_vanilla() -> StaticWitnessLogicObj: | 
					
						
							|  |  |  |     return StaticWitnessLogicObj(get_vanilla_logic()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 03:40:47 +02:00
										 |  |  | @cache_argsless | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | def get_sigma_normal() -> StaticWitnessLogicObj: | 
					
						
							|  |  |  |     return StaticWitnessLogicObj(get_sigma_normal_logic()) | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-06-06 03:40:47 +02:00
										 |  |  | @cache_argsless | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | def get_sigma_expert() -> StaticWitnessLogicObj: | 
					
						
							|  |  |  |     return StaticWitnessLogicObj(get_sigma_expert_logic()) | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-05 17:10:09 +02:00
										 |  |  | @cache_argsless | 
					
						
							|  |  |  | def get_umbra_variety() -> StaticWitnessLogicObj: | 
					
						
							|  |  |  |     return StaticWitnessLogicObj(get_umbra_variety_logic()) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  | def __getattr__(name: str) -> StaticWitnessLogicObj: | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |     if name == "vanilla": | 
					
						
							|  |  |  |         return get_vanilla() | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |     if name == "sigma_normal": | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |         return get_sigma_normal() | 
					
						
							| 
									
										
										
										
											2024-07-02 23:59:26 +02:00
										 |  |  |     if name == "sigma_expert": | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |         return get_sigma_expert() | 
					
						
							| 
									
										
										
										
											2024-09-05 17:10:09 +02:00
										 |  |  |     if name == "umbra_variety": | 
					
						
							|  |  |  |         return get_umbra_variety() | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |     raise AttributeError(f"module '{__name__}' has no attribute '{name}'") | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | parse_items() | 
					
						
							| 
									
										
										
										
											2022-10-09 04:13:52 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | ALL_REGIONS_BY_NAME = get_sigma_normal().ALL_REGIONS_BY_NAME | 
					
						
							|  |  |  | ALL_AREAS_BY_NAME = get_sigma_normal().ALL_AREAS_BY_NAME | 
					
						
							|  |  |  | STATIC_CONNECTIONS_BY_REGION_NAME = get_sigma_normal().STATIC_CONNECTIONS_BY_REGION_NAME | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | ENTITIES_BY_HEX = get_sigma_normal().ENTITIES_BY_HEX | 
					
						
							|  |  |  | ENTITIES_BY_NAME = get_sigma_normal().ENTITIES_BY_NAME | 
					
						
							|  |  |  | STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = get_sigma_normal().STATIC_DEPENDENT_REQUIREMENTS_BY_HEX | 
					
						
							| 
									
										
										
										
											2023-02-01 21:18:07 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | OBELISK_SIDE_ID_TO_EP_HEXES = get_sigma_normal().OBELISK_SIDE_ID_TO_EP_HEXES | 
					
						
							| 
									
										
										
										
											2024-02-29 07:40:08 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | EP_TO_OBELISK_SIDE = get_sigma_normal().EP_TO_OBELISK_SIDE | 
					
						
							| 
									
										
										
										
											2024-02-29 07:40:08 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | ENTITY_ID_TO_NAME = get_sigma_normal().ENTITY_ID_TO_NAME |