Example #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
Example #2
0
def test_requirement_as_set_4():
    req = RequirementOr([
        Requirement.impossible(),
        _req("A"),
        Requirement.trivial(),
    ])
    assert req.as_set(None) == RequirementSet([
        RequirementList([]),
    ])
Example #3
0
def test_edit_connections(game_editor):
    # Setup
    landing_site = game_editor.game.world_list.area_by_area_location(
        AreaIdentifier("Temple Grounds", "Landing Site"))
    source = landing_site.node_with_name("Save Station")
    target = landing_site.node_with_name("Door to Service Access")
    assert landing_site.connections[source][target] != Requirement.trivial()

    # Run
    game_editor.edit_connections(landing_site, source, target,
                                 Requirement.trivial())

    # Assert
    assert landing_site.connections[source][target] == Requirement.trivial()
Example #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
Example #5
0
 def requirement_to_leave(
         self, patches: GamePatches,
         current_resources: CurrentResources) -> Requirement:
     if current_resources.get("add_self_as_requirement_to_resources") == 1:
         return ResourceRequirement(self.event, 1, False)
     else:
         return Requirement.trivial()
Example #6
0
 def requirement_to_leave(self, context: NodeContext) -> Requirement:
     # FIXME: using non-resource as key in CurrentResources
     if context.current_resources.get(
             "add_self_as_requirement_to_resources") == 1:
         return ResourceRequirement(self.pickup_index, 1, False)
     else:
         return Requirement.trivial()
Example #7
0
    def _lock_connection(
            self,
            context: NodeContext) -> typing.Optional[tuple[Node, Requirement]]:
        requirement = Requirement.trivial()

        forward_weakness = self.get_front_weakness(context)
        forward_lock = forward_weakness.lock

        if forward_lock is not None:
            requirement = self._get_lock_requirement(forward_weakness)

        back_weakness = self.get_back_weakness(context)
        back_lock = None
        if back_weakness is not None:
            back_lock = back_weakness.lock

            if back_lock is not None and back_lock.lock_type == DockLockType.FRONT_BLAST_BACK_BLAST:
                requirement = RequirementAnd(
                    [requirement, back_lock.requirement])

        if forward_lock is None and back_lock is None:
            return None

        lock_node = context.node_provider.node_by_identifier(
            self.get_lock_node_identifier(context))
        return lock_node, requirement
Example #8
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}"
Example #9
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}`")
Example #10
0
    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, Requirement.trivial())])
        return reach
Example #11
0
 def requirement_to_leave(
         self, patches: GamePatches,
         current_resources: CurrentResources) -> Requirement:
     # FIXME: using non-resource as key in CurrentResources
     if current_resources.get("add_self_as_requirement_to_resources") == 1:
         return ResourceRequirement(self.pickup_index, 1, False)
     else:
         return Requirement.trivial()
Example #12
0
def test_requirement_as_set_3():
    req = RequirementOr([
        Requirement.impossible(),
        _req("A"),
    ])
    assert req.as_set == RequirementSet([
        RequirementList([_req("A")]),
    ])
Example #13
0
def test_requirement_as_set_2():
    req = RequirementAnd([
        Requirement.trivial(),
        _req("A"),
    ])
    assert req.as_set(None) == RequirementSet([
        RequirementList([_req("A")]),
    ])
Example #14
0
    def create_new_template(self):
        template_name, did_confirm = QtWidgets.QInputDialog.getText(self, "New Template", "Insert template name:")
        if not did_confirm or template_name == "":
            return

        self.db.requirement_template[template_name] = Requirement.trivial()
        self.create_template_editor(template_name)
        self.tab_template_layout.removeWidget(self.create_new_template_button)
        self.tab_template_layout.addWidget(self.create_new_template_button)
Example #15
0
    def reach_from_state(
        cls,
        game: GameDescription,
        initial_state: State,
    ) -> "GeneratorReach":

        reach = cls(game, initial_state, graph_module.RandovaniaGraph.new())
        reach._expand_graph(
            [GraphPath(None, initial_state.node, Requirement.trivial())])
        return reach
Example #16
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}")
Example #17
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}
Example #18
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()
Example #19
0
    def connections_from(
            self,
            context: NodeContext) -> typing.Iterator[tuple[Node, Requirement]]:
        target_area_identifier = context.patches.elevator_connection.get(
            context.node_provider.identifier_for_node(self),
            self.default_connection,
        )
        if target_area_identifier is None:
            return

        yield context.node_provider.default_node_for_area(
            target_area_identifier), Requirement.trivial()
Example #20
0
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,
                                                 Requirement.trivial(),
                                                 DockType.MORPH_BALL_DOOR)
                                ],
                                other=[
                                    DockWeakness(0, "Other Door", False,
                                                 Requirement.trivial(),
                                                 DockType.OTHER)
                                ],
                                portal=portal_types)
Example #21
0
def test_apply_edit_connections_change(
    echoes_game_data,
    skip_qtbot,
):
    # Setup
    window = DataEditorWindow(echoes_game_data, True)
    skip_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 (Sky Temple Grounds)"))
    window.area_selector_box.setCurrentIndex(
        window.area_selector_box.findText(landing_site.name))
    window._apply_edit_connections(source, target, Requirement.trivial())

    # Assert
    assert landing_site.connections[source][target] == Requirement.trivial()
Example #22
0
def test_connections_from_dock_blast_shield(empty_patches):
    # Setup
    trivial = Requirement.trivial()
    req_1 = ResourceRequirement(
        SimpleResourceInfo("Ev1", "Ev1", ResourceType.EVENT), 1, False)
    req_2 = ResourceRequirement(
        SimpleResourceInfo("Ev2", "Ev2", ResourceType.EVENT), 1, False)
    dock_type = DockType("Type", "Type", frozendict())
    weak_1 = DockWeakness("Weak 1", frozendict(), req_1, None)
    weak_2 = DockWeakness("Weak 2", frozendict(), trivial,
                          DockLock(DockLockType.FRONT_BLAST_BACK_BLAST, req_2))

    node_1_identifier = NodeIdentifier.create("W", "Area 1", "Node 1")
    node_2_identifier = NodeIdentifier.create("W", "Area 2", "Node 2")

    node_1 = DockNode(node_1_identifier, False, None, "", ("default", ), {},
                      dock_type, node_2_identifier, weak_1, None, None)
    node_1_lock = DockLockNode.create_from_dock(node_1)
    node_2 = DockNode(node_2_identifier, False, None, "", ("default", ), {},
                      dock_type, node_1_identifier, weak_2, None, None)
    node_2_lock = DockLockNode.create_from_dock(node_2)

    area_1 = Area("Area 1", None, True, [node_1, node_1_lock], {}, {})
    area_2 = Area("Area 2", None, True, [node_2, node_2_lock], {}, {})

    world = World("W", [area_1, area_2], {})
    world_list = WorldList([world])

    context = NodeContext(
        patches=empty_patches,
        current_resources={},
        database=None,
        node_provider=world_list,
    )

    # Run
    result_1 = list(node_1.connections_from(context))
    result_2 = list(node_2.connections_from(context))

    # Assert
    assert result_1 == [
        (node_2,
         RequirementAnd([req_1,
                         ResourceRequirement.simple(node_2_identifier)])),
        (node_1_lock, RequirementAnd([trivial, req_2])),
    ]
    assert result_2 == [
        (node_1, ResourceRequirement.simple(node_2_identifier)),
        (node_2_lock, req_2),
    ]
Example #23
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}
Example #24
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)
Example #25
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)
Example #26
0
    def _potential_nodes_from(self, node: Node) -> Iterator[Tuple[Node, Requirement, bool]]:
        extra_requirement = _extra_requirement_for_node(self._game, node)
        requirement_to_leave = node.requirement_to_leave(self._state.patches, self._state.resources)

        for target_node, requirement in self._game.world_list.potential_nodes_from(node, self.state.patches):
            if target_node is None:
                continue

            if requirement_to_leave != Requirement.trivial():
                requirement = RequirementAnd([requirement, requirement_to_leave])

            if extra_requirement is not None:
                requirement = RequirementAnd([requirement, extra_requirement])

            satisfied = requirement.satisfied(self._state.resources, self._state.energy)
            yield target_node, requirement, satisfied
Example #27
0
    def satisfiable_actions(
        self,
        state: State,
        victory_condition: Requirement,
    ) -> Iterator[Tuple[ResourceNode, int]]:

        interesting_resources = calculate_interesting_resources(
            self._satisfiable_requirements.union(
                victory_condition.as_set(
                    state.resource_database).alternatives), state.resources,
            state.energy, state.resource_database)

        # print(" > satisfiable actions, with {} interesting resources".format(len(interesting_resources)))
        for action, energy in self.possible_actions(state):
            for resource, amount in action.resource_gain_on_collect(
                    state.node_context()):
                if resource in interesting_resources:
                    yield action, energy
                    break
Example #28
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)
Example #29
0
    def _potential_nodes_from(
            self, node: Node) -> Iterator[Tuple[Node, RequirementSet]]:
        extra_requirement = _extra_requirement_for_node(
            self._game, self.node_context(), node)
        requirement_to_leave = node.requirement_to_leave(
            self._state.node_context())

        for target_node, requirement in self._game.world_list.potential_nodes_from(
                node, self.node_context()):
            if target_node is None:
                continue

            if requirement_to_leave != Requirement.trivial():
                requirement = RequirementAnd(
                    [requirement, requirement_to_leave])

            if extra_requirement is not None:
                requirement = RequirementAnd([requirement, extra_requirement])

            yield target_node, requirement.as_set(
                self._state.resource_database)
Example #30
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