feat: add exact player context actions

This commit is contained in:
Rijad Zuzo
2026-07-30 23:27:46 +02:00
parent d950c3da68
commit 7d410d8193
12 changed files with 762 additions and 67 deletions
+137
View File
@@ -0,0 +1,137 @@
class_name PlayerInteractionHud
extends Control
const FEEDBACK_SECONDS := 2.0
const ENTER_OFFSET := 7.0
const ACCENT_READY := Color(0.9, 0.63, 0.28, 0.95)
const ACCENT_SUCCESS := Color(0.55, 0.78, 0.38, 0.95)
const ACCENT_BLOCKED := Color(0.82, 0.4, 0.24, 0.95)
@export var player: Node
@export var simulation_manager: Node
@onready var accent: ColorRect = $Accent
@onready var action_label: Label = $Copy/Action
@onready var detail_label: Label = $Copy/Detail
var resting_position: Vector2
var motion_tween: Tween
var current_context_key := ""
var feedback_version := 0
var feedback_active := false
var feedback_succeeded := false
func _ready() -> void:
resting_position = position
_reset_transient_state()
if player == null or not player.has_signal("interaction_feedback"):
push_error("PlayerInteractionHud: player interaction feedback is unavailable")
return
player.interaction_feedback.connect(_on_interaction_feedback)
if simulation_manager != null and simulation_manager.has_signal("state_restored"):
simulation_manager.state_restored.connect(_on_state_restored)
call_deferred("refresh_prompt", true)
func _process(_delta: float) -> void:
if not feedback_active:
refresh_prompt()
func refresh_prompt(force: bool = false) -> void:
if player == null or not player.has_method("get_interaction_context"):
_hide_prompt()
return
var context: PlayerInteractionResult = player.get_interaction_context()
if context == null:
if force or not current_context_key.is_empty():
current_context_key = ""
_hide_prompt()
return
var next_key := context.cache_key()
if not force and next_key == current_context_key:
return
current_context_key = next_key
feedback_succeeded = false
_present(
"E · %s" % context.prompt_text,
context.detail_text,
ACCENT_READY if context.is_available() else ACCENT_BLOCKED,
true
)
func _on_interaction_feedback(heading: String, message: String, succeeded: bool) -> void:
feedback_version += 1
var version := feedback_version
feedback_active = true
feedback_succeeded = succeeded
current_context_key = ""
_present(heading, message, ACCENT_SUCCESS if succeeded else ACCENT_BLOCKED, true)
_resume_prompt_after_feedback(version)
func _resume_prompt_after_feedback(version: int) -> void:
await get_tree().create_timer(FEEDBACK_SECONDS).timeout
if version != feedback_version or not is_inside_tree():
return
feedback_active = false
refresh_prompt(true)
func _on_state_restored() -> void:
_reset_transient_state()
refresh_prompt(true)
func _present(heading: String, detail: String, accent_color: Color, animate: bool) -> void:
if motion_tween != null:
motion_tween.kill()
action_label.text = heading
detail_label.text = detail
accent.color = accent_color
visible = true
accent.pivot_offset = accent.size * 0.5
if not animate:
position = resting_position
modulate.a = 1.0
accent.scale.x = 1.0
return
position = resting_position + Vector2(0.0, ENTER_OFFSET)
modulate.a = 0.0
accent.scale.x = 0.18
motion_tween = create_tween().set_parallel(true)
motion_tween.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
motion_tween.tween_property(self, "position", resting_position, 0.22)
motion_tween.tween_property(self, "modulate:a", 1.0, 0.18)
motion_tween.tween_property(accent, "scale:x", 1.0, 0.28)
func _hide_prompt() -> void:
if not visible:
return
if motion_tween != null:
motion_tween.kill()
motion_tween = create_tween().set_parallel(true)
motion_tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN)
motion_tween.tween_property(self, "modulate:a", 0.0, 0.16)
motion_tween.tween_property(self, "position:y", resting_position.y - 3.0, 0.16)
var hiding_tween := motion_tween
await hiding_tween.finished
if motion_tween == hiding_tween and current_context_key.is_empty() and not feedback_active:
visible = false
func _reset_transient_state() -> void:
feedback_version += 1
feedback_active = false
feedback_succeeded = false
current_context_key = ""
if motion_tween != null:
motion_tween.kill()
visible = false
position = resting_position
modulate.a = 0.0
if accent != null:
accent.scale.x = 1.0