Files
gamedev-the-steward/world/world_view_manager.gd
T
admin c61d286005 feat: wood inventory, carrying, and woodpile storage
Wood now follows the same gather-carry-deposit pattern as food. NPCs put harvested wood into inventory, walk to the VillageWoodpile StorageNode, and deposit it. Village wood syncs from storage. Player harvesting also routes wood to the woodpile. ActiveWorldAdapter exposes woodpile routing for DEPOSIT_WOOD action targeting. WorldViewManager recognizes wood inventory changes. Tests verify woodpile node existence, storage ID, navigation reachability, and adapter routing.
2026-07-08 18:55:51 +02:00

190 lines
6.4 KiB
GDScript

extends Node
const DEBUG_LOGS := true
@export var npc_visual_scene: PackedScene
@export var simulation_manager: Node
@export var active_npcs_parent: Node3D
var active_npc_visuals := {}
func debug_log(message: String) -> void:
if DEBUG_LOGS:
print("[WorldViewManager] ", message)
func _ready() -> void:
call_deferred("initialize_world_view")
func initialize_world_view() -> void:
await get_tree().physics_frame
if simulation_manager.has_signal("npc_target_requested"):
simulation_manager.npc_target_requested.connect(_on_npc_target_requested)
else:
push_error("WorldViewManager: missing npc_target_requested signal")
if simulation_manager.has_signal("npc_travel_requested"):
simulation_manager.npc_travel_requested.connect(_on_npc_travel_requested)
else:
push_error("WorldViewManager: missing npc_travel_requested signal")
if simulation_manager.has_signal("npc_died"):
simulation_manager.npc_died.connect(_on_npc_died)
else:
push_error("WorldViewManager: SimulationManager has no npc_died signal")
if simulation_manager.has_signal("npc_inventory_changed"):
simulation_manager.npc_inventory_changed.connect(_on_npc_inventory_changed)
else:
push_error("WorldViewManager: SimulationManager has no npc_inventory_changed signal")
if simulation_manager.has_signal("npc_task_changed"):
simulation_manager.npc_task_changed.connect(_on_npc_task_changed)
else:
push_error("WorldViewManager: SimulationManager has no npc_task_changed signal")
if simulation_manager.has_signal("state_restored"):
simulation_manager.state_restored.connect(_on_simulation_state_restored)
else:
push_error("WorldViewManager: SimulationManager has no state_restored signal")
spawn_initial_npcs()
func spawn_initial_npcs() -> void:
if simulation_manager == null:
push_error("WorldViewManager: simulation_manager is missing")
return
if npc_visual_scene == null:
push_error("WorldViewManager: npc_visual_scene is missing")
return
if active_npcs_parent == null:
push_error("WorldViewManager: active_npcs_parent is missing")
return
debug_log("Spawning NPC visuals. Count: %s" % simulation_manager.npcs.size())
for npc in simulation_manager.npcs:
spawn_npc_visual(npc)
func spawn_npc_visual(npc: SimNPC) -> void:
if active_npc_visuals.has(npc.id):
return
var visual = npc_visual_scene.instantiate()
active_npcs_parent.add_child(visual)
visual.global_position = npc.position
visual.setup_from_sim(npc)
if visual.has_signal("arrived_at_target"):
visual.arrived_at_target.connect(_on_npc_visual_arrived)
else:
push_error("WorldViewManager: NPCVisual has no arrived_at_target signal")
if visual.has_signal("navigation_failed"):
visual.navigation_failed.connect(_on_npc_visual_navigation_failed)
else:
push_error("WorldViewManager: NPCVisual has no navigation_failed signal")
if visual.has_signal("position_changed"):
visual.position_changed.connect(_on_npc_visual_position_changed)
else:
push_error("WorldViewManager: NPCVisual has no position_changed signal")
active_npc_visuals[npc.id] = visual
debug_log("Spawned visual for %s at %s" % [npc.npc_name, npc.position])
if npc.is_dead:
visual.apply_dead_visual_state()
elif npc.task_state == SimNPC.TASK_STATE_TRAVELING:
simulation_manager.request_current_travel(npc.id)
func despawn_npc_visual(npc_id: int) -> bool:
var visual = active_npc_visuals.get(npc_id) as Node3D
if visual == null:
return false
simulation_manager.synchronize_npc_position(npc_id, visual.global_position)
active_npc_visuals.erase(npc_id)
visual.queue_free()
return true
func reload_npc_visual(npc_id: int) -> bool:
if active_npc_visuals.has(npc_id):
return false
for npc in simulation_manager.npcs:
if npc.id == npc_id:
spawn_npc_visual(npc)
return true
return false
func _on_npc_target_requested(npc: SimNPC) -> void:
var visual = active_npc_visuals.get(npc.id) as Node3D
if visual == null:
return
if not simulation_manager.resolve_npc_target(npc.id, visual.global_position):
debug_log("Target resolution failed for %s" % npc.npc_name)
func _on_npc_travel_requested(npc: SimNPC, target_position: Vector3) -> void:
var visual = active_npc_visuals.get(npc.id)
if visual == null:
return
visual.set_target_position(target_position)
func _on_npc_visual_arrived(sim_id: int) -> void:
debug_log("NPC visual arrived. sim_id=%s" % sim_id)
if simulation_manager == null:
push_error("WorldViewManager: simulation_manager missing during arrival")
return
var visual = active_npc_visuals.get(sim_id) as Node3D
if visual != null:
simulation_manager.synchronize_npc_position(sim_id, visual.global_position)
simulation_manager.notify_npc_arrived(sim_id)
func _on_npc_visual_navigation_failed(sim_id: int) -> void:
debug_log("NPC visual navigation failed. sim_id=%s" % sim_id)
if simulation_manager == null:
push_error("WorldViewManager: simulation_manager missing during failure")
return
var visual = active_npc_visuals.get(sim_id) as Node3D
if visual != null:
simulation_manager.synchronize_npc_position(sim_id, visual.global_position)
simulation_manager.notify_npc_navigation_failed(sim_id)
func _on_npc_visual_position_changed(sim_id: int, active_position: Vector3) -> void:
simulation_manager.synchronize_npc_position(sim_id, active_position)
func _on_npc_died(npc: SimNPC) -> void:
if not active_npc_visuals.has(npc.id):
return
var visual = active_npc_visuals[npc.id]
if visual.has_method("apply_dead_visual_state"):
visual.apply_dead_visual_state()
else:
push_error("WorldViewManager: NPCVisual has no apply_dead_visual_state")
func _on_npc_inventory_changed(npc: SimNPC, item_id: StringName, amount: float) -> void:
var visual = active_npc_visuals.get(npc.id)
if visual == null:
return
if item_id == SimulationIds.RESOURCE_FOOD:
if visual.has_method("set_carried_food_visible"):
visual.set_carried_food_visible(amount > 0.0)
elif item_id == SimulationIds.RESOURCE_WOOD:
if visual.has_method("set_carried_wood_visible"):
visual.set_carried_wood_visible(amount > 0.0)
func _on_npc_task_changed(npc: SimNPC, _old_task: StringName, _new_task: StringName) -> void:
var visual = active_npc_visuals.get(npc.id)
if visual == null:
return
if visual.has_method("set_task_presentation"):
visual.set_task_presentation(npc.current_task, npc.task_state)
else:
push_error("WorldViewManager: NPCVisual has no set_task_presentation method")
func _on_simulation_state_restored() -> void:
for visual in active_npc_visuals.values():
visual.queue_free()
active_npc_visuals.clear()
spawn_initial_npcs()