 3fa01a41cd
			
		
	
	3fa01a41cd
	
	
	
		
			
			Basically, the function for "checking entrances both ways" only checked one way. This resulted in unreachable locations. This affects Expert seeds with (non-remote doors and specific types of EP Shuffle), as well as seeds with non-remote doors + specific types of disabled panels + specific types of EP Shuffle. Also includes two changes that makes spoiler logs nicer (not creating unnecessary events).
		
			
				
	
	
		
			221 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			221 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| Defines the rules by which locations can be accessed,
 | |
| depending on the items received
 | |
| """
 | |
| 
 | |
| from typing import TYPE_CHECKING, Callable, FrozenSet
 | |
| 
 | |
| from BaseClasses import CollectionState
 | |
| from .player_logic import WitnessPlayerLogic
 | |
| from .locations import WitnessPlayerLocations
 | |
| from . import StaticWitnessLogic, WitnessRegions
 | |
| from worlds.generic.Rules import set_rule
 | |
| 
 | |
| if TYPE_CHECKING:
 | |
|     from . import WitnessWorld
 | |
| 
 | |
| 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:
 | |
|     entrance_forward = regio.created_entrances[source, target]
 | |
|     entrance_backward = regio.created_entrances[target, source]
 | |
| 
 | |
|     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]:
 | |
|     """
 | |
|     Checks whether item and panel requirements are met for
 | |
|     a panel
 | |
|     """
 | |
| 
 | |
|     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]:
 | |
|     """
 | |
|     Lambdas are created in a for loop so values need to be captured
 | |
|     """
 | |
|     entity_req = world.player_logic.REQUIREMENTS_BY_HEX[entity_hex]
 | |
| 
 | |
|     return _meets_item_requirements(entity_req, world)
 | |
| 
 | |
| 
 | |
| def set_rules(world: "WitnessWorld"):
 | |
|     """
 | |
|     Sets all rules for all locations
 | |
|     """
 | |
| 
 | |
|     for location in world.locat.CHECK_LOCATION_TABLE:
 | |
|         real_location = location
 | |
| 
 | |
|         if location in world.locat.EVENT_LOCATION_TABLE:
 | |
|             real_location = location[:-7]
 | |
| 
 | |
|         associated_entity = world.player_logic.REFERENCE_LOGIC.ENTITIES_BY_NAME[real_location]
 | |
|         entity_hex = associated_entity["entity_hex"]
 | |
| 
 | |
|         rule = make_lambda(entity_hex, world)
 | |
| 
 | |
|         location = world.multiworld.get_location(location, world.player)
 | |
| 
 | |
|         set_rule(location, rule)
 | |
| 
 | |
|     world.multiworld.completion_condition[world.player] = lambda state: state.has('Victory', world.player)
 |