예제 #1
0
 def edit_template(self, name: str):
     requirement = self.db.requirement_template[name]
     editor = ConnectionsEditor(self, self.db, requirement)
     result = editor.exec_()
     if result == QtWidgets.QDialog.Accepted:
         self.db.requirement_template[name] = editor.final_requirement
         self.editor_for_template[name].create_visualizer(self.db)
예제 #2
0
 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
예제 #3
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_set = self.current_area.connections[from_node].get(target_node)
        editor = ConnectionsEditor(self, self.resource_database, requirement_set)
        result = editor.exec_()

        if result == QDialog.Accepted:
            self._apply_edit_connections(from_node, target_node, editor.final_requirement_set)
예제 #4
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)
예제 #5
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}")
예제 #6
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._type_to_tab = {
            GenericNode: self.tab_generic,
            DockNode: self.tab_dock,
            PickupNode: self.tab_pickup,
            TeleporterNode: self.tab_teleporter,
            EventNode: self.tab_event,
            ConfigurableNode: 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)

        self.layers_combo.clear()
        for layer in game.layers:
            self.layers_combo.addItem(layer)

        self.dock_type_combo.clear()
        for i, dock_type in enumerate(game.dock_weakness_database.dock_types):
            self.dock_type_combo.addItem(dock_type.long_name, userData=dock_type)
        refresh_if_needed(self.dock_type_combo, self.on_dock_type_combo)

        for world in sorted(game.world_list.worlds, key=lambda x: x.name):
            self.dock_connection_world_combo.addItem(world.name, userData=world)
            self.teleporter_destination_world_combo.addItem(world.name, userData=world)
        refresh_if_needed(self.teleporter_destination_world_combo, self.on_dock_connection_world_combo)
        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, dock_type in enumerate(enum_lib.iterate_enum(LoreType)):
            self.lore_type_combo.setItemData(i, dock_type)
        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.name_edit.textEdited.connect(self.on_name_edit)
        self.node_type_combo.currentIndexChanged.connect(self.on_node_type_combo)
        self.dock_connection_world_combo.currentIndexChanged.connect(self.on_dock_connection_world_combo)
        self.dock_connection_area_combo.currentIndexChanged.connect(self.on_dock_connection_area_combo)
        self.dock_type_combo.currentIndexChanged.connect(self.on_dock_type_combo)
        self.dock_update_name_button.clicked.connect(self.on_dock_update_name_button)
        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)
        self.description_edit.setMarkdown(node.description)
        self.extra_edit.setPlainText(json.dumps(frozen_lib.unwrap(node.extra), indent=4))

        try:
            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)
        except Exception:
            pass

        self.on_name_edit(self.name_edit.text())

    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, ConfigurableNode):
            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):
        # Connection
        other_node = self.game.world_list.node_by_identifier(node.default_connection)
        area = self.game.world_list.nodes_to_area(other_node)
        world = self.game.world_list.nodes_to_world(other_node)

        self.dock_connection_world_combo.setCurrentIndex(self.dock_connection_world_combo.findData(world))
        refresh_if_needed(self.dock_connection_world_combo, self.on_dock_connection_world_combo)
        self.dock_connection_area_combo.setCurrentIndex(self.dock_connection_area_combo.findData(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(other_node))

        # Dock Weakness
        self.dock_type_combo.setCurrentIndex(self.dock_type_combo.findData(node.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_area_location(node.default_connection)
        try:
            area = self.game.world_list.area_by_area_location(node.default_connection)
        except KeyError:
            area = None

        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_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: ConfigurableNode):
        pass

    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.REQUIRES_ITEM:
            self.logbook_extra_combo.setCurrentIndex(self.logbook_extra_combo.findData(node.required_translator))

        elif node.lore_type == LoreType.SPECIFIC_PICKUP:
            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_name_edit(self, value: str):
        has_error = False

        try:
            new_node = self.create_new_node()
        except ValueError:
            new_node = None

        if isinstance(new_node, DockNode):
            area = self.game.world_list.nodes_to_area(self.node)
            has_error = not integrity_check.dock_has_correct_name(area, new_node)[0]

        common_qt_lib.set_error_border_stylesheet(self.name_edit, has_error)

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

    def on_dock_connection_world_combo(self, _):
        world: World = self.dock_connection_world_combo.currentData()

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

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

        self.dock_connection_node_combo.clear()
        empty = True
        if area is not None:
            for node in area.nodes:
                if isinstance(node, DockNode):
                    self.dock_connection_node_combo.addItem(node.name, userData=node)
                    empty = False
        if empty:
            self.dock_connection_node_combo.addItem("Other", 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_dock_update_name_button(self):
        new_node = self.create_new_node()
        assert isinstance(new_node, DockNode)
        expected_name = integrity_check.base_dock_name(new_node)
        self.name_edit.setText(expected_name)
        self.on_name_edit(self.name_edit.text())

    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.REQUIRES_ITEM:
            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.SPECIFIC_PICKUP:
            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()
        identifier = dataclasses.replace(self.node.identifier, node_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())
        description = self.description_edit.toMarkdown()
        extra = json.loads(self.extra_edit.toPlainText())
        layers = (self.layers_combo.currentText(),)

        if node_type == GenericNode:
            return GenericNode(identifier, heal, location, description, layers, extra)

        elif node_type == DockNode:
            connection_node: Node = self.dock_connection_node_combo.currentData()

            return DockNode(
                identifier, heal, location, description, layers, extra,
                self.dock_type_combo.currentData(),
                self.game.world_list.identifier_for_node(connection_node),
                self.dock_weakness_combo.currentData(),
                None, None,
            )

        elif node_type == PickupNode:
            return PickupNode(
                identifier, heal, location, description, layers, extra,
                PickupIndex(self.pickup_index_spin.value()),
                self.major_location_check.isChecked(),
            )

        elif node_type == TeleporterNode:
            dest_world: World = self.teleporter_destination_world_combo.currentData()
            dest_area: Area = self.teleporter_destination_area_combo.currentData()

            return TeleporterNode(
                identifier, heal, location, description, layers, extra,
                AreaIdentifier(
                    world_name=dest_world.name,
                    area_name=dest_area.name,
                ),
                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(
                identifier, heal, location, description, layers, extra,
                event,
            )

        elif node_type == ConfigurableNode:
            return ConfigurableNode(
                identifier, heal, location, description, layers, extra,
            )

        elif node_type == LogbookNode:
            lore_type: LoreType = self.lore_type_combo.currentData()
            if lore_type == LoreType.REQUIRES_ITEM:
                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.SPECIFIC_PICKUP:
                hint_index = self.logbook_extra_combo.currentData()
            else:
                hint_index = None

            return LogbookNode(
                identifier, heal, location, description, layers, extra,
                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(
                identifier, heal, location, description, layers, extra,
                self._unlocked_by_requirement,
                self._get_command_visor()
            )

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

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

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

    def try_accept(self):
        try:
            self.create_new_node()
            self.accept()
        except Exception as e:
            logging.exception(f"Unable to save node: {e}")

            box = QtWidgets.QMessageBox(
                QtWidgets.QMessageBox.Warning,
                "Invalid configuration",
                f"Unable to save node: {e}",
                QtWidgets.QMessageBox.Ok,
                None,
            )
            box.setDefaultButton(QtWidgets.QMessageBox.Ok)
            box.setDetailedText("".join(traceback.format_tb(e.__traceback__)))
            common_qt_lib.set_default_window_icon(box)
            box.exec_()