feat: add nearby villager field notes

This commit is contained in:
Rijad Zuzo
2026-08-03 12:47:54 +02:00
parent ead75fca68
commit 2082773d31
14 changed files with 978 additions and 23 deletions
+1
View File
@@ -638,6 +638,7 @@ visible = false
[node name="VillageWoodpile" parent="WorldObjects/StorageSites" instance=ExtResource("14_storage")]
position = Vector3(8, 0, -12)
storage_id = &"village_woodpile"
display_name = "Village Woodpile"
[node name="PantrySignPole" type="MeshInstance3D" parent="WorldObjects/StorageSites/VillagePantry"]
position = Vector3(-1.05, 1.1, 1.25)
+111
View File
@@ -0,0 +1,111 @@
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
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
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)
+1
View File
@@ -0,0 +1 @@
uid://c4hhfxayuo63l
+20
View File
@@ -130,6 +130,26 @@ func reload_npc_visual(npc_id: int) -> bool:
return false
func find_nearest_active_npc_id(origin: Vector3, max_distance: float) -> int:
if not origin.is_finite() or not is_finite(max_distance) or max_distance < 0.0:
return -1
var nearest_id := -1
var nearest_distance_squared := max_distance * max_distance
var sorted_ids: Array = active_npc_visuals.keys()
sorted_ids.sort()
for npc_id in sorted_ids:
var visual := active_npc_visuals[npc_id] as Node3D
if visual == null or not is_instance_valid(visual) or bool(visual.get("is_dead_visual")):
continue
var distance_squared := origin.distance_squared_to(visual.global_position)
if distance_squared > nearest_distance_squared:
continue
if nearest_id < 0 or distance_squared < nearest_distance_squared:
nearest_id = int(npc_id)
nearest_distance_squared = distance_squared
return nearest_id
func _on_npc_target_requested(npc: SimNPC) -> void:
var visual = active_npc_visuals.get(npc.id) as Node3D
if visual == null: