Files
settled-reach/client/ui/interaction_prompt.gd
T
jpmschweitzerandClaude Opus 4.6 283518d332 refactor(ui): convert HUD status + interaction prompt to implant (#786, #788)
HUD: merge TimeDisplay into ImplantPanel (time/health/perception in one
panel), remove dead tscn nodes, move HUD to InsertOverlay for D-051 bloom.
Interaction prompt: convert from PanelContainer+StyleBoxFlat to Control
with ImplantPanel+ImplantDataRow, fix panel height for 42px minimum.
Tests updated for new scene structure and public API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 12:51:58 +02:00

117 lines
3.4 KiB
GDScript

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", "")