refactor: separate action system responsibilities

This commit is contained in:
2026-07-05 14:17:45 +02:00
parent 81df7b5939
commit 4673f0698d
18 changed files with 458 additions and 383 deletions
+50 -28
View File
@@ -7,6 +7,8 @@ signal npc_task_changed(
)
signal village_changed(village: SimVillage)
signal npc_died(npc: SimNPC)
signal npc_target_requested(npc: SimNPC)
signal npc_travel_requested(npc: SimNPC, target_position: Vector3)
var village := SimVillage.new()
@@ -14,11 +16,15 @@ var npcs: Array[SimNPC] = []
@export var tick_interval := 1.2
@export var simulation_seed: int = 1337
@export var debug_logs := true
@export var active_world_adapter: Node
var clock: SimulationClock
var tick_count := 0
var wander_random_sources := {}
var resource_states: Dictionary = {}
var action_selector := ActionSelectionSystem.new()
var action_executor := ActionExecutionSystem.new()
var target_resolver := ActionTargetResolver.new()
var names := [
"Amina", "Tarik", "Jasmin"
@@ -97,7 +103,24 @@ func simulate_tick() -> void:
var was_complete := npc.task_complete
var was_dead := npc.is_dead
npc.simulate_tick(village)
action_executor.advance_npc(npc, village)
if (
not npc.is_dead
and old_state in [
SimNPC.TASK_STATE_IDLE,
SimNPC.TASK_STATE_COMPLETE
]
and npc.task_state in [
SimNPC.TASK_STATE_IDLE,
SimNPC.TASK_STATE_COMPLETE
]
):
var selection := action_selector.select_action(npc, village)
if selection != null:
npc.set_task(
selection.action_id,
selection.duration_override
)
if old_task != npc.current_task and old_target != &"":
release_npc_reservation(npc.id)
@@ -113,6 +136,7 @@ func simulate_tick() -> void:
if old_task != npc.current_task and not npc.is_dead:
npc_task_changed.emit(npc, old_task, npc.current_task)
npc_target_requested.emit(npc)
if not was_complete and npc.task_complete and not npc.is_dead:
var completed_task := npc.current_task
@@ -162,6 +186,7 @@ func simulate_tick() -> void:
if needs_immediate_food_after_gather and village.food > 0:
npc.set_task(SimulationIds.ACTION_EAT, 1.0)
npc_task_changed.emit(npc, completed_task, npc.current_task)
npc_target_requested.emit(npc)
if debug_logs:
print("[SimulationManager] ", npc.npc_name, " gathered food while starving and is going to eat immediately.")
@@ -255,6 +280,7 @@ func notify_npc_navigation_failed(npc_id: int) -> void:
npc.last_task = failed_task
npc.set_task(SimulationIds.ACTION_WANDER, 2.0)
npc_task_changed.emit(npc, failed_task, npc.current_task)
npc_target_requested.emit(npc)
if debug_logs:
print("[SimulationManager] ", npc.npc_name, " could not navigate for ", failed_task, " and is trying a wander target")
@@ -263,6 +289,29 @@ func notify_npc_navigation_failed(npc_id: int) -> void:
func notify_npc_target_unavailable(npc_id: int) -> void:
notify_npc_navigation_failed(npc_id)
func resolve_npc_target(npc_id: int, origin: Vector3) -> bool:
if active_world_adapter == null:
push_error("SimulationManager: active_world_adapter is missing")
return false
for npc in npcs:
if npc.id != npc_id:
continue
if npc.is_dead or npc.task_state != SimNPC.TASK_STATE_TRAVELING:
return false
var result := target_resolver.resolve(
npc,
origin,
self,
active_world_adapter
)
if result.is_empty():
notify_npc_target_unavailable(npc.id)
return false
npc.target_id = StringName(result.get("target_id", ""))
npc_travel_requested.emit(npc, result["position"])
return true
return false
func register_loaded_resource_nodes() -> void:
for node in ResourceNode.get_all():
register_resource_node(node)
@@ -305,33 +354,6 @@ func release_resource(node_id: StringName, agent_id: int) -> void:
if resource_state != null:
resource_state.release(agent_id)
func acquire_resource_target(
action_id: StringName,
agent_id: int,
from_position: Vector3
) -> ResourceNode:
var best: ResourceNode
var best_distance := INF
for node in ResourceNode.get_all():
var resource_state := get_resource_state(node.node_id)
if resource_state == null:
continue
if resource_state.get_action_id() != action_id:
continue
if not resource_state.can_npc_use():
continue
if not resource_state.is_available_for(agent_id):
continue
var distance := from_position.distance_squared_to(
node.interaction_point.global_position
)
if distance < best_distance:
best_distance = distance
best = node
if best == null or not reserve_resource(best.node_id, agent_id):
return null
return best
func find_resource_node_for_player(
from_position: Vector3,
max_distance: float