Esempio n. 1
0
    def connections_from(self, node: Node, patches: GamePatches) -> Iterator[Tuple[Node, Requirement]]:
        """
        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 + Requirement 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.requirement
            except IndexError:
                # TODO: fix data to not having docks pointing to nothing
                yield None, Requirement.impossible()

        if isinstance(node, TeleporterNode):
            try:
                yield self.resolve_teleporter_node(node, patches), Requirement.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, Requirement.impossible()

        if isinstance(node, PlayerShipNode):
            for other_node in self.all_nodes:
                if isinstance(other_node, PlayerShipNode) and other_node != node:
                    yield other_node, other_node.is_unlocked
Esempio n. 2
0
def base_dock_name_raw(dock_type: DockType, weakness: DockWeakness,
                       connection: AreaIdentifier) -> str:
    expected_connector = "to"
    if weakness.requirement == Requirement.impossible(
    ) and weakness.name != "Not Determined":
        expected_connector = "from"
    return f"{dock_type.long_name} {expected_connector} {connection.area_name}"
Esempio n. 3
0
    def read_area(self, data: Dict) -> Area:
        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] = {}

            for target_name, target_requirement in origin_data[
                    "connections"].items():
                try:
                    the_set = read_requirement(target_requirement,
                                               self.resource_database)
                except MissingResource as e:
                    raise MissingResource(
                        f"In area {data['name']}, connection from {origin.name} to {target_name} got error: {e}"
                    )

                if the_set != Requirement.impossible():
                    connections[origin][nodes_by_name[target_name]] = the_set

        area_name = data["name"]
        try:
            return Area(area_name, data["in_dark_aether"], data["asset_id"],
                        data["default_node_index"],
                        data["valid_starting_location"], nodes, connections)
        except KeyError as e:
            raise KeyError(f"Missing key `{e}` for area `{area_name}`")
Esempio n. 4
0
    def configurable_node_assignment(self, configuration: DreadConfiguration, game: GameDescription,
                                     rng: Random) -> NodeConfigurationAssignment:
        result = {}

        rsb = game.resource_database

        requirement_for_type = {
            "POWERBEAM": rsb.requirement_template["Shoot Beam"],
            "BOMB": rsb.requirement_template["Lay Bomb"],
            "MISSILE": rsb.requirement_template["Shoot Missile"],
            "SUPERMISSILE": rsb.requirement_template["Shoot Super Missile"],
            "POWERBOMB": rsb.requirement_template["Lay Power Bomb"],
            "SCREWATTACK": ResourceRequirement(rsb.get_item("Screw"), 1, False),
            "WEIGHT": Requirement.impossible(),
            "SPEEDBOOST": ResourceRequirement(rsb.get_item("Speed"), 1, False),
        }

        for node in game.world_list.all_nodes:
            if not isinstance(node, ConfigurableNode):
                continue

            result[game.world_list.identifier_for_node(node)] = RequirementAnd([
                requirement_for_type[block_type]
                for block_type in node.extra["tile_types"]
            ]).simplify()

        return result
Esempio n. 5
0
def test_requirement_as_set_3():
    req = RequirementOr([
        Requirement.impossible(),
        _req("A"),
    ])
    assert req.as_set(None) == RequirementSet([
        RequirementList([_req("A")]),
    ])
Esempio n. 6
0
def test_basic_search_with_translator_gate(has_translator: bool,
                                           echoes_resource_database):
    # Setup
    scan_visor = echoes_resource_database.get_item(10)

    node_a = GenericNode("Node A", True, None, 0)
    node_b = GenericNode("Node B", True, None, 1)
    node_c = GenericNode("Node C", True, None, 2)
    translator_node = TranslatorGateNode("Translator Gate", True, None, 3,
                                         TranslatorGate(1), scan_visor)

    world_list = WorldList([
        World("Test World", "Test Dark World", 1, [
            Area(
                "Test Area A", False, 10, 0, True,
                [node_a, node_b, node_c, translator_node], {
                    node_a: {
                        node_b: Requirement.trivial(),
                        translator_node: Requirement.trivial(),
                    },
                    node_b: {
                        node_a: Requirement.trivial(),
                    },
                    node_c: {
                        translator_node: Requirement.trivial(),
                    },
                    translator_node: {
                        node_a: Requirement.trivial(),
                        node_c: Requirement.trivial(),
                    },
                })
        ])
    ])
    game_specific = EchoesGameSpecific(energy_per_tank=100,
                                       safe_zone_heal_per_second=1,
                                       beam_configurations=(),
                                       dangerous_energy_tank=False)
    game = GameDescription(RandovaniaGame.PRIME2,
                           DockWeaknessDatabase([], [], [], []),
                           echoes_resource_database, game_specific,
                           Requirement.impossible(), None, {}, world_list)

    patches = game.create_game_patches()
    patches = patches.assign_gate_assignment({TranslatorGate(1): scan_visor})
    initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99,
                          node_a, patches, None, echoes_resource_database,
                          game.world_list)

    # Run
    reach = reach_with_all_safe_resources(game, initial_state)

    # Assert
    if has_translator:
        assert set(
            reach.safe_nodes) == {node_a, node_b, translator_node, node_c}
    else:
        assert set(reach.safe_nodes) == {node_a, node_b}
Esempio n. 7
0
def test_requirement_as_set_4():
    req = RequirementOr([
        Requirement.impossible(),
        _req("A"),
        Requirement.trivial(),
    ])
    assert req.as_set == RequirementSet([
        RequirementList([]),
    ])
Esempio n. 8
0
    def replace_connection_with(self, target_node: Node,
                                requirement: Requirement):
        current_node = self.current_node

        if requirement == Requirement.impossible():
            requirement = None

        self.editor.edit_connections(self.current_area, current_node,
                                     target_node, requirement)
        self.update_connections()
        self.area_view_canvas.update()
Esempio n. 9
0
def test_basic_search_with_translator_gate(has_translator: bool, echoes_resource_database, echoes_game_patches):
    # Setup
    scan_visor = echoes_resource_database.get_item("DarkVisor")
    nc = functools.partial(NodeIdentifier.create, "Test World", "Test Area A")

    node_a = GenericNode(nc("Node A"), True, None, "", ("default",), {})
    node_b = GenericNode(nc("Node B"), True, None, "", ("default",), {})
    node_c = GenericNode(nc("Node C"), True, None, "", ("default",), {})
    translator_node = ConfigurableNode(translator_identif := nc("Translator Gate"),
                                       True, None, "", ("default",), {})

    world_list = WorldList([
        World("Test World", [
            Area("Test Area A", None, True, [node_a, node_b, node_c, translator_node],
                 {
                     node_a: {
                         node_b: Requirement.trivial(),
                         translator_node: Requirement.trivial(),
                     },
                     node_b: {
                         node_a: Requirement.trivial(),
                     },
                     node_c: {
                         translator_node: Requirement.trivial(),
                     },
                     translator_node: {
                         node_a: Requirement.trivial(),
                         node_c: Requirement.trivial(),
                     },
                 },
                 {}
                 )
        ], {})
    ])
    game = GameDescription(RandovaniaGame.METROID_PRIME_ECHOES, DockWeaknessDatabase([], {}, (None, None)),
                           echoes_resource_database, ("default",), Requirement.impossible(),
                           None, {}, None, world_list)

    patches = echoes_game_patches.assign_node_configuration({
        translator_identif: ResourceRequirement(scan_visor, 1, False)
    })
    initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99,
                          node_a, patches, None, StateGameData(echoes_resource_database, game.world_list, 100, 99))

    # Run
    reach = reach_lib.reach_with_all_safe_resources(game, initial_state)

    # Assert
    if has_translator:
        assert set(reach.safe_nodes) == {node_a, node_b, translator_node, node_c}
    else:
        assert set(reach.safe_nodes) == {node_a, node_b}
Esempio n. 10
0
    def _open_edit_connection(self):
        from_node = self.current_node
        target_node = self.current_connection_node

        assert from_node is not None
        assert target_node is not None

        requirement = self.current_area.connections[from_node].get(
            target_node, Requirement.impossible())
        editor = ConnectionsEditor(self, self.resource_database, requirement)
        result = editor.exec_()

        if result == QDialog.Accepted:
            self._apply_edit_connections(from_node, target_node,
                                         editor.final_requirement)
Esempio n. 11
0
    async def _open_edit_connection(self):
        if self._check_for_edit_dialog():
            return

        from_node = self.current_node
        target_node = self.current_connection_node
        assert from_node is not None
        assert target_node is not None

        requirement = self.current_area.connections[from_node].get(
            target_node, Requirement.impossible())
        editor = ConnectionsEditor(self, self.resource_database, requirement)
        if await self._execute_edit_dialog(editor):
            self._apply_edit_connections(from_node, target_node,
                                         editor.final_requirement)
Esempio n. 12
0
def pretty_print_requirement(requirement: Requirement,
                             level: int = 0) -> Iterator[Tuple[int, str]]:
    if requirement == Requirement.impossible():
        yield level, "Impossible"

    elif requirement == Requirement.trivial():
        yield level, "Trivial"

    elif isinstance(requirement, (RequirementAnd, RequirementOr)):
        yield from pretty_print_requirement_array(requirement, level)

    elif isinstance(requirement, ResourceRequirement):
        yield level, pretty_print_resource_requirement(requirement)

    elif isinstance(requirement, RequirementTemplate):
        yield level, requirement.template_name
    else:
        raise RuntimeError(
            f"Unknown requirement type: {type(requirement)} - {requirement}")
Esempio n. 13
0
    def update_connections(self):
        current_node = self.current_node
        current_connection_node = self.current_connection_node

        assert current_node != current_connection_node or current_node is None

        if self._connections_visualizer is not None:
            self._connections_visualizer.deleteLater()
            self._connections_visualizer = None

        if current_connection_node is None or current_node is None:
            assert len(self.current_area.nodes) <= 1 or not self.edit_mode
            return

        requirement = self.current_area.connections[current_node].get(
            self.current_connection_node, Requirement.impossible())
        self._connections_visualizer = ConnectionsVisualizer(
            self.other_node_alternatives_contents,
            self.alternatives_grid_layout, self.resource_database, requirement,
            False)
Esempio n. 14
0
    def state_for_current_configuration(self) -> Optional[State]:
        state = self._initial_state.copy()
        if self._actions:
            state.node = self._actions[-1]

        for teleporter, combo in self._elevator_id_to_combo.items():
            state.patches.elevator_connection[teleporter] = combo.currentData()

        for gate, item in self._translator_gate_to_combo.items():
            scan_visor = self.game_description.resource_database.get_item(
                "Scan")

            requirement: Optional[
                LayoutTranslatorRequirement] = item.currentData()
            if requirement is None:
                translator_req = Requirement.impossible()
            else:
                translator = self.game_description.resource_database.get_item(
                    requirement.item_name)
                translator_req = ResourceRequirement(translator, 1, False)

            state.patches.configurable_nodes[gate] = RequirementAnd([
                ResourceRequirement(scan_visor, 1, False),
                translator_req,
            ])

        for pickup, quantity in self._collected_pickups.items():
            for _ in range(quantity):
                add_pickup_to_state(state, pickup)

        for node in self._collected_nodes:
            add_resource_gain_to_current_resources(
                node.resource_gain_on_collect(state.node_context()),
                state.resources)

        return state
Esempio n. 15
0
 def final_requirement(self) -> Optional[Requirement]:
     result = self.build_requirement()
     if result == Requirement.impossible():
         return None
     return result
Esempio n. 16
0
    def contextMenuEvent(self, event: QtGui.QContextMenuEvent) -> None:
        local_pos = QPointF(self.mapFromGlobal(event.globalPos()))
        local_pos -= self.get_area_canvas_offset()
        self._next_node_location = self.qt_local_to_game_loc(local_pos)

        menu = QtWidgets.QMenu(self)
        if self.state is None:
            menu.addAction(self._show_all_connections_action)
        if self.edit_mode:
            menu.addAction(self._create_node_action)
            menu.addAction(self._move_node_action)
            self._move_node_action.setEnabled(
                self.highlighted_node is not None)
            if self.highlighted_node is not None:
                self._move_node_action.setText(
                    f"Move {self.highlighted_node.name} here")

        # Areas Menu
        menu.addSeparator()
        areas_at_mouse = self._other_areas_at_position(local_pos)

        for area in areas_at_mouse:
            sub_menu = QtWidgets.QMenu(f"Area: {area.name}", self)
            sub_menu.addAction("View area").triggered.connect(
                functools.partial(self.SelectAreaRequest.emit, area))
            if self.edit_mode:
                sub_menu.addAction(
                    "Create dock here to this area").triggered.connect(
                        functools.partial(self.CreateDockRequest.emit,
                                          self._next_node_location, area))
            menu.addMenu(sub_menu)

        if not areas_at_mouse:
            sub_menu = QtGui.QAction("No areas here", self)
            sub_menu.setEnabled(False)
            menu.addAction(sub_menu)

        # Nodes Menu
        menu.addSeparator()
        nodes_at_mouse = self._nodes_at_position(local_pos)
        if self.highlighted_node in nodes_at_mouse:
            nodes_at_mouse.remove(self.highlighted_node)

        for node in nodes_at_mouse:
            if len(nodes_at_mouse) == 1:
                menu.addAction(node.name).setEnabled(False)
                sub_menu = menu
            else:
                sub_menu = QtWidgets.QMenu(node.name, self)

            sub_menu.addAction("Highlight this").triggered.connect(
                functools.partial(self.SelectNodeRequest.emit, node))
            view_connections = sub_menu.addAction("View connections to this")
            view_connections.setEnabled(
                (self.edit_mode and self.highlighted_node != node) or
                (node in self.area.connections.get(self.highlighted_node, {})))
            view_connections.triggered.connect(
                functools.partial(self.SelectConnectionsRequest.emit, node))

            if self.edit_mode:
                sub_menu.addSeparator()
                sub_menu.addAction(
                    "Replace connection with Trivial").triggered.connect(
                        functools.partial(
                            self.ReplaceConnectionsRequest.emit,
                            node,
                            Requirement.trivial(),
                        ))
                sub_menu.addAction("Remove connection").triggered.connect(
                    functools.partial(
                        self.ReplaceConnectionsRequest.emit,
                        node,
                        Requirement.impossible(),
                    ))
                if areas_at_mouse:
                    move_menu = QtWidgets.QMenu("Move to...", self)
                    for area in areas_at_mouse:
                        move_menu.addAction(area.name).triggered.connect(
                            functools.partial(
                                self.MoveNodeToAreaRequest.emit,
                                node,
                                area,
                            ))
                    sub_menu.addMenu(move_menu)

            if sub_menu != menu:
                menu.addMenu(sub_menu)

        if not nodes_at_mouse:
            sub_menu = QtGui.QAction("No other nodes here", self)
            sub_menu.setEnabled(False)
            menu.addAction(sub_menu)

        # Done

        menu.exec_(event.globalPos())
Esempio n. 17
0
def test_impossible_requirement_str():
    assert str(Requirement.impossible()) == "Impossible"
Esempio n. 18
0
def test_impossible_requirement_damage():
    assert Requirement.impossible().damage({}, None) == MAX_DAMAGE
Esempio n. 19
0
def test_impossible_requirement_satisfied():
    assert not Requirement.impossible().satisfied({}, 99, None)
Esempio n. 20
0
def test_impossible_requirement_as_set():
    assert Requirement.impossible().as_set(None) == RequirementSet.impossible()
Esempio n. 21
0
def find_invalid_strongly_connected_components(
        game: GameDescription) -> Iterator[str]:
    import networkx
    graph = networkx.DiGraph()

    for node in game.world_list.all_nodes:
        if isinstance(node, DockLockNode):
            continue
        graph.add_node(node)

    context = NodeContext(
        patches=GamePatches(
            player_index=0,
            configuration=None,
            pickup_assignment={},
            elevator_connection={},
            dock_connection={},
            dock_weakness={},
            configurable_nodes={},
            starting_items={},
            starting_location=game.starting_location,
            hints={},
        ),
        current_resources={},
        database=game.resource_database,
        node_provider=game.world_list,
    )

    for node in game.world_list.all_nodes:
        if node not in graph:
            continue

        for other, req in game.world_list.potential_nodes_from(node, context):
            if other not in graph:
                continue

            if req != Requirement.impossible():
                graph.add_edge(node, other)

    starting_node = game.world_list.resolve_teleporter_connection(
        game.starting_location)

    for strong_comp in networkx.strongly_connected_components(graph):
        components: set[Node] = strong_comp

        # The starting location determines the default component
        if starting_node in components:
            continue

        if any(
                node.extra.get("different_strongly_connected_component", False)
                for node in components):
            continue

        if len(components) == 1:
            node = next(iter(components))

            # If the component is a single node which is the default node of it's area, allow it
            area = game.world_list.nodes_to_area(node)
            if area.default_node == node.name:
                continue

            # We accept nodes that have no paths out or in.
            if not graph.in_edges(node) and not graph.edges(node):
                continue

        names = sorted(
            game.world_list.node_name(node, with_world=True)
            for node in strong_comp)
        yield "Unknown strongly connected component detected containing {} nodes:\n{}".format(
            len(names), names)