extends CanvasLayer const DEBUG_LOGS := false @export var simulation_manager: Node @onready var village_stats_label: Label = $VillagePanel/MarginContainer/VillageStatsLabel @onready var npc_inspector_label: Label = $NpcInspectorPanel/MarginContainer/NpcInspectorLabel var selected_npc_index := 0 var inspector_refresh_accumulator := 0.0 var debug_overlay_visible := true func _ready() -> void: if simulation_manager == null: push_error("VillageUI: simulation_manager missing") return if simulation_manager.has_signal("village_changed"): simulation_manager.village_changed.connect(_on_village_changed) else: push_error("VillageUI: SimulationManager has no village_changed signal") if simulation_manager.has_signal("npc_decision_recorded"): simulation_manager.npc_decision_recorded.connect(_on_npc_decision_recorded) else: push_error("VillageUI: SimulationManager has no npc_decision_recorded signal") if simulation_manager.has_signal("state_restored"): simulation_manager.state_restored.connect(_on_state_restored) if "village" in simulation_manager: _on_village_changed(simulation_manager.village) _refresh_npc_inspector() func _process(delta: float) -> void: inspector_refresh_accumulator += delta if inspector_refresh_accumulator < 0.25: return inspector_refresh_accumulator = 0.0 _refresh_npc_inspector() func _unhandled_key_input(event: InputEvent) -> void: if not event is InputEventKey or not event.pressed or event.echo: return if event.keycode != KEY_TAB or simulation_manager.npcs.is_empty(): return selected_npc_index = (selected_npc_index + 1) % simulation_manager.npcs.size() _refresh_npc_inspector() get_viewport().set_input_as_handled() func _on_village_changed(village: SimVillage) -> void: village_stats_label.text = ( """ Village Food: %s Wood: %s Safety: %s Knowledge: %s Starving: %s Food Mod: %.2f Safety Mod: %.2f Knowledge Mod: %.2f """ % [ round(village.food), round(village.wood), round(village.safety), round(village.knowledge), simulation_manager.get_starving_count(), village.food_modifier, village.safety_modifier, village.knowledge_modifier ] ) func _on_npc_decision_recorded(_npc: SimNPC, _decision: ActionSelectionResult) -> void: _refresh_npc_inspector() func _on_state_restored() -> void: selected_npc_index = 0 _refresh_npc_inspector() func _refresh_npc_inspector() -> void: if not debug_overlay_visible: return if simulation_manager == null or simulation_manager.npcs.is_empty(): npc_inspector_label.text = "No villagers available" return selected_npc_index = clampi(selected_npc_index, 0, simulation_manager.npcs.size() - 1) var npc: SimNPC = simulation_manager.npcs[selected_npc_index] var profession_definition := SimulationDefinitions.get_profession(npc.profession) var profession_name := String(npc.profession) if profession_definition != null: profession_name = profession_definition.display_name var action_name := _get_action_name(npc.current_task) var destination_name := "Resolving" if not npc.target_id.is_empty(): destination_name = String(npc.target_id) var decision: ActionSelectionResult = simulation_manager.get_latest_decision(npc.id) var reason_text := "Awaiting the next decision" var score_text := "" if decision != null: reason_text = decision.reason var score_keys: Array = decision.scores.keys() score_keys.sort() var score_rows: Array[String] = [] for action_id in score_keys: score_rows.append( "%s %.2f" % [_get_action_name(StringName(action_id)), float(decision.scores[action_id])] ) if not score_rows.is_empty(): score_text = "\n\nUtility\n" + "\n".join(score_rows) npc_inspector_label.text = ( "%s ยท %s\n" + "Task: %s (%s)\n" + "Destination: %s\n" + "Hunger: %.0f Energy: %.0f\n" + "Carrying food: %.0f\n\n" + "Why\n%s%s\n\n" + "[Tab] Next villager [F10] Cinematic/debug [F12] Demo reset" ) % [ npc.npc_name, profession_name, action_name, npc.task_state, destination_name, npc.hunger, npc.energy, npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD), reason_text, score_text ] func _get_action_name(action_id: StringName) -> String: if action_id == SimulationIds.ACTION_IDLE: return "Idle" if action_id == SimulationIds.ACTION_DEAD: return "Dead" var definition := SimulationDefinitions.get_action(action_id) if definition != null: return definition.display_name return String(action_id).capitalize() func set_debug_overlay_visible(is_visible: bool) -> void: debug_overlay_visible = is_visible visible = is_visible if is_visible: _refresh_npc_inspector()