feat: add exact player context actions
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user