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")