 e93842a52c
			
		
	
	e93842a52c
	
	
	
		
			
			Co-authored-by: blastron <blastron@mac.com> Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
		
			
				
	
	
		
			148 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			148 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| Defines Region for The Witness, assigns locations to them,
 | |
| and connects them with the proper requirements
 | |
| """
 | |
| from typing import FrozenSet, TYPE_CHECKING, Dict, Tuple, List
 | |
| 
 | |
| from BaseClasses import Entrance, Region
 | |
| from Utils import KeyedDefaultDict
 | |
| from .static_logic import StaticWitnessLogic
 | |
| from .locations import WitnessPlayerLocations, StaticWitnessLocations
 | |
| from .player_logic import WitnessPlayerLogic
 | |
| 
 | |
| if TYPE_CHECKING:
 | |
|     from . import WitnessWorld
 | |
| 
 | |
| 
 | |
| class WitnessRegions:
 | |
|     """Class that defines Witness Regions"""
 | |
| 
 | |
|     locat = None
 | |
|     logic = None
 | |
| 
 | |
|     @staticmethod
 | |
|     def make_lambda(item_requirement: FrozenSet[FrozenSet[str]], world: "WitnessWorld"):
 | |
|         from .rules import _meets_item_requirements
 | |
| 
 | |
|         """
 | |
|         Lambdas are made in a for loop, so the values have to be captured
 | |
|         This function is for that purpose
 | |
|         """
 | |
| 
 | |
|         return _meets_item_requirements(item_requirement, world)
 | |
| 
 | |
|     def connect_if_possible(self, world: "WitnessWorld", source: str, target: str, req: FrozenSet[FrozenSet[str]],
 | |
|                             regions_by_name: Dict[str, Region], backwards: bool = False):
 | |
|         """
 | |
|         connect two regions and set the corresponding requirement
 | |
|         """
 | |
| 
 | |
|         # 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]
 | |
| 
 | |
|         backwards = " Backwards" if backwards else ""
 | |
|         connection_name = source + " to " + target + backwards
 | |
| 
 | |
|         connection = Entrance(
 | |
|             world.player,
 | |
|             connection_name,
 | |
|             source_region
 | |
|         )
 | |
| 
 | |
|         connection.access_rule = self.make_lambda(final_requirement, world)
 | |
| 
 | |
|         source_region.exits.append(connection)
 | |
|         connection.connect(target_region)
 | |
| 
 | |
|         self.created_entrances[(source, target)].append(connection)
 | |
| 
 | |
|         # 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)
 | |
| 
 | |
|     def create_regions(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic):
 | |
|         """
 | |
|         Creates all the regions for The Witness
 | |
|         """
 | |
|         from . import create_region
 | |
| 
 | |
|         all_locations = set()
 | |
|         regions_by_name = dict()
 | |
| 
 | |
|         for region_name, region in self.reference_logic.ALL_REGIONS_BY_NAME.items():
 | |
|             locations_for_this_region = [
 | |
|                 self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] for panel in region["panels"]
 | |
|                 if self.reference_logic.ENTITIES_BY_HEX[panel]["checkName"] in self.locat.CHECK_LOCATION_TABLE
 | |
|             ]
 | |
|             locations_for_this_region += [
 | |
|                 StaticWitnessLocations.get_event_name(panel) for panel in region["panels"]
 | |
|                 if StaticWitnessLocations.get_event_name(panel) in self.locat.EVENT_LOCATION_TABLE
 | |
|             ]
 | |
| 
 | |
|             all_locations = all_locations | set(locations_for_this_region)
 | |
| 
 | |
|             new_region = create_region(world, region_name, self.locat, locations_for_this_region)
 | |
| 
 | |
|             regions_by_name[region_name] = new_region
 | |
| 
 | |
|         for region_name, region in self.reference_logic.ALL_REGIONS_BY_NAME.items():
 | |
|             for connection in player_logic.CONNECTIONS_BY_REGION_NAME[region_name]:
 | |
|                 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:
 | |
|                     continue
 | |
| 
 | |
|                 regions_to_check.add(target.name)
 | |
|                 reachable_regions.add(target.name)
 | |
| 
 | |
|         final_regions_list = [v for k, v in regions_by_name.items() if k in reachable_regions]
 | |
| 
 | |
|         world.multiworld.regions += final_regions_list
 | |
| 
 | |
|     def __init__(self, locat: WitnessPlayerLocations, world: "WitnessWorld"):
 | |
|         difficulty = world.options.puzzle_randomization.value
 | |
| 
 | |
|         if difficulty == 0:
 | |
|             self.reference_logic = StaticWitnessLogic.sigma_normal
 | |
|         elif difficulty == 1:
 | |
|             self.reference_logic = StaticWitnessLogic.sigma_expert
 | |
|         elif difficulty == 2:
 | |
|             self.reference_logic = StaticWitnessLogic.vanilla
 | |
| 
 | |
|         self.locat = locat
 | |
|         self.created_entrances: Dict[Tuple[str, str], List[Entrance]] = KeyedDefaultDict(lambda _: [])
 |