extends CanvasLayer var _interactables: Array[Node] = [] var _active_interactable: Node = null var _dialog_visible := false var _dialog_source: Node = null var _prompt_label: Label var _dialog_panel: PanelContainer var _dialog_label: RichTextLabel func _ready() -> void: layer = 50 process_mode = Node.PROCESS_MODE_ALWAYS _setup_ui() _set_dialog_visible(false) _update_prompt_visibility() func _process(_delta: float) -> void: if Input.is_action_just_pressed("interact"): if _dialog_visible: _set_dialog_visible(false) return if _active_interactable != null: _open_dialog_for(_active_interactable) func register_interactable(interactable: Node) -> void: if interactable == null: return if _interactables.has(interactable): return _interactables.append(interactable) _refresh_active_interactable() func unregister_interactable(interactable: Node) -> void: if interactable == null: return _interactables.erase(interactable) _refresh_active_interactable() func _refresh_active_interactable() -> void: while _interactables.size() > 0 and not is_instance_valid(_interactables[_interactables.size() - 1]): _interactables.pop_back() _active_interactable = _interactables[_interactables.size() - 1] if _interactables.size() > 0 else null _update_prompt_visibility() func _open_dialog_for(interactable: Node) -> void: if _dialog_label: _dialog_label.text = _get_dialog_text(interactable) _dialog_source = interactable _set_dialog_visible(true) func show_text(text: String) -> void: if _dialog_label: _dialog_label.text = text _dialog_source = null _set_dialog_visible(true) func show_text_from(source: Node, text: String) -> void: if _dialog_label: _dialog_label.text = text _dialog_source = source _set_dialog_visible(true) func close_if_text(expected_text: String) -> void: if not _dialog_visible: return if _dialog_label == null: return if _dialog_label.text != expected_text: return _set_dialog_visible(false) func is_dialog_open() -> bool: return _dialog_visible func is_dialog_open_from(source: Node) -> bool: return _dialog_visible and _dialog_source == source func close_if_source(source: Node) -> void: if not _dialog_visible: return if _dialog_source != source: return _set_dialog_visible(false) func _set_dialog_visible(dialog_open: bool) -> void: _dialog_visible = dialog_open if not _dialog_visible: _dialog_source = null if _dialog_panel: _dialog_panel.visible = _dialog_visible _update_prompt_visibility() func _update_prompt_visibility() -> void: if _prompt_label == null: return var has_active := _active_interactable != null _prompt_label.visible = has_active and not _dialog_visible if _prompt_label.visible: _prompt_label.text = _get_prompt_text(_active_interactable) func _get_prompt_text(interactable: Node) -> String: if interactable == null: return "" if interactable.has_method("get_dialog_prompt"): return String(interactable.call("get_dialog_prompt")) return "Press E to interact" func _get_dialog_text(interactable: Node) -> String: if interactable == null: return "" if interactable.has_method("get_dialog_text"): return String(interactable.call("get_dialog_text")) return "..." func _setup_ui() -> void: var root := Control.new() root.name = "DialogRoot" root.set_anchors_preset(Control.PRESET_FULL_RECT) root.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(root) _prompt_label = Label.new() _prompt_label.name = "PromptLabel" _prompt_label.set_anchors_preset(Control.PRESET_CENTER_BOTTOM) _prompt_label.offset_left = -140.0 _prompt_label.offset_top = -64.0 _prompt_label.offset_right = 140.0 _prompt_label.offset_bottom = -36.0 _prompt_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _prompt_label.mouse_filter = Control.MOUSE_FILTER_IGNORE _prompt_label.text = "Press E to interact" root.add_child(_prompt_label) _dialog_panel = PanelContainer.new() _dialog_panel.name = "DialogPanel" _dialog_panel.visible = false _dialog_panel.set_anchors_preset(Control.PRESET_CENTER_BOTTOM) _dialog_panel.offset_left = -300.0 _dialog_panel.offset_top = -220.0 _dialog_panel.offset_right = 300.0 _dialog_panel.offset_bottom = -80.0 _dialog_panel.mouse_filter = Control.MOUSE_FILTER_IGNORE root.add_child(_dialog_panel) _dialog_label = RichTextLabel.new() _dialog_label.name = "DialogText" _dialog_label.custom_minimum_size = Vector2(560.0, 120.0) _dialog_label.fit_content = true _dialog_label.scroll_active = false _dialog_label.bbcode_enabled = true _dialog_label.text = "" _dialog_panel.add_child(_dialog_label)