| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | """
 | 
					
						
							|  |  |  | Defines the rules by which locations can be accessed, | 
					
						
							|  |  |  | depending on the items received | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | from typing import TYPE_CHECKING, Callable, FrozenSet | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | from BaseClasses import CollectionState | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | from .player_logic import WitnessPlayerLogic | 
					
						
							|  |  |  | from .locations import WitnessPlayerLocations | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | from . import StaticWitnessLogic, WitnessRegions | 
					
						
							| 
									
										
										
										
											2023-06-26 00:38:39 +02:00
										 |  |  | from worlds.generic.Rules import set_rule | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | if TYPE_CHECKING: | 
					
						
							|  |  |  |     from . import WitnessWorld | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | laser_hexes = [ | 
					
						
							|  |  |  |     "0x028A4", | 
					
						
							|  |  |  |     "0x00274", | 
					
						
							|  |  |  |     "0x032F9", | 
					
						
							|  |  |  |     "0x01539", | 
					
						
							|  |  |  |     "0x181B3", | 
					
						
							|  |  |  |     "0x0C2B2", | 
					
						
							|  |  |  |     "0x00509", | 
					
						
							|  |  |  |     "0x00BF6", | 
					
						
							|  |  |  |     "0x014BB", | 
					
						
							|  |  |  |     "0x012FB", | 
					
						
							|  |  |  |     "0x17C65", | 
					
						
							|  |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _has_laser(laser_hex: str, world: "WitnessWorld", player: int) -> Callable[[CollectionState], bool]: | 
					
						
							|  |  |  |     if laser_hex == "0x012FB": | 
					
						
							|  |  |  |         return lambda state: ( | 
					
						
							|  |  |  |             _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat)(state) | 
					
						
							|  |  |  |             and state.has("Desert Laser Redirection", player) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         return _can_solve_panel(laser_hex, world, world.player, world.player_logic, world.locat) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _has_lasers(amount: int, world: "WitnessWorld") -> Callable[[CollectionState], bool]: | 
					
						
							|  |  |  |     laser_lambdas = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for laser_hex in laser_hexes: | 
					
						
							|  |  |  |         has_laser_lambda = _has_laser(laser_hex, world, world.player) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         laser_lambdas.append(has_laser_lambda) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return lambda state: sum(laser_lambda(state) for laser_lambda in laser_lambdas) >= amount | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _can_solve_panel(panel: str, world: "WitnessWorld", player: int, player_logic: WitnessPlayerLogic, | 
					
						
							|  |  |  |                      locat: WitnessPlayerLocations) -> Callable[[CollectionState], bool]: | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Determines whether a panel can be solved | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     panel_obj = player_logic.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel] | 
					
						
							|  |  |  |     entity_name = panel_obj["checkName"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if entity_name + " Solved" in locat.EVENT_LOCATION_TABLE: | 
					
						
							|  |  |  |         return lambda state: state.has(player_logic.EVENT_ITEM_PAIRS[entity_name + " Solved"], player) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         return make_lambda(panel, world) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _can_move_either_direction(state: CollectionState, source: str, target: str, regio: WitnessRegions) -> bool: | 
					
						
							| 
									
										
										
										
											2023-12-07 06:36:46 +01:00
										 |  |  |     entrance_forward = regio.created_entrances[source, target] | 
					
						
							|  |  |  |     entrance_backward = regio.created_entrances[target, source] | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |         any(entrance.can_reach(state) for entrance in entrance_forward) | 
					
						
							|  |  |  |         or | 
					
						
							|  |  |  |         any(entrance.can_reach(state) for entrance in entrance_backward) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _can_do_expert_pp2(state: CollectionState, world: "WitnessWorld") -> bool: | 
					
						
							|  |  |  |     player = world.player | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     hedge_2_access = ( | 
					
						
							|  |  |  |         _can_move_either_direction(state, "Keep 2nd Maze", "Keep", world.regio) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     hedge_3_access = ( | 
					
						
							|  |  |  |             _can_move_either_direction(state, "Keep 3rd Maze", "Keep", world.regio) | 
					
						
							|  |  |  |             or _can_move_either_direction(state, "Keep 3rd Maze", "Keep 2nd Maze", world.regio) | 
					
						
							|  |  |  |             and hedge_2_access | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     hedge_4_access = ( | 
					
						
							|  |  |  |             _can_move_either_direction(state, "Keep 4th Maze", "Keep", world.regio) | 
					
						
							|  |  |  |             or _can_move_either_direction(state, "Keep 4th Maze", "Keep 3rd Maze", world.regio) | 
					
						
							|  |  |  |             and hedge_3_access | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     hedge_access = ( | 
					
						
							|  |  |  |             _can_move_either_direction(state, "Keep 4th Maze", "Keep Tower", world.regio) | 
					
						
							|  |  |  |             and state.can_reach("Keep", "Region", player) | 
					
						
							|  |  |  |             and hedge_4_access | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     backwards_to_fourth = ( | 
					
						
							|  |  |  |             state.can_reach("Keep", "Region", player) | 
					
						
							|  |  |  |             and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Keep Tower", world.regio) | 
					
						
							|  |  |  |             and ( | 
					
						
							|  |  |  |                     _can_move_either_direction(state, "Keep", "Keep Tower", world.regio) | 
					
						
							|  |  |  |                     or hedge_access | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     shadows_shortcut = ( | 
					
						
							|  |  |  |             state.can_reach("Main Island", "Region", player) | 
					
						
							|  |  |  |             and _can_move_either_direction(state, "Keep 4th Pressure Plate", "Shadows", world.regio) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     backwards_access = ( | 
					
						
							|  |  |  |             _can_move_either_direction(state, "Keep 3rd Pressure Plate", "Keep 4th Pressure Plate", world.regio) | 
					
						
							|  |  |  |             and (backwards_to_fourth or shadows_shortcut) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     front_access = ( | 
					
						
							|  |  |  |             _can_move_either_direction(state, "Keep 2nd Pressure Plate", "Keep", world.regio) | 
					
						
							|  |  |  |             and state.can_reach("Keep", "Region", player) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return front_access and backwards_access | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _can_do_theater_to_tunnels(state: CollectionState, world: "WitnessWorld") -> bool: | 
					
						
							|  |  |  |     direct_access = ( | 
					
						
							|  |  |  |             _can_move_either_direction(state, "Tunnels", "Windmill Interior", world.regio) | 
					
						
							|  |  |  |             and _can_move_either_direction(state, "Theater", "Windmill Interior", world.regio) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     theater_from_town = ( | 
					
						
							|  |  |  |             _can_move_either_direction(state, "Town", "Windmill Interior", world.regio) | 
					
						
							|  |  |  |             and _can_move_either_direction(state, "Theater", "Windmill Interior", world.regio) | 
					
						
							|  |  |  |             or _can_move_either_direction(state, "Town", "Theater", world.regio) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     tunnels_from_town = ( | 
					
						
							|  |  |  |             _can_move_either_direction(state, "Tunnels", "Windmill Interior", world.regio) | 
					
						
							|  |  |  |             and _can_move_either_direction(state, "Town", "Windmill Interior", world.regio) | 
					
						
							|  |  |  |             or _can_move_either_direction(state, "Tunnels", "Town", world.regio) | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return direct_access or theater_from_town and tunnels_from_town | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _has_item(item: str, world: "WitnessWorld", player: int, | 
					
						
							|  |  |  |               player_logic: WitnessPlayerLogic, locat: WitnessPlayerLocations) -> Callable[[CollectionState], bool]: | 
					
						
							|  |  |  |     if item in player_logic.REFERENCE_LOGIC.ALL_REGIONS_BY_NAME: | 
					
						
							|  |  |  |         return lambda state: state.can_reach(item, "Region", player) | 
					
						
							|  |  |  |     if item == "7 Lasers": | 
					
						
							|  |  |  |         laser_req = world.options.mountain_lasers.value | 
					
						
							|  |  |  |         return _has_lasers(laser_req, world) | 
					
						
							|  |  |  |     if item == "11 Lasers": | 
					
						
							|  |  |  |         laser_req = world.options.challenge_lasers.value | 
					
						
							|  |  |  |         return _has_lasers(laser_req, world) | 
					
						
							|  |  |  |     elif item == "PP2 Weirdness": | 
					
						
							|  |  |  |         return lambda state: _can_do_expert_pp2(state, world) | 
					
						
							|  |  |  |     elif item == "Theater to Tunnels": | 
					
						
							|  |  |  |         return lambda state: _can_do_theater_to_tunnels(state, world) | 
					
						
							|  |  |  |     if item in player_logic.EVENT_PANELS: | 
					
						
							|  |  |  |         return _can_solve_panel(item, world, player, player_logic, locat) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     prog_item = StaticWitnessLogic.get_parent_progressive_item(item) | 
					
						
							|  |  |  |     return lambda state: state.has(prog_item, player, player_logic.MULTI_AMOUNTS[item]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _meets_item_requirements(requirements: FrozenSet[FrozenSet[str]], | 
					
						
							|  |  |  |                              world: "WitnessWorld") -> Callable[[CollectionState], bool]: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |     Checks whether item and panel requirements are met for | 
					
						
							|  |  |  |     a panel | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |     lambda_conversion = [ | 
					
						
							|  |  |  |         [_has_item(item, world, world.player, world.player_logic, world.locat) for item in subset] | 
					
						
							|  |  |  |         for subset in requirements | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return lambda state: any( | 
					
						
							|  |  |  |         all(condition(state) for condition in sub_requirement) | 
					
						
							|  |  |  |         for sub_requirement in lambda_conversion | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def make_lambda(entity_hex: str, world: "WitnessWorld") -> Callable[[CollectionState], bool]: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Lambdas are created in a for loop so values need to be captured | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |     entity_req = world.player_logic.REQUIREMENTS_BY_HEX[entity_hex] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return _meets_item_requirements(entity_req, world) | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  | def set_rules(world: "WitnessWorld"): | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Sets all rules for all locations | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |     for location in world.locat.CHECK_LOCATION_TABLE: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |         real_location = location | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         if location in world.locat.EVENT_LOCATION_TABLE: | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  |             real_location = location[:-7] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         associated_entity = world.player_logic.REFERENCE_LOGIC.ENTITIES_BY_NAME[real_location] | 
					
						
							|  |  |  |         entity_hex = associated_entity["entity_hex"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         rule = make_lambda(entity_hex, world) | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         location = world.multiworld.get_location(location, world.player) | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |         set_rule(location, rule) | 
					
						
							| 
									
										
										
										
											2022-04-29 00:42:11 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 06:27:03 +01:00
										 |  |  |     world.multiworld.completion_condition[world.player] = lambda state: state.has('Victory', world.player) |