feat: synchronize authoritative npc position
- NpcVisual emits position_changed signal during _physics_process - SimulationManager.synchronize_npc_position() writes active position into SimNPC state on movement, arrival, and navigation failure - WorldViewManager forwards position_changed and syncs before despawn/reload - SimNPC stores travel_target_position and has_travel_target as versioned state; NPCStateRecord v2 persists them with v1 migration - WorldViewManager.spawn_npc_visual() resumes travel for traveling NPCs via SimulationManager.request_current_travel() - WorldViewManager.despawn_npc_visual() syncs position before removal - WorldViewManager.reload_npc_visual() spawns a visual from persisted state without rerolling target selection - tests/npc_visual_lifecycle_test.gd proves unload/reload preserves action, target, reservation, position, RNG state, and checksum - simulation_state_serialization_test adds legacy v1 NPC migration test that does not invent an active travel target
This commit is contained in:
@@ -17,7 +17,6 @@ func _ready() -> void:
|
||||
|
||||
func initialize_world_view() -> void:
|
||||
await get_tree().physics_frame
|
||||
spawn_initial_npcs()
|
||||
if simulation_manager.has_signal("npc_target_requested"):
|
||||
simulation_manager.npc_target_requested.connect(
|
||||
_on_npc_target_requested
|
||||
@@ -34,7 +33,7 @@ func initialize_world_view() -> void:
|
||||
simulation_manager.npc_died.connect(_on_npc_died)
|
||||
else:
|
||||
push_error("WorldViewManager: SimulationManager has no npc_died signal")
|
||||
request_current_targets()
|
||||
spawn_initial_npcs()
|
||||
|
||||
func spawn_initial_npcs() -> void:
|
||||
if simulation_manager == null:
|
||||
@@ -65,14 +64,37 @@ func spawn_npc_visual(npc: SimNPC) -> void:
|
||||
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 request_current_targets() -> void:
|
||||
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.task_state != SimNPC.TASK_STATE_TRAVELING:
|
||||
continue
|
||||
_on_npc_target_requested(npc)
|
||||
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
|
||||
@@ -98,6 +120,12 @@ func _on_npc_visual_arrived(sim_id: int) -> void:
|
||||
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:
|
||||
@@ -105,8 +133,20 @@ func _on_npc_visual_navigation_failed(sim_id: int) -> void:
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user