def connections_from( self, node: Node, patches: GamePatches) -> Iterator[Tuple[Node, RequirementSet]]: """ Queries all nodes from other areas you can go from a given node. Aka, doors and teleporters :param patches: :param node: :return: Generator of pairs Node + RequirementSet for going to that node """ if isinstance(node, DockNode): # TODO: respect is_blast_shield: if already opened once, no requirement needed. # Includes opening form behind with different criteria try: target_node = self.resolve_dock_node(node, patches) original_area = self.nodes_to_area(node) dock_weakness = patches.dock_weakness.get( (original_area.area_asset_id, node.dock_index), node.default_dock_weakness) yield target_node, dock_weakness.requirements except IndexError: # TODO: fix data to not having docks pointing to nothing yield None, RequirementSet.impossible() if isinstance(node, TeleporterNode): try: yield self.resolve_teleporter_node( node, patches), RequirementSet.trivial() except IndexError: # TODO: fix data to not have teleporters pointing to areas with invalid default_node_index print("Teleporter is broken!", node) yield None, RequirementSet.impossible()
def read_area(self, data: Dict) -> Area: name = data["name"] nodes = read_array(data["nodes"], self.read_node) nodes_by_name = {node.name: node for node in nodes} connections = {} for i, origin_data in enumerate(data["nodes"]): origin = nodes[i] connections[origin] = {} extra_requirement = None if is_resource_node( origin) and self.add_self_as_requirement_to_resources: extra_requirement = RequirementList.with_single_resource( origin.resource()) for target_name, target_requirements in origin_data[ "connections"].items(): the_set = read_requirement_set(target_requirements, self.resource_database) if extra_requirement is not None: the_set = the_set.union(RequirementSet([extra_requirement ])) if the_set != RequirementSet.impossible(): connections[origin][nodes_by_name[target_name]] = the_set return Area(name, data["asset_id"], data["default_node_index"], nodes, connections)
def log_skip_action_missing_requirement(node: Node, game: GameDescription, requirement_set: RequirementSet): if _DEBUG_LEVEL > 1: if node in _last_printed_additional and _last_printed_additional[node] == requirement_set: print("{}* Skip {}, same additional".format(_indent(), n(node))) else: print("{}* Skip {}, missing additional:".format(_indent(), n(node))) requirement_set.pretty_print(_indent(-1)) _last_printed_additional[node] = requirement_set
def requirements_to_leave(self, patches: GamePatches, current_resources: CurrentResources) -> RequirementSet: if current_resources.get("add_self_as_requirement_to_resources") == 1: return RequirementSet([ RequirementList(0, [ IndividualRequirement(self.event, 1, False), ]) ]) else: return RequirementSet.trivial()
def requirements_to_leave(self, patches: GamePatches, current_resources: CurrentResources) -> RequirementSet: # FIXME: using non-resource as key in CurrentResources if current_resources.get("add_self_as_requirement_to_resources") == 1: return RequirementSet([ RequirementList(0, [ IndividualRequirement(self.pickup_index, 1, False), ]) ]) else: return RequirementSet.trivial()
def read_dock_weakness_database(data: Dict, resource_database: ResourceDatabase, ) -> DockWeaknessDatabase: door_types = read_array(data["door"], lambda item: read_dock_weakness(item, resource_database, DockType.DOOR)) portal_types = read_array(data["portal"], lambda item: read_dock_weakness(item, resource_database, DockType.PORTAL)) return DockWeaknessDatabase( door=door_types, morph_ball=[ DockWeakness(0, "Morph Ball Door", False, RequirementSet.trivial(), DockType.MORPH_BALL_DOOR) ], other=[ DockWeakness(0, "Other Door", False, RequirementSet.trivial(), DockType.OTHER) ], portal=portal_types)
def test_set_dangerous_resources(): # setup list_a = MagicMock() list_b = MagicMock() list_a.dangerous_resources = [1, 2, 3] list_b.dangerous_resources = ["a", "b", "c"] req_set = RequirementSet([]) req_set.alternatives = frozenset([list_a, list_b]) # Run result = set(req_set.dangerous_resources) # Assert assert result == {1, 2, 3, "a", "b", "c"}
def test_simplify_requirement_set_static(): res_a, id_req_a = make_req_a() res_b, id_req_b = make_req_b() the_set = RequirementSet([ RequirementList(0, [id_req_a]), RequirementList(0, [id_req_b]), ]) simple_1 = the_set.simplify({res_a: 0, res_b: 0}) simple_2 = the_set.simplify({res_a: 0, res_b: 1}) simple_3 = the_set.simplify({res_a: 1, res_b: 1}) assert simple_1.alternatives == frozenset() assert simple_2.alternatives == frozenset([RequirementList(0, [])]) assert simple_3.alternatives == frozenset([RequirementList(0, [])])
def build_requirement_set(self) -> Optional[RequirementSet]: return RequirementSet([ RequirementList.without_misc_resources( [row.current_individual for row in element.rows], self.resource_database) for element in self._elements ])
def __init__(self, parent: QWidget, grid_layout: QGridLayout, resource_database: ResourceDatabase, requirement_set: Optional[RequirementSet], edit_mode: bool, num_columns_for_alternatives: int = 2): assert requirement_set != RequirementSet.impossible() self.parent = parent self.resource_database = resource_database self.edit_mode = edit_mode self.grid_layout = grid_layout self._elements = [] self.num_columns_for_alternatives = num_columns_for_alternatives if requirement_set is not None: empty = True for alternative in requirement_set.alternatives: if alternative.items or self.edit_mode: empty = False self._add_box_with_requirements(alternative) if empty and not self.edit_mode: self._add_box_with_labels(["Trivial."]) elif not self.edit_mode: self._add_box_with_labels(["Impossible to Reach."])
def _modify_resources(game: GameDescription, resource: ResourceInfo, ): """ This change all occurrences of the given resource to have difficulty 4 :param game: :param resource: :return: """ new_difficulty = 4 database = game.resource_database def _replace(alternative: RequirementList) -> RequirementList: if alternative.get(resource) is not None: return RequirementList.without_misc_resources( database=database, items=[ (individual if individual.resource != database.difficulty_resource else IndividualRequirement(database.difficulty_resource, new_difficulty, False)) for individual in alternative.values() ] ) else: return alternative for area in game.world_list.all_areas: for source, connection in area.connections.items(): connection.update({ target: RequirementSet( _replace(alternative) for alternative in requirements.alternatives ) for target, requirements in connection.items() })
def requirements_to_leave(self, patches: GamePatches, current_resources: CurrentResources) -> RequirementSet: return RequirementSet([ RequirementList(0, [ IndividualRequirement(patches.translator_gates[self.gate], 1, False), IndividualRequirement(self.scan_visor, 1, False), ]) ])
def calculate_reach(cls, logic: Logic, initial_state: State) -> "ResolverReach": checked_nodes = set() nodes_to_check: List[Node] = [initial_state.node] reach_nodes: List[Node] = [] requirements_by_node: Dict[Node, Set[RequirementList]] = defaultdict(set) path_to_node: Dict[Node, Tuple[Node, ...]] = {} path_to_node[initial_state.node] = tuple() while nodes_to_check: node = nodes_to_check.pop() checked_nodes.add(node) if node != initial_state.node: reach_nodes.append(node) for target_node, requirements in logic.game.world_list.potential_nodes_from( node, initial_state.patches): if target_node in checked_nodes or target_node in nodes_to_check: continue # Check if the normal requirements to reach that node is satisfied satisfied = requirements.satisfied( initial_state.resources, initial_state.resource_database) if satisfied: # If it is, check if we additional requirements figured out by backtracking is satisfied satisfied = logic.get_additional_requirements( node).satisfied(initial_state.resources, initial_state.resource_database) if satisfied: nodes_to_check.append(target_node) path_to_node[target_node] = path_to_node[node] + (node, ) elif target_node: # If we can't go to this node, store the reason in order to build the satisfiable requirements. # Note we ignore the 'additional requirements' here because it'll be added on the end. requirements_by_node[target_node].update( requirements.alternatives) # Discard satisfiable requirements of nodes reachable by other means for node in set(reach_nodes).intersection(requirements_by_node.keys()): requirements_by_node.pop(node) if requirements_by_node: satisfiable_requirements = frozenset.union(*[ RequirementSet(requirements).union( logic.get_additional_requirements(node)).alternatives for node, requirements in requirements_by_node.items() ]) else: satisfiable_requirements = frozenset() return ResolverReach(reach_nodes, path_to_node, satisfiable_requirements, logic)
def test_requirement_as_set_3(): req = RequirementOr([ Requirement.impossible(), _req("A"), ]) assert req.as_set(None) == RequirementSet([ RequirementList([_req("A")]), ])
def test_requirement_as_set_2(): req = RequirementAnd([ Requirement.trivial(), _req("A"), ]) assert req.as_set(None) == RequirementSet([ RequirementList([_req("A")]), ])
def reach_from_state(cls, game: GameDescription, initial_state: State, ) -> "GeneratorReach": reach = cls(game, initial_state, networkx.DiGraph()) reach._expand_graph([GraphPath(None, initial_state.node, RequirementSet.trivial())]) return reach
def requirements_to_leave( self, patches: GamePatches, current_resources: CurrentResources) -> RequirementSet: return RequirementSet([ RequirementList(0, [ IndividualRequirement(self.pickup_node.pickup_index, 1, False), ]) ])
def read_requirement_set( data: List[List[Dict]], resource_database: ResourceDatabase) -> RequirementSet: alternatives = read_array( data, lambda x: read_requirement_list( x, resource_database=resource_database)) return RequirementSet(alternative for alternative in alternatives if alternative is not None)
def requirements_to_leave( self, patches: GamePatches, current_resources: CurrentResources) -> RequirementSet: items = [IndividualRequirement(self.scan_visor, 1, False)] if self.required_translator is not None: items.append( IndividualRequirement(self.required_translator, 1, False)) return RequirementSet([RequirementList(0, items)])
def test_requirement_as_set_4(): req = RequirementOr([ Requirement.impossible(), _req("A"), Requirement.trivial(), ]) assert req.as_set == RequirementSet([ RequirementList([]), ])
def test_replace_missing(replacement): trivial = RequirementSet.trivial() req_a = SimpleResourceInfo(0, "A", "A", "") id_req_a = IndividualRequirement(req_a, 1, False) result = trivial.replace(id_req_a, replacement) assert result == trivial
def test_requirement_as_set_5(): req = RequirementAnd([ _req("A"), _req("B"), _req("C"), ]) assert req.as_set(None) == RequirementSet([ RequirementList([_req("A"), _req("B"), _req("C")]), ])
def reach_from_state( cls, logic: Logic, initial_state: State, ) -> "GeneratorReach": reach = cls(logic, initial_state, networkx.DiGraph()) reach._expand_graph( [GraphPath(None, initial_state.node, RequirementSet.trivial())]) return reach
def _simplify_requirement_set_for_additional_requirements( requirements: RequirementSet, state: State) -> RequirementSet: new_alternatives = [ _simplify_requirement_list(alternative, state) for alternative in requirements.alternatives ] return RequirementSet(alternative for alternative in new_alternatives # RequirementList.simplify may return None if alternative is not None)
def test_prevent_redundant(): res_a, id_req_a = make_req_a() res_b, id_req_b = make_req_b() the_set = RequirementSet([ RequirementList(0, [id_req_a]), RequirementList(0, [id_req_a, id_req_b]), ]) assert the_set.alternatives == frozenset([RequirementList(0, [id_req_a])])
def reach_from_state( cls, game: GameDescription, initial_state: State, ) -> "GeneratorReach": reach = cls(game, initial_state, graph_module.RandovaniaGraph.new()) game.world_list.ensure_has_node_cache() reach._expand_graph( [GraphPath(None, initial_state.node, RequirementSet.trivial())]) return reach
def _extra_requirement_for_node(game: GameDescription, node: Node) -> Optional[RequirementSet]: extra_requirement = None if is_resource_node(node): node_resource = node.resource() if node_resource in game.dangerous_resources: extra_requirement = RequirementSet( [RequirementList.with_single_resource(node_resource)]) return extra_requirement
def test_requirement_as_set_1(): req = RequirementAnd([ _req("A"), RequirementOr([_req("B"), _req("C")]), RequirementOr([_req("D"), _req("E")]), ]) assert req.as_set == RequirementSet([ RequirementList([_req("A"), _req("B"), _req("D")]), RequirementList([_req("A"), _req("B"), _req("E")]), RequirementList([_req("A"), _req("C"), _req("D")]), RequirementList([_req("A"), _req("C"), _req("E")]), ])
def _simplify_additional_requirement_set( requirements: RequirementSet, state: State, dangerous_resources: FrozenSet[ResourceInfo], ) -> RequirementSet: new_alternatives = [ _simplify_requirement_list(alternative, state, dangerous_resources) for alternative in requirements.alternatives ] return RequirementSet(alternative for alternative in new_alternatives # RequirementList.simplify may return None if alternative is not None)
def test_apply_edit_connections_change( echoes_game_data, qtbot, ): # Setup window = DataEditorWindow(echoes_game_data, True) qtbot.addWidget(window) game = window.game_description landing_site = game.world_list.area_by_asset_id(1655756413) source = landing_site.node_with_name("Save Station") target = landing_site.node_with_name("Door to Service Access") # Run window.world_selector_box.setCurrentIndex( window.world_selector_box.findText("Temple Grounds")) window.area_selector_box.setCurrentIndex( window.area_selector_box.findText(landing_site.name)) window._apply_edit_connections(source, target, RequirementSet.trivial()) # Assert assert landing_site.connections[source][target] == RequirementSet.trivial()