extends Control # Interaction prompt — displays available interaction verb for nearby entity. # Server-driven: shows when GameState.nearby_interactions is non-empty, # hides when empty. No game logic — pure display layer. # # v0.1: Single-line "E - Talk" (first verb on nearest entity) # v0.2: Will be replaced/extended with radial verb menu. # Public interface: get_interaction_target(), get_selected_verb() const FADE_IN: float = 0.15 const FADE_OUT: float = 0.15 var _is_showing: bool = false var _active_tween: Tween = null var _current_target_id: int = -1 # OQ-07 (#522): when false, prompt is suppressed (z-layer 6 insert overlay only) var _insert_active: bool = true var _panel: ImplantPanel var _row: ImplantDataRow func _ready() -> void: modulate.a = 0.0 visible = false _is_showing = false mouse_filter = Control.MOUSE_FILTER_IGNORE var theme_res := load("res://ui/implant/default_implant.tres") as ImplantTheme _panel = ImplantPanel.new() _panel.name = "PromptPanel" _panel.theme_resource = theme_res _panel.mouse_filter = Control.MOUSE_FILTER_IGNORE _panel.set_anchors_preset(Control.PRESET_FULL_RECT) add_child(_panel) _row = ImplantDataRow.new("", theme_res.text_primary if theme_res else Color.WHITE) _row.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _panel.add_component(_row) func _process(_delta: float) -> void: # OQ-07: insert off means no verb labels (z-layer 6 insert overlay suppressed) if not _insert_active: if _is_showing: _hide_prompt() return var interactions: Array = GameState.nearby_interactions if interactions.size() > 0: _show_prompt(interactions[0]) elif _is_showing: _hide_prompt() func _show_prompt(interaction: Dictionary) -> void: var target_id: int = interaction.get("entity_id", -1) var verbs: Array = interaction.get("verbs", []) if verbs.is_empty(): if _is_showing: _hide_prompt() return # v0.1: pick first verb (sorted by priority from server) var verb_label: String = verbs[0].get("label", "") var display_text: String = "E - %s" % verb_label if _current_target_id != target_id or _row.text != display_text: _row.set_content(display_text) _current_target_id = target_id if not _is_showing: visible = true _is_showing = true if _active_tween and _active_tween.is_valid(): _active_tween.kill() _active_tween = create_tween() _active_tween.tween_property(self, "modulate:a", 1.0, FADE_IN) func _hide_prompt() -> void: if not _is_showing: return _is_showing = false _current_target_id = -1 if _active_tween and _active_tween.is_valid(): _active_tween.kill() _active_tween = create_tween() _active_tween.tween_property(self, "modulate:a", 0.0, FADE_OUT) _active_tween.tween_callback(func(): visible = false) ## Returns the current interaction target entity ID, or -1 if no interaction. func get_interaction_target() -> int: return _current_target_id ## OQ-07 (#522): insert off hides prompt (diegetic: no insert data on z-layer 6). ## Cursor shape changes still fire on cursor_renderer.gd. func set_insert_active(active: bool) -> void: _insert_active = active if not active and _is_showing: _hide_prompt() ## Returns the selected verb kind (v0.1: first verb on nearest, v0.2: radial selection). func get_selected_verb() -> String: var interactions: Array = GameState.nearby_interactions if interactions.is_empty(): return "" var verbs: Array = interactions[0].get("verbs", []) if verbs.is_empty(): return "" return verbs[0].get("kind", "")