277 lines
9.6 KiB
GDScript
277 lines
9.6 KiB
GDScript
extends Node
|
|
|
|
@export var debug_logs := false
|
|
|
|
@export var npc_visual_scene: PackedScene
|
|
@export var simulation_manager: Node
|
|
@export var active_npcs_parent: Node3D
|
|
@export var pantry_visual: 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("relationship_changed"):
|
|
simulation_manager.relationship_changed.connect(_on_relationship_changed)
|
|
else:
|
|
push_error("WorldViewManager: SimulationManager has no relationship_changed signal")
|
|
if simulation_manager.has_signal("economic_event_recorded"):
|
|
simulation_manager.economic_event_recorded.connect(_on_economic_event_recorded)
|
|
else:
|
|
push_error("WorldViewManager: SimulationManager has no economic_event_recorded signal")
|
|
if simulation_manager.has_signal("opportunity_opened"):
|
|
simulation_manager.opportunity_opened.connect(_on_opportunity_changed)
|
|
else:
|
|
push_error("WorldViewManager: SimulationManager has no opportunity_opened signal")
|
|
if simulation_manager.has_signal("opportunity_resolved"):
|
|
simulation_manager.opportunity_resolved.connect(_on_opportunity_resolved)
|
|
else:
|
|
push_error("WorldViewManager: SimulationManager has no opportunity_resolved 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()
|
|
_refresh_world_state_presentation(false)
|
|
|
|
|
|
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
|
|
_sync_npc_opportunity_concern(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_relationship_changed(
|
|
relationship: RelationshipStateRecord, _cause_event: EconomicEventRecord
|
|
) -> void:
|
|
if relationship == null:
|
|
return
|
|
var visual = active_npc_visuals.get(relationship.get_observer_id())
|
|
if visual == null:
|
|
return
|
|
if visual.has_method("play_trust_gain_reaction"):
|
|
visual.play_trust_gain_reaction()
|
|
else:
|
|
push_error("WorldViewManager: NPCVisual has no play_trust_gain_reaction method")
|
|
|
|
|
|
func _on_economic_event_recorded(_event: EconomicEventRecord) -> void:
|
|
_refresh_pantry_stock(true)
|
|
|
|
|
|
func _on_opportunity_changed(_opportunity: OpportunityStateRecord) -> void:
|
|
_refresh_world_state_presentation(true)
|
|
|
|
|
|
func _on_opportunity_resolved(
|
|
_opportunity: OpportunityStateRecord, _cause_event: EconomicEventRecord
|
|
) -> void:
|
|
_refresh_world_state_presentation(true)
|
|
|
|
|
|
func _on_simulation_state_restored() -> void:
|
|
for visual in active_npc_visuals.values():
|
|
visual.queue_free()
|
|
active_npc_visuals.clear()
|
|
spawn_initial_npcs()
|
|
_refresh_world_state_presentation(false)
|
|
|
|
|
|
func _refresh_world_state_presentation(animate: bool) -> void:
|
|
_refresh_pantry_stock(animate)
|
|
_refresh_opportunity_concern()
|
|
|
|
|
|
func _refresh_pantry_stock(animate: bool) -> void:
|
|
if pantry_visual == null or not pantry_visual.has_method("set_food_amount"):
|
|
return
|
|
var pantry: StorageStateRecord = simulation_manager.get_pantry()
|
|
var amount := 0.0
|
|
if pantry != null:
|
|
amount = pantry.get_amount(SimulationIds.RESOURCE_FOOD)
|
|
pantry_visual.set_food_amount(amount, animate)
|
|
|
|
|
|
func _refresh_opportunity_concern() -> void:
|
|
for visual in active_npc_visuals.values():
|
|
if visual.has_method("set_opportunity_concern_visible"):
|
|
visual.set_opportunity_concern_visible(false)
|
|
var opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity()
|
|
if opportunity == null:
|
|
return
|
|
var interested_visual = active_npc_visuals.get(opportunity.get_interested_npc_id())
|
|
if (
|
|
interested_visual != null
|
|
and interested_visual.has_method("set_opportunity_concern_visible")
|
|
):
|
|
interested_visual.set_opportunity_concern_visible(true)
|
|
|
|
|
|
func _sync_npc_opportunity_concern(npc_id: int, visual: Node3D) -> void:
|
|
if not visual.has_method("set_opportunity_concern_visible"):
|
|
return
|
|
var opportunity: OpportunityStateRecord = simulation_manager.get_active_opportunity()
|
|
visual.set_opportunity_concern_visible(
|
|
opportunity != null and opportunity.get_interested_npc_id() == npc_id
|
|
)
|