From 052b36a4e7830d95f7e4b5e62c46859dd617953e Mon Sep 17 00:00:00 2001
From: Rijad Zuzo <rijad.zuzo@matriq.ch>
Date: Sun, 21 Jun 2026 00:11:34 +0200
Subject: [PATCH] feat: add completition of work only after work is done

---
 main.tscn                       | 22 +++++++++++++----
 player/npc/NpcVisual.gd         | 27 +++++++++++++++++++++
 simulation/SimNPC.gd            | 42 ++++++++++++++++++++++-----------
 simulation/SimulationManager.gd | 20 ++++++++++++++++
 world/world_view_manager.gd     | 18 +++++++++++++-
 5 files changed, 109 insertions(+), 20 deletions(-)

diff --git a/main.tscn b/main.tscn
index 867b7b4..db8d495 100644
--- a/main.tscn
+++ b/main.tscn
@@ -49,6 +49,7 @@ height = 5.774
 [sub_resource type="BoxMesh" id="BoxMesh_kek77"]
 
 [sub_resource type="PrismMesh" id="PrismMesh_5vw27"]
+size = Vector3(3, 5, 2)
 
 [sub_resource type="BoxMesh" id="BoxMesh_4c57u"]
 
@@ -146,30 +147,41 @@ mesh = SubResource("BoxMesh_5vw27")
 transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 8, 0, -6)
 
 [node name="smallTree" parent="TaskZone/ForestZone" instance=ExtResource("6_5vw27")]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.13034248, 0.23380828, -0.6683569)
+transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, 1.891, 0, -0.22)
+
+[node name="smallTree2" parent="TaskZone/ForestZone" instance=ExtResource("6_5vw27")]
+transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, -0.043, 0, -1.767)
+
+[node name="smallTree3" parent="TaskZone/ForestZone" instance=ExtResource("6_5vw27")]
+transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, 0.13, 0, 0.614)
 
 [node name="highTree" parent="TaskZone/ForestZone" instance=ExtResource("7_kek77")]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.78092957, 0.23380828, 0.17081833)
+transform = Transform3D(3, 0, 0, 0, 3, 0, 0, 0, 3, -1.539, 0, 0.171)
 
 [node name="GuardZone" type="Marker3D" parent="TaskZone"]
 transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 8)
 
 [node name="MeshInstance3D" type="MeshInstance3D" parent="TaskZone/GuardZone"]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3, 0)
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.354011, 0)
 mesh = SubResource("CylinderMesh_5vw27")
 
 [node name="StudyZone" type="Marker3D" parent="TaskZone"]
 transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -6, 0, 6)
 
 [node name="MeshInstance3D" type="MeshInstance3D" parent="TaskZone/StudyZone"]
-transform = Transform3D(1, 0, 0, 0, 10, 0, 0, 0, 1, 0, 5, 0)
+transform = Transform3D(3.279775, 0, 0, 0, 5.511466, 0, 0, 0, 1, 0, 2.8180213, 0)
 mesh = SubResource("BoxMesh_kek77")
+metadata/_edit_group_ = true
 
 [node name="RestZone" type="Marker3D" parent="TaskZone"]
 transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 4, 0, 5)
 
 [node name="MeshInstance3D" type="MeshInstance3D" parent="TaskZone/RestZone"]
-transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.4911091, 0)
+mesh = SubResource("PrismMesh_5vw27")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="TaskZone/RestZone"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.3897309, 1.399735, 1.0352364)
 mesh = SubResource("PrismMesh_5vw27")
 
 [node name="FoodZone" type="Marker3D" parent="TaskZone"]
diff --git a/player/npc/NpcVisual.gd b/player/npc/NpcVisual.gd
index 57383d4..b7cd605 100644
--- a/player/npc/NpcVisual.gd
+++ b/player/npc/NpcVisual.gd
@@ -1,5 +1,7 @@
 extends CharacterBody3D
 
+signal arrived_at_target(sim_id: int)
+
 const DEBUG_LOGS := false
 
 @export var move_speed := 3.0
@@ -8,6 +10,7 @@ const DEBUG_LOGS := false
 
 @onready var nav_agent: NavigationAgent3D = $NavigationAgent3D
 
+var has_reported_arrival := false
 var sim_id: int = -1
 var npc_name: String = ""
 var profession: String = ""
@@ -30,6 +33,24 @@ func set_target_position(pos: Vector3) -> void:
 	if current_target.distance_to(pos) < 0.1:
 		return
 
+	current_target = pos
+	has_reported_arrival = false
+	nav_agent.target_position = pos
+
+	await get_tree().physics_frame
+
+	current_path = NavigationServer3D.map_get_path(
+		get_world_3d().navigation_map,
+		global_position,
+		pos,
+		true
+	)
+
+	path_index = 0
+	debug_log("New target: %s Path points: %s" % [pos, current_path.size()])
+	if current_target.distance_to(pos) < 0.1:
+		return
+
 	current_target = pos
 	nav_agent.target_position = pos
 
@@ -50,6 +71,12 @@ func _physics_process(delta: float) -> void:
 	if current_path.is_empty() or path_index >= current_path.size():
 		velocity = Vector3.ZERO
 		move_and_slide()
+
+		if not has_reported_arrival and current_target != Vector3.INF:
+			has_reported_arrival = true
+			debug_log("Arrived at target")
+			arrived_at_target.emit(sim_id)
+
 		return
 
 	var next_pos := current_path[path_index]
diff --git a/simulation/SimNPC.gd b/simulation/SimNPC.gd
index 3e82eb2..e218040 100644
--- a/simulation/SimNPC.gd
+++ b/simulation/SimNPC.gd
@@ -1,6 +1,11 @@
 class_name SimNPC
 extends RefCounted
 
+const TASK_STATE_IDLE := "idle"
+const TASK_STATE_TRAVELING := "traveling"
+const TASK_STATE_WORKING := "working"
+const TASK_STATE_COMPLETE := "complete"
+
 var id: int
 var npc_name: String
 var profession: String
@@ -9,6 +14,7 @@ var energy: float
 var strength: float
 var intelligence: float
 var current_task: String = "idle"
+var task_state: String = TASK_STATE_IDLE
 var position: Vector3
 
 var is_starving := false
@@ -31,7 +37,6 @@ func _init(
 	intelligence = _intelligence
 	hunger = randf_range(20.0, 80.0)
 	energy = randf_range(40.0, 100.0)
-
 	position = Vector3(randf_range(-8.0, 8.0), 0.0, randf_range(-8.0, 8.0))
 
 func simulate_tick(village: SimVillage) -> void:
@@ -48,30 +53,30 @@ func simulate_tick(village: SimVillage) -> void:
 
 	energy = clamp(energy, 0.0, 100.0)
 
-	if not task_complete:
-		task_progress += 1.0
-		if task_progress >= task_duration:
-			task_complete = true
+	if task_state == TASK_STATE_IDLE or task_state == TASK_STATE_COMPLETE:
+		choose_new_task(village)
 		return
 
-	choose_new_task(village)
-			
-			
-func choose_new_task(village: SimVillage) -> void:
-	task_progress = 0.0
-	task_complete = false
+	if task_state == TASK_STATE_TRAVELING:
+		return
 
+	if task_state == TASK_STATE_WORKING:
+		task_progress += 1.0
+
+		if task_progress >= task_duration:
+			task_complete = true
+			task_state = TASK_STATE_COMPLETE
+
+func choose_new_task(village: SimVillage) -> void:
 	if hunger > 75.0:
 		if village.food > 0:
 			set_task("eat", 2.0)
-			hunger -= 25.0
 		else:
 			set_task("gather_food", 5.0)
 		return
- 
+
 	if energy < 30.0:
 		set_task("rest", 4.0)
-		energy += 20.0
 		return
 
 	if village.food < 15.0:
@@ -107,3 +112,12 @@ func set_task(task: String, duration: float) -> void:
 	task_duration = duration
 	task_progress = 0.0
 	task_complete = false
+	task_state = TASK_STATE_TRAVELING
+
+func start_working() -> void:
+	if task_state != TASK_STATE_TRAVELING:
+		return
+
+	task_state = TASK_STATE_WORKING
+	task_progress = 0.0
+	task_complete = false
diff --git a/simulation/SimulationManager.gd b/simulation/SimulationManager.gd
index 7360533..7b6cf84 100644
--- a/simulation/SimulationManager.gd
+++ b/simulation/SimulationManager.gd
@@ -56,6 +56,7 @@ func simulate_tick() -> void:
 
 	for npc in npcs:
 		var old_task := npc.current_task
+		var old_state := npc.task_state
 		var was_complete := npc.task_complete
 
 		npc.simulate_tick(village)
@@ -67,6 +68,10 @@ func simulate_tick() -> void:
 			village.apply_npc_task(npc)
 			village_was_changed = true
 
+			npc.task_complete = true
+			npc.task_state = SimNPC.TASK_STATE_IDLE
+			npc.current_task = "idle"
+
 		if DEBUG_LOGS:
 			print(
 				npc.npc_name,
@@ -74,6 +79,8 @@ func simulate_tick() -> void:
 				npc.profession,
 				" | task: ",
 				npc.current_task,
+				" | state: ",
+				npc.task_state,
 				" | progress: ",
 				npc.task_progress,
 				"/",
@@ -86,11 +93,24 @@ func simulate_tick() -> void:
 				round(npc.energy)
 			)
 
+			if old_state != npc.task_state:
+				print("[SimulationManager] State changed: ", npc.npc_name, " ", old_state, " -> ", npc.task_state)
+
 	if village_was_changed:
 		village_changed.emit(village)
 
 	if DEBUG_LOGS:
 		print(village.get_summary())
+		
+func notify_npc_arrived(npc_id: int) -> void:
+	for npc in npcs:
+		if npc.id == npc_id:
+			if npc.task_state == SimNPC.TASK_STATE_TRAVELING:
+				npc.start_working()
+
+				if DEBUG_LOGS:
+					print("[SimulationManager] ", npc.npc_name, " arrived and started working on: ", npc.current_task)
+			return
 
 func eat_food(amount: float) -> void:
 	village.food -= amount
diff --git a/world/world_view_manager.gd b/world/world_view_manager.gd
index 6c20b5d..fce741a 100644
--- a/world/world_view_manager.gd
+++ b/world/world_view_manager.gd
@@ -61,8 +61,12 @@ func spawn_npc_visual(npc: SimNPC) -> void:
 	visual.global_position = npc.position
 	visual.setup_from_sim(npc)
 
-	active_npc_visuals[npc.id] = visual
+	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")
 
+	active_npc_visuals[npc.id] = visual
 	debug_log("Spawned visual for %s at %s" % [npc.npc_name, npc.position])
 
 func update_npc_targets() -> void:
@@ -111,3 +115,15 @@ func _on_npc_task_changed(npc: SimNPC, old_task: String, new_task: String) -> vo
 	
 	if target != null:
 		visual.set_target_position(target.global_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 report")
+		return
+
+	if simulation_manager.has_method("notify_npc_arrived"):
+		simulation_manager.notify_npc_arrived(sim_id)
+	else:
+		push_error("WorldViewManager: SimulationManager has no notify_npc_arrived method")
