class_name ActionSelectionSystem extends RefCounted func select_action(npc: SimNPC, village: SimVillage) -> ActionSelectionResult: if npc.is_dead: return null if npc.is_starving: if npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) >= 1.0: return ActionSelectionResult.new( SimulationIds.ACTION_EAT, 1.0, "Starving and carrying food" ) if village.food > 0: return ActionSelectionResult.new( SimulationIds.ACTION_WITHDRAW_FOOD, 1.0, "Starving; pantry has food" ) return ActionSelectionResult.new( SimulationIds.ACTION_GATHER_FOOD, 4.0, "Starving; pantry is empty" ) if npc.hunger > 75.0: if npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) >= 1.0: return ActionSelectionResult.new( SimulationIds.ACTION_EAT, -1.0, "Hungry and carrying food" ) if village.food > 0: return ActionSelectionResult.new( SimulationIds.ACTION_WITHDRAW_FOOD, -1.0, "Hungry; pantry has food" ) return ActionSelectionResult.new( SimulationIds.ACTION_GATHER_FOOD, -1.0, "Hungry; pantry is empty" ) if npc.get_inventory_amount(SimulationIds.RESOURCE_FOOD) > 0.0: return ActionSelectionResult.new( SimulationIds.ACTION_DEPOSIT_FOOD, -1.0, "Carrying food for storage" ) if npc.energy < 25.0: return ActionSelectionResult.new( SimulationIds.ACTION_REST, -1.0, "Energy is below the rest threshold" ) return _choose_best_work_action(npc, village) func _choose_best_work_action(npc: SimNPC, village: SimVillage) -> ActionSelectionResult: var scores := { SimulationIds.ACTION_GATHER_FOOD: _calculate_score( npc, SimulationIds.ACTION_GATHER_FOOD, village.food_priority, village.food, 15.0, 30.0, 3.0, 1.5 ), SimulationIds.ACTION_GATHER_WOOD: _calculate_score( npc, SimulationIds.ACTION_GATHER_WOOD, village.wood_priority, village.wood, 10.0, 20.0, 2.5, 1.0 ), SimulationIds.ACTION_PATROL: _calculate_score( npc, SimulationIds.ACTION_PATROL, village.safety_priority, village.safety, 30.0, 50.0, 3.0, 1.5 ), SimulationIds.ACTION_STUDY: _calculate_score( npc, SimulationIds.ACTION_STUDY, village.knowledge_priority, village.knowledge, 10.0, 25.0, 1.5, 0.75 ) } var best_action := SimulationIds.ACTION_GATHER_FOOD var best_score: float = scores[best_action] for action_id in [ SimulationIds.ACTION_GATHER_WOOD, SimulationIds.ACTION_PATROL, SimulationIds.ACTION_STUDY ]: var score: float = scores[action_id] if score > best_score: best_action = action_id best_score = score return ActionSelectionResult.new( best_action, -1.0, "Highest current work utility", scores ) func _calculate_score( npc: SimNPC, action_id: StringName, base_priority: float, resource_amount: float, critical_threshold: float, low_threshold: float, critical_bonus: float, low_bonus: float ) -> float: var score := base_priority if resource_amount < critical_threshold: score += critical_bonus elif resource_amount < low_threshold: score += low_bonus var definition := SimulationDefinitions.get_action(action_id) if ( definition != null and not definition.preferred_profession_id.is_empty() and npc.profession == definition.preferred_profession_id ): score += 2.0 if action_id == npc.last_task: score -= 1.5 score += npc.random_source.randf_range(0.0, 0.5) if npc.debug_logs: print("[ActionSelection] ", npc.npc_name, " score ", action_id, " = ", score) return score