Exemplo n.º 1
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()
Exemplo n.º 2
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
Exemplo n.º 3
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()
Exemplo n.º 4
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()
Exemplo n.º 5
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
Exemplo n.º 6
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
Exemplo n.º 7
0
def test_requirement_as_set_2():
    req = RequirementAnd([
        Requirement.trivial(),
        _req("A"),
    ])
    assert req.as_set(None) == RequirementSet([
        RequirementList([_req("A")]),
    ])
Exemplo n.º 8
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()
Exemplo n.º 9
0
def test_requirement_as_set_4():
    req = RequirementOr([
        Requirement.impossible(),
        _req("A"),
        Requirement.trivial(),
    ])
    assert req.as_set(None) == RequirementSet([
        RequirementList([]),
    ])
Exemplo n.º 10
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)
Exemplo n.º 11
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
Exemplo n.º 12
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()
Exemplo n.º 13
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)
Exemplo n.º 14
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),
    ]
Exemplo n.º 15
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()
Exemplo n.º 16
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}
Exemplo n.º 17
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
Exemplo n.º 18
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}
Exemplo n.º 19
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}")
Exemplo n.º 20
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)
Exemplo n.º 21
0
    def calculate_reach(cls,
                        logic: Logic,
                        initial_state: State) -> "ResolverReach":

        checked_nodes: Dict[Node, int] = {}

        # Keys: nodes to check
        # Value: how much energy was available when visiting that node
        nodes_to_check: Dict[Node, int] = {
            initial_state.node: initial_state.energy
        }

        reach_nodes: Dict[Node, int] = {}
        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 = next(iter(nodes_to_check))
            energy = nodes_to_check.pop(node)

            if node.heal:
                energy = initial_state.maximum_energy

            checked_nodes[node] = energy
            if node != initial_state.node:
                reach_nodes[node] = energy

            requirement_to_leave = node.requirement_to_leave(initial_state.patches, initial_state.resources)

            for target_node, requirement in logic.game.world_list.potential_nodes_from(node, initial_state.patches):
                if target_node is None:
                    continue

                if checked_nodes.get(target_node, math.inf) <= energy or nodes_to_check.get(target_node,
                                                                                            math.inf) <= energy:
                    continue

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

                # Check if the normal requirements to reach that node is satisfied
                satisfied = requirement.satisfied(initial_state.resources, energy)
                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,
                                                                                  energy)

                if satisfied:
                    nodes_to_check[target_node] = energy - requirement.damage(initial_state.resources)
                    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(requirement.as_set.alternatives)

        # Discard satisfiable requirements of nodes reachable by other means
        for node in set(reach_nodes.keys()).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)
Exemplo n.º 22
0
 def requirement_to_leave(self, patches: GamePatches, current_resources: CurrentResources) -> Requirement:
     return Requirement.trivial()
Exemplo n.º 23
0
         ]),
         _req("A"),
 ),
 (
         RequirementAnd([
             RequirementOr([
                 _req("A"),
             ]),
         ]),
         _req("A"),
 ),
 (
         RequirementAnd([
             RequirementOr([
                 _req("B"),
                 Requirement.trivial()
             ]),
             RequirementAnd([
                 Requirement.trivial(),
                 _req("A"),
             ]),
         ]),
         _req("A"),
 ),
 (
         RequirementOr([
             _req("A"),
             RequirementAnd([_req("B"), _req("C")]),
             RequirementAnd([_req("B"), _req("D")]),
         ]),
         RequirementOr([
Exemplo n.º 24
0
    def __init__(self, game: GameDescription, node: Node):
        super().__init__()
        self.setupUi(self)
        common_qt_lib.set_default_window_icon(self)

        self.game = game
        self.node = node
        self.world = game.world_list.nodes_to_world(node)
        world = self.world

        self._type_to_tab = {
            GenericNode: self.tab_generic,
            DockNode: self.tab_dock,
            PickupNode: self.tab_pickup,
            TeleporterNode: self.tab_teleporter,
            EventNode: self.tab_event,
            TranslatorGateNode: self.tab_translator_gate,
            LogbookNode: self.tab_logbook,
            PlayerShipNode: self.tab_player_ship,
        }
        tab_to_type = {
            tab: node_type
            for node_type, tab in self._type_to_tab.items()
        }

        # Dynamic Stuff
        for i, node_type in enumerate(self._type_to_tab.keys()):
            self.node_type_combo.setItemData(i, node_type)

        for area in world.areas:
            self.dock_connection_area_combo.addItem(area.name, area)
        refresh_if_needed(self.dock_connection_area_combo,
                          self.on_dock_connection_area_combo)

        for i, enum in enumerate(enum_lib.iterate_enum(DockType)):
            self.dock_type_combo.setItemData(i, enum)

        for world in sorted(game.world_list.worlds, key=lambda x: x.name):
            self.teleporter_destination_world_combo.addItem(
                "{0.name} ({0.dark_name})".format(world), userData=world)
        refresh_if_needed(self.teleporter_destination_world_combo,
                          self.on_teleporter_destination_world_combo)

        for event in sorted(game.resource_database.event,
                            key=lambda it: it.long_name):
            self.event_resource_combo.addItem(event.long_name, event)
        if self.event_resource_combo.count() == 0:
            self.event_resource_combo.addItem("No events in database", None)
            self.event_resource_combo.setEnabled(False)

        for i, enum in enumerate(enum_lib.iterate_enum(LoreType)):
            self.lore_type_combo.setItemData(i, enum)
        refresh_if_needed(self.lore_type_combo, self.on_lore_type_combo)

        self.set_unlocked_by(Requirement.trivial())

        # Signals
        self.button_box.accepted.connect(self.try_accept)
        self.button_box.rejected.connect(self.reject)
        self.node_type_combo.currentIndexChanged.connect(
            self.on_node_type_combo)
        self.dock_connection_area_combo.currentIndexChanged.connect(
            self.on_dock_connection_area_combo)
        self.dock_connection_node_combo.currentIndexChanged.connect(
            self.on_dock_connection_node_combo)
        self.dock_type_combo.currentIndexChanged.connect(
            self.on_dock_type_combo)
        self.teleporter_destination_world_combo.currentIndexChanged.connect(
            self.on_teleporter_destination_world_combo)
        self.lore_type_combo.currentIndexChanged.connect(
            self.on_lore_type_combo)
        self.player_ship_unlocked_button.clicked.connect(
            self.on_player_ship_unlocked_button)

        # Hide the tab bar
        tab_bar: QtWidgets.QTabBar = self.tab_widget.findChild(
            QtWidgets.QTabBar)
        tab_bar.hide()

        # Values
        self.name_edit.setText(node.name)
        self.heals_check.setChecked(node.heal)
        self.location_group.setChecked(node.location is not None)
        if node.location is not None:
            self.location_x_spin.setValue(node.location.x)
            self.location_y_spin.setValue(node.location.y)
            self.location_z_spin.setValue(node.location.z)

        visible_tab = self._fill_for_type(node)
        self.node_type_combo.setCurrentIndex(
            self.node_type_combo.findData(tab_to_type[visible_tab]))
        refresh_if_needed(self.node_type_combo, self.on_node_type_combo)
Exemplo n.º 25
0
class NodeDetailsPopup(QtWidgets.QDialog, Ui_NodeDetailsPopup):
    _unlocked_by_requirement = Requirement.trivial()
    _connections_visualizer = None

    def __init__(self, game: GameDescription, node: Node):
        super().__init__()
        self.setupUi(self)
        common_qt_lib.set_default_window_icon(self)

        self.game = game
        self.node = node
        self.world = game.world_list.nodes_to_world(node)
        world = self.world

        self._type_to_tab = {
            GenericNode: self.tab_generic,
            DockNode: self.tab_dock,
            PickupNode: self.tab_pickup,
            TeleporterNode: self.tab_teleporter,
            EventNode: self.tab_event,
            TranslatorGateNode: self.tab_translator_gate,
            LogbookNode: self.tab_logbook,
            PlayerShipNode: self.tab_player_ship,
        }
        tab_to_type = {
            tab: node_type
            for node_type, tab in self._type_to_tab.items()
        }

        # Dynamic Stuff
        for i, node_type in enumerate(self._type_to_tab.keys()):
            self.node_type_combo.setItemData(i, node_type)

        for area in world.areas:
            self.dock_connection_area_combo.addItem(area.name, area)
        refresh_if_needed(self.dock_connection_area_combo,
                          self.on_dock_connection_area_combo)

        for i, enum in enumerate(enum_lib.iterate_enum(DockType)):
            self.dock_type_combo.setItemData(i, enum)

        for world in sorted(game.world_list.worlds, key=lambda x: x.name):
            self.teleporter_destination_world_combo.addItem(
                "{0.name} ({0.dark_name})".format(world), userData=world)
        refresh_if_needed(self.teleporter_destination_world_combo,
                          self.on_teleporter_destination_world_combo)

        for event in sorted(game.resource_database.event,
                            key=lambda it: it.long_name):
            self.event_resource_combo.addItem(event.long_name, event)
        if self.event_resource_combo.count() == 0:
            self.event_resource_combo.addItem("No events in database", None)
            self.event_resource_combo.setEnabled(False)

        for i, enum in enumerate(enum_lib.iterate_enum(LoreType)):
            self.lore_type_combo.setItemData(i, enum)
        refresh_if_needed(self.lore_type_combo, self.on_lore_type_combo)

        self.set_unlocked_by(Requirement.trivial())

        # Signals
        self.button_box.accepted.connect(self.try_accept)
        self.button_box.rejected.connect(self.reject)
        self.node_type_combo.currentIndexChanged.connect(
            self.on_node_type_combo)
        self.dock_connection_area_combo.currentIndexChanged.connect(
            self.on_dock_connection_area_combo)
        self.dock_connection_node_combo.currentIndexChanged.connect(
            self.on_dock_connection_node_combo)
        self.dock_type_combo.currentIndexChanged.connect(
            self.on_dock_type_combo)
        self.teleporter_destination_world_combo.currentIndexChanged.connect(
            self.on_teleporter_destination_world_combo)
        self.lore_type_combo.currentIndexChanged.connect(
            self.on_lore_type_combo)
        self.player_ship_unlocked_button.clicked.connect(
            self.on_player_ship_unlocked_button)

        # Hide the tab bar
        tab_bar: QtWidgets.QTabBar = self.tab_widget.findChild(
            QtWidgets.QTabBar)
        tab_bar.hide()

        # Values
        self.name_edit.setText(node.name)
        self.heals_check.setChecked(node.heal)
        self.location_group.setChecked(node.location is not None)
        if node.location is not None:
            self.location_x_spin.setValue(node.location.x)
            self.location_y_spin.setValue(node.location.y)
            self.location_z_spin.setValue(node.location.z)

        visible_tab = self._fill_for_type(node)
        self.node_type_combo.setCurrentIndex(
            self.node_type_combo.findData(tab_to_type[visible_tab]))
        refresh_if_needed(self.node_type_combo, self.on_node_type_combo)

    def _fill_for_type(self, node: Node) -> QtWidgets.QWidget:
        if isinstance(node, GenericNode):
            return self.tab_generic

        elif isinstance(node, DockNode):
            self.fill_for_dock(node)
            return self.tab_dock

        elif isinstance(node, PickupNode):
            self.fill_for_pickup(node)
            return self.tab_pickup

        elif isinstance(node, TeleporterNode):
            self.fill_for_teleporter(node)
            return self.tab_teleporter

        elif isinstance(node, EventNode):
            self.fill_for_event(node)
            return self.tab_event

        elif isinstance(node, TranslatorGateNode):
            self.fill_for_translator_gate(node)
            return self.tab_translator_gate

        elif isinstance(node, LogbookNode):
            self.fill_for_logbook_node(node)
            return self.tab_logbook

        elif isinstance(node, PlayerShipNode):
            self.fill_for_player_ship_node(node)
            return self.tab_player_ship

        else:
            raise ValueError(f"Unknown node type: {node}")

    def fill_for_dock(self, node: DockNode):
        self.dock_index_spin.setValue(node.dock_index)

        # Connection
        other_area = self.game.world_list.area_by_area_location(
            AreaLocation(self.world.world_asset_id,
                         node.default_connection.area_asset_id))
        self.dock_connection_area_combo.setCurrentIndex(
            self.dock_connection_area_combo.findData(other_area))
        refresh_if_needed(self.dock_connection_area_combo,
                          self.on_dock_connection_area_combo)
        self.dock_connection_node_combo.setCurrentIndex(
            self.dock_connection_node_combo.findData(
                node.default_connection.dock_index))
        self.dock_connection_index_raw_spin.setValue(
            node.default_connection.dock_index)

        # Dock Weakness
        self.dock_type_combo.setCurrentIndex(
            self.dock_type_combo.findData(
                node.default_dock_weakness.dock_type))
        refresh_if_needed(self.dock_type_combo, self.on_dock_type_combo)
        self.dock_weakness_combo.setCurrentIndex(
            self.dock_weakness_combo.findData(node.default_dock_weakness))

    def fill_for_pickup(self, node: PickupNode):
        self.pickup_index_spin.setValue(node.pickup_index.index)
        self.major_location_check.setChecked(node.major_location)

    def fill_for_teleporter(self, node: TeleporterNode):
        world = self.game.world_list.world_by_asset_id(
            node.default_connection.world_asset_id)
        area = self.game.world_list.area_by_area_location(
            node.default_connection)

        self.teleporter_instance_id_edit.setText(
            hex(node.teleporter_instance_id) if node.
            teleporter_instance_id is not None else "")
        self.teleporter_destination_world_combo.setCurrentIndex(
            self.teleporter_destination_world_combo.findData(world))
        refresh_if_needed(self.teleporter_destination_world_combo,
                          self.on_teleporter_destination_world_combo)
        self.teleporter_destination_area_combo.setCurrentIndex(
            self.teleporter_destination_area_combo.findData(area))
        self.teleporter_scan_asset_id_edit.setText(
            hex(node.scan_asset_id) if node.scan_asset_id is not None else "")
        self.teleporter_editable_check.setChecked(node.editable)
        self.teleporter_vanilla_name_edit.setChecked(
            node.keep_name_when_vanilla)

    def fill_for_event(self, node: EventNode):
        self.event_resource_combo.setCurrentIndex(
            self.event_resource_combo.findData(node.event))

    def fill_for_translator_gate(self, node: TranslatorGateNode):
        self.translator_gate_spin.setValue(node.gate.index)

    def fill_for_logbook_node(self, node: LogbookNode):
        self.logbook_string_asset_id_edit.setText(
            hex(node.string_asset_id).upper())
        self.lore_type_combo.setCurrentIndex(
            self.lore_type_combo.findData(node.lore_type))
        refresh_if_needed(self.lore_type_combo, self.on_lore_type_combo)

        if node.lore_type == LoreType.LUMINOTH_LORE:
            self.logbook_extra_combo.setCurrentIndex(
                self.logbook_extra_combo.findData(node.required_translator))

        elif node.lore_type == LoreType.LUMINOTH_WARRIOR:
            self.logbook_extra_combo.setCurrentIndex(
                self.logbook_extra_combo.findData(node.hint_index))

    def fill_for_player_ship_node(self, node: PlayerShipNode):
        self.set_unlocked_by(node.is_unlocked)

    def set_unlocked_by(self, requirement: Requirement):
        if self._connections_visualizer is not None:
            self._connections_visualizer.deleteLater()
            self._connections_visualizer = None

        self._unlocked_by_requirement = requirement
        self._connections_visualizer = ConnectionsVisualizer(
            self.player_ship_unlocked_group, self.player_ship_unlocked_layout,
            self.game.resource_database, requirement, False)

    # Signals
    def on_node_type_combo(self, _):
        self.tab_widget.setCurrentWidget(
            self._type_to_tab[self.node_type_combo.currentData()])

    def on_dock_connection_area_combo(self, _):
        area: Area = self.dock_connection_area_combo.currentData()

        self.dock_connection_node_combo.clear()
        for node in area.nodes:
            if isinstance(node, DockNode):
                self.dock_connection_node_combo.addItem(
                    f"{node.name} - Dock {node.dock_index}", node.dock_index)
        self.dock_connection_node_combo.addItem("Other", None)

    def on_dock_connection_node_combo(self, _):
        self.dock_connection_index_raw_spin.setEnabled(
            self.dock_connection_node_combo.currentData() is None)

    def on_dock_type_combo(self, _):
        self.dock_weakness_combo.clear()

        for weakness in self.game.dock_weakness_database.get_by_type(
                self.dock_type_combo.currentData()):
            self.dock_weakness_combo.addItem(weakness.name, weakness)

    def on_teleporter_destination_world_combo(self, _):
        world: World = self.teleporter_destination_world_combo.currentData()

        self.teleporter_destination_area_combo.clear()
        for area in sorted(world.areas, key=lambda x: x.name):
            self.teleporter_destination_area_combo.addItem(area.name,
                                                           userData=area)

    def on_lore_type_combo(self, _):
        lore_type: LoreType = self.lore_type_combo.currentData()

        self.logbook_extra_combo.clear()

        if lore_type == LoreType.LUMINOTH_LORE:
            self.logbook_extra_label.setText("Translator needed:")
            for item in self.game.resource_database.item:
                self.logbook_extra_combo.addItem(item.long_name, item)

        elif lore_type == LoreType.LUMINOTH_WARRIOR:
            self.logbook_extra_label.setText("Pickup index hinted:")
            for node in self.game.world_list.all_nodes:
                if isinstance(node, PickupNode):
                    self.logbook_extra_combo.addItem(
                        "{} - {}".format(
                            self.game.world_list.node_name(node, True, True),
                            node.pickup_index.index,
                        ), node.pickup_index.index)

        else:
            self.logbook_extra_label.setText("Nothing:")
            self.logbook_extra_combo.addItem("Nothing", None)

    @asyncSlot()
    async def on_player_ship_unlocked_button(self):
        self._edit_popup = ConnectionsEditor(self, self.game.resource_database,
                                             self._unlocked_by_requirement)
        self._edit_popup.setModal(True)
        try:
            result = await async_dialog.execute_dialog(self._edit_popup)
            if result == QtWidgets.QDialog.Accepted:
                self.set_unlocked_by(self._edit_popup.final_requirement)
        finally:
            self._edit_popup = None

    # Final
    def create_new_node(self) -> Node:
        node_type = self.node_type_combo.currentData()
        name = self.name_edit.text()
        heal = self.heals_check.isChecked()
        location = None
        if self.location_group.isChecked():
            location = NodeLocation(self.location_x_spin.value(),
                                    self.location_y_spin.value(),
                                    self.location_z_spin.value())
        index = self.node.index

        if node_type == GenericNode:
            return GenericNode(name, heal, location, index)

        elif node_type == DockNode:
            return DockNode(
                name,
                heal,
                location,
                index,
                self.dock_index_spin.value(),
                DockConnection(
                    self.dock_connection_area_combo.currentData(
                    ).area_asset_id,
                    self.dock_connection_node_combo.currentData()),
                self.dock_weakness_combo.currentData(),
            )

        elif node_type == PickupNode:
            return PickupNode(
                name,
                heal,
                location,
                index,
                PickupIndex(self.pickup_index_spin.value()),
                self.major_location_check.isChecked(),
            )

        elif node_type == TeleporterNode:
            instance_id = self.teleporter_instance_id_edit.text()
            scan_asset_id = self.teleporter_scan_asset_id_edit.text()

            return TeleporterNode(
                name,
                heal,
                location,
                index,
                int(instance_id, 0) if instance_id != "" else None,
                AreaLocation(
                    self.teleporter_destination_world_combo.currentData(
                    ).world_asset_id,
                    self.teleporter_destination_area_combo.currentData().
                    area_asset_id),
                int(scan_asset_id, 0) if scan_asset_id != "" else None,
                self.teleporter_vanilla_name_edit.isChecked(),
                self.teleporter_editable_check.isChecked(),
            )

        elif node_type == EventNode:
            event = self.event_resource_combo.currentData()
            if event is None:
                raise ValueError(
                    "There are no events in the database, unable to create EventNode."
                )
            return EventNode(
                name,
                heal,
                location,
                index,
                event,
            )

        elif node_type == TranslatorGateNode:
            return TranslatorGateNode(
                name, heal, location, index,
                TranslatorGate(self.translator_gate_spin.value()),
                self._get_scan_visor())

        elif node_type == LogbookNode:
            lore_type: LoreType = self.lore_type_combo.currentData()
            if lore_type == LoreType.LUMINOTH_LORE:
                required_translator = self.logbook_extra_combo.currentData()
                if required_translator is None:
                    raise ValueError("Missing required translator.")
            else:
                required_translator = None

            if lore_type == LoreType.LUMINOTH_WARRIOR:
                hint_index = self.logbook_extra_combo.currentData()
            else:
                hint_index = None

            return LogbookNode(
                name, heal, location, index,
                int(self.logbook_string_asset_id_edit.text(), 0),
                self._get_scan_visor(), lore_type, required_translator,
                hint_index)

        elif node_type == PlayerShipNode:
            return PlayerShipNode(name, heal, location, index,
                                  self._unlocked_by_requirement,
                                  self._get_command_visor())

        else:
            raise RuntimeError(f"Unknown node type: {node_type}")

    def _get_scan_visor(self):
        return find_resource_info_with_long_name(
            self.game.resource_database.item, "Scan Visor")

    def _get_command_visor(self):
        return find_resource_info_with_long_name(
            self.game.resource_database.item, "Command Visor")

    def try_accept(self):
        try:
            self.create_new_node()
            self.accept()
        except Exception as e:
            QtWidgets.QMessageBox.warning(self, "Invalid configuration",
                                          f"Unable to save node: {e}")
Exemplo n.º 26
0
def test_trivial_requirement_str():
    assert str(Requirement.trivial()) == "Trivial"
Exemplo n.º 27
0
def test_trivial_requirement_damage():
    assert Requirement.trivial().damage({}, None) == 0
Exemplo n.º 28
0
def test_trivial_requirement_satisfied():
    assert Requirement.trivial().satisfied({}, 99, None)
Exemplo n.º 29
0
def test_trivial_requirement_as_set():
    assert Requirement.trivial().as_set(None) == RequirementSet.trivial()
Exemplo n.º 30
0
         RequirementAnd([
             _req("A"),
         ]),
     ]),
     _req("A"),
 ), (
     RequirementAnd([
         RequirementOr([
             _req("A"),
         ]),
     ]),
     _req("A"),
 ),
  (
      RequirementAnd([
          RequirementOr([_req("B"), Requirement.trivial()]),
          RequirementAnd([
              Requirement.trivial(),
              _req("A"),
          ]),
      ]),
      _req("A"),
  ),
  (
      RequirementOr([
          _req("A"),
          RequirementAnd([_req("B"), _req("C")]),
          RequirementAnd([_req("B"), _req("D")]),
      ]),
      RequirementOr([
          _req("A"),