Files
gamedev-the-steward/world/ui/villager_field_note_hud.gd
T
2026-08-12 10:02:45 +02:00

126 lines
3.8 KiB
GDScript

class_name VillagerFieldNoteHud
extends Control
const ENTER_OFFSET := 9.0
const EXIT_OFFSET := 4.0
const PROBE_INTERVAL_SECONDS := 0.1
@export var player: Node
@export var simulation_manager: Node
@onready var accent: ColorRect = $Accent
@onready var copy: VBoxContainer = $Copy
@onready var name_label: Label = $Copy/Name
@onready var activity_label: Label = $Copy/Activity
@onready var target_label: Label = $Copy/Target
@onready var carrying_label: Label = $Copy/Carrying
@onready var need_label: Label = $Copy/Need
@onready var reason_label: Label = $Copy/Reason
var copy_resting_position: Vector2
var current_context_key := ""
var motion_tween: Tween
var motion_version := 0
var is_hiding := false
var _probe_elapsed := 0.0
func _ready() -> void:
copy_resting_position = copy.position
visible = false
modulate.a = 0.0
if simulation_manager != null and simulation_manager.has_signal("state_restored"):
simulation_manager.state_restored.connect(_on_state_restored)
call_deferred("refresh_note", true)
func _process(delta: float) -> void:
_probe_elapsed += delta
if _probe_elapsed < PROBE_INTERVAL_SECONDS:
return
_probe_elapsed = fmod(_probe_elapsed, PROBE_INTERVAL_SECONDS)
refresh_note()
func refresh_note(force: bool = false) -> void:
if force:
_probe_elapsed = 0.0
if player == null or not player.has_method("get_nearby_villager_inspection"):
current_context_key = ""
_hide_note()
return
var context: VillagerInspectionResult = player.get_nearby_villager_inspection()
if context == null:
if force or not current_context_key.is_empty():
current_context_key = ""
_hide_note()
return
var next_key := context.cache_key()
if not force and next_key == current_context_key:
return
current_context_key = next_key
_present(context, true)
func _present(context: VillagerInspectionResult, animate: bool) -> void:
motion_version += 1
if motion_tween != null and motion_tween.is_valid():
motion_tween.kill()
motion_tween = null
is_hiding = false
name_label.text = context.npc_name
activity_label.text = context.activity_text()
target_label.text = "Target · %s" % context.target_name
carrying_label.text = "Carrying · %s" % context.carrying_text()
if context.has_open_need:
need_label.text = "Need · %s\n%s" % [context.need_progress, context.need_response]
need_label.visible = true
else:
need_label.visible = false
reason_label.text = "Why · %s" % context.decision_reason
visible = true
accent.pivot_offset = accent.size * 0.5
if not animate:
copy.position = copy_resting_position
modulate.a = 1.0
accent.scale.y = 1.0
return
copy.position = copy_resting_position + Vector2(-ENTER_OFFSET, 0.0)
modulate.a = 0.0
accent.scale.y = 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(copy, "position", copy_resting_position, 0.2)
motion_tween.tween_property(self, "modulate:a", 1.0, 0.16)
motion_tween.tween_property(accent, "scale:y", 1.0, 0.26)
func _hide_note() -> void:
if not visible or is_hiding:
return
is_hiding = true
motion_version += 1
var hide_version := motion_version
if motion_tween != null and motion_tween.is_valid():
motion_tween.kill()
motion_tween = null
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.14)
motion_tween.tween_property(copy, "position:x", copy_resting_position.x - EXIT_OFFSET, 0.14)
motion_tween.finished.connect(_on_hide_finished.bind(hide_version), CONNECT_ONE_SHOT)
func _on_hide_finished(hide_version: int) -> void:
if motion_version != hide_version:
return
motion_tween = null
is_hiding = false
if current_context_key.is_empty():
visible = false
func _on_state_restored() -> void:
current_context_key = ""
call_deferred("refresh_note", true)