94 lines
3.0 KiB
GDScript
94 lines
3.0 KiB
GDScript
class_name ActionTargetResolver
|
|
extends RefCounted
|
|
|
|
|
|
func resolve(
|
|
npc: SimNPC, origin: Vector3, simulation_manager: Node, active_world_adapter: Node
|
|
) -> Dictionary:
|
|
var definition := SimulationDefinitions.get_action(npc.current_task)
|
|
if definition == null:
|
|
return {}
|
|
match definition.target_type:
|
|
SimulationIds.TARGET_RESOURCE:
|
|
return _resolve_resource(
|
|
npc, origin, definition, simulation_manager, active_world_adapter
|
|
)
|
|
SimulationIds.TARGET_ACTIVITY:
|
|
return _resolve_activity(npc, origin, simulation_manager, active_world_adapter)
|
|
SimulationIds.TARGET_FREE:
|
|
return {
|
|
"target_id": "", "position": origin + simulation_manager.get_wander_offset(npc.id)
|
|
}
|
|
return {}
|
|
|
|
|
|
func _resolve_resource(
|
|
npc: SimNPC,
|
|
origin: Vector3,
|
|
definition: ActionDefinition,
|
|
simulation_manager: Node,
|
|
active_world_adapter: Node
|
|
) -> Dictionary:
|
|
var best: Dictionary = {}
|
|
var best_score := INF
|
|
for candidate in active_world_adapter.get_resource_candidates(definition.resource_action_id):
|
|
var node_id := StringName(candidate["target_id"])
|
|
var state: ResourceStateRecord = simulation_manager.get_resource_state(node_id)
|
|
if state == null or not state.can_npc_use() or not state.is_available_for(npc.id):
|
|
continue
|
|
var position: Vector3 = candidate["position"]
|
|
var score := score_resource_candidate(npc, origin, position, state)
|
|
if score < best_score:
|
|
best_score = score
|
|
best = candidate
|
|
if best.is_empty():
|
|
return {}
|
|
var target_id := StringName(best["target_id"])
|
|
if not simulation_manager.reserve_resource(target_id, npc.id):
|
|
return {}
|
|
return best
|
|
|
|
|
|
func _resolve_activity(
|
|
npc: SimNPC, origin: Vector3, simulation_manager: Node, active_world_adapter: Node
|
|
) -> Dictionary:
|
|
if not active_world_adapter.has_method("get_activity_candidates"):
|
|
return active_world_adapter.get_activity_target(npc.current_task, origin)
|
|
|
|
var best: Dictionary = {}
|
|
var best_distance := INF
|
|
var candidates: Array[Dictionary] = active_world_adapter.get_activity_candidates(npc.current_task)
|
|
for candidate in candidates:
|
|
var target_id := StringName(candidate["target_id"])
|
|
var capacity := maxi(int(candidate.get("capacity", 1)), 1)
|
|
if (
|
|
simulation_manager.has_method("get_activity_target_claim_count")
|
|
and simulation_manager.get_activity_target_claim_count(target_id, npc.id) >= capacity
|
|
):
|
|
continue
|
|
var position: Vector3 = candidate["position"]
|
|
var distance := origin.distance_squared_to(position)
|
|
if distance < best_distance:
|
|
best_distance = distance
|
|
best = candidate
|
|
|
|
if not best.is_empty():
|
|
return best
|
|
if not candidates.is_empty():
|
|
return {}
|
|
return active_world_adapter.get_activity_target(npc.current_task, origin)
|
|
|
|
|
|
func score_resource_candidate(
|
|
npc: SimNPC, origin: Vector3, position: Vector3, state: ResourceStateRecord
|
|
) -> float:
|
|
var distance := origin.distance_to(position)
|
|
var comfort_overage := maxf(distance - state.get_comfort_distance(), 0.0)
|
|
var risk_weight := 20.0 + maxf(100.0 - npc.energy, 0.0) * 0.2
|
|
return (
|
|
distance
|
|
+ comfort_overage * 2.5
|
|
+ state.get_safety_risk() * risk_weight
|
|
- state.get_discovery_priority()
|
|
)
|