| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | """
 | 
					
						
							|  |  |  | Defines Region for The Witness, assigns locations to them, | 
					
						
							|  |  |  | and connects them with the proper requirements | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | from collections import defaultdict | 
					
						
							|  |  |  | from typing import TYPE_CHECKING, Dict, FrozenSet, List, Tuple | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | from BaseClasses import Entrance, Region | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | from worlds.generic.Rules import CollectionRule | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from .data import static_logic as static_witness_logic | 
					
						
							|  |  |  | from .locations import WitnessPlayerLocations, static_witness_locations | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | from .player_logic import WitnessPlayerLogic | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from . import WitnessWorld | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  | class WitnessPlayerRegions: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     """Class that defines Witness Regions""" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |     player_locations = None | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     logic = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |     @staticmethod | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |     def make_lambda(item_requirement: FrozenSet[FrozenSet[str]], world: "WitnessWorld") -> CollectionRule: | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         from .rules import _meets_item_requirements | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Lambdas are made in a for loop, so the values have to be captured | 
					
						
							|  |  |  |         This function is for that purpose | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         return _meets_item_requirements(item_requirement, world) | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |     def connect_if_possible(self, world: "WitnessWorld", source: str, target: str, req: FrozenSet[FrozenSet[str]], | 
					
						
							|  |  |  |                             regions_by_name: Dict[str, Region], backwards: bool = False): | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         connect two regions and set the corresponding requirement | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Remove any possibilities where being in the target region would be required anyway. | 
					
						
							|  |  |  |         real_requirement = frozenset({option for option in req if target not in option}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # There are some connections that should only be done one way. If this is a backwards connection, check for that | 
					
						
							|  |  |  |         if backwards: | 
					
						
							|  |  |  |             real_requirement = frozenset({option for option in real_requirement if "TrueOneWay" not in option}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Dissolve any "True" or "TrueOneWay" | 
					
						
							|  |  |  |         real_requirement = frozenset({option - {"True", "TrueOneWay"} for option in real_requirement}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # If there is no way to actually use this connection, don't even bother making it. | 
					
						
							|  |  |  |         if not real_requirement: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # We don't need to check for the accessibility of the source region. | 
					
						
							|  |  |  |         final_requirement = frozenset({option - frozenset({source}) for option in real_requirement}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         source_region = regions_by_name[source] | 
					
						
							|  |  |  |         target_region = regions_by_name[target] | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-03 00:08:24 +01:00
										 |  |  |         backwards = " Backwards" if backwards else "" | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         connection_name = source + " to " + target + backwards | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         connection = Entrance( | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |             world.player, | 
					
						
							|  |  |  |             connection_name, | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |             source_region | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         connection.access_rule = self.make_lambda(final_requirement, world) | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         source_region.exits.append(connection) | 
					
						
							|  |  |  |         connection.connect(target_region) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-07 06:36:46 +01:00
										 |  |  |         self.created_entrances[source, target].append(connection) | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Register any necessary indirect connections | 
					
						
							|  |  |  |         mentioned_regions = { | 
					
						
							|  |  |  |             single_unlock for option in final_requirement for single_unlock in option | 
					
						
							|  |  |  |             if single_unlock in self.reference_logic.ALL_REGIONS_BY_NAME | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for dependent_region in mentioned_regions: | 
					
						
							|  |  |  |             world.multiworld.register_indirect_condition(regions_by_name[dependent_region], connection) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |     def create_regions(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic) -> None: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         Creates all the regions for The Witness | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         from . import create_region | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         all_locations = set() | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         regions_by_name = dict() | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         for region_name, region in self.reference_logic.ALL_REGIONS_BY_NAME.items(): | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |             locations_for_this_region = [ | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |                 self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] for panel in region["panels"] | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |                 if self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] | 
					
						
							|  |  |  |                 in self.player_locations.CHECK_LOCATION_TABLE | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |             ] | 
					
						
							|  |  |  |             locations_for_this_region += [ | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |                 static_witness_locations.get_event_name(panel) for panel in region["panels"] | 
					
						
							|  |  |  |                 if static_witness_locations.get_event_name(panel) in self.player_locations.EVENT_LOCATION_TABLE | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |             ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             all_locations = all_locations | set(locations_for_this_region) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |             new_region = create_region(world, region_name, self.player_locations, locations_for_this_region) | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             regions_by_name[region_name] = new_region | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         for region_name, region in self.reference_logic.ALL_REGIONS_BY_NAME.items(): | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |             for connection in player_logic.CONNECTIONS_BY_REGION_NAME[region_name]: | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |                 self.connect_if_possible(world, region_name, connection[0], connection[1], regions_by_name) | 
					
						
							|  |  |  |                 self.connect_if_possible(world, connection[0], region_name, connection[1], regions_by_name, True) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # find regions that are completely disconnected from the start node and remove them | 
					
						
							|  |  |  |         regions_to_check = {"Menu"} | 
					
						
							|  |  |  |         reachable_regions = {"Menu"} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         while regions_to_check: | 
					
						
							|  |  |  |             next_region = regions_to_check.pop() | 
					
						
							|  |  |  |             region_obj = regions_by_name[next_region] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for exit in region_obj.exits: | 
					
						
							|  |  |  |                 target = exit.connected_region | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if target.name in reachable_regions: | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  |                     continue | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |                 regions_to_check.add(target.name) | 
					
						
							|  |  |  |                 reachable_regions.add(target.name) | 
					
						
							| 
									
										
										
										
											2023-03-03 00:08:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |         self.created_regions = {k: v for k, v in regions_by_name.items() if k in reachable_regions} | 
					
						
							| 
									
										
										
										
											2023-03-03 00:08:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |         world.multiworld.regions += self.created_regions.values() | 
					
						
							| 
									
										
										
										
											2022-07-17 12:56:22 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |     def __init__(self, player_locations: WitnessPlayerLocations, world: "WitnessWorld") -> None: | 
					
						
							| 
									
										
										
										
											2024-02-11 02:25:03 +01:00
										 |  |  |         difficulty = world.options.puzzle_randomization | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-11 02:25:03 +01:00
										 |  |  |         if difficulty == "sigma_normal": | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |             self.reference_logic = static_witness_logic.sigma_normal | 
					
						
							| 
									
										
										
										
											2024-02-11 02:25:03 +01:00
										 |  |  |         elif difficulty == "sigma_expert": | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |             self.reference_logic = static_witness_logic.sigma_expert | 
					
						
							| 
									
										
										
										
											2024-02-11 02:25:03 +01:00
										 |  |  |         elif difficulty == "none": | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |             self.reference_logic = static_witness_logic.vanilla | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-12 00:27:42 +02:00
										 |  |  |         self.player_locations = player_locations | 
					
						
							|  |  |  |         self.created_entrances: Dict[Tuple[str, str], List[Entrance]] = defaultdict(lambda: []) | 
					
						
							| 
									
										
										
										
											2024-02-28 04:44:22 +01:00
										 |  |  |         self.created_regions: Dict[str, Region] = dict() |