class_name VillagerFieldNoteHud extends Control const ENTER_OFFSET := 9.0 const EXIT_OFFSET := 4.0 @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 reason_label: Label = $Copy/Reason @onready var need_label: Label = $Copy/Need var copy_resting_position: Vector2 var current_context_key := "" var motion_tween: Tween var motion_version := 0 var is_hiding := false 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: refresh_note() func refresh_note(force: bool = false) -> void: 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() reason_label.text = "Why · %s" % context.decision_reason _present_opportunity_note(context.opportunity_note) 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 _present_opportunity_note(note: VillagerOpportunityNote) -> void: if note == null or not note.has_need: need_label.visible = false need_label.text = "Need · " return need_label.visible = true need_label.text = "Need · %s\n%s" % [note.need_text, note.help_text] 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)