feat: index loaded resource discovery
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
class_name ActionTargetResolver
|
||||
extends RefCounted
|
||||
|
||||
var last_resource_query_stats: Dictionary = {}
|
||||
|
||||
|
||||
func resolve(
|
||||
npc: SimNPC, origin: Vector3, simulation_manager: Node, active_world_adapter: Node
|
||||
@@ -28,10 +30,33 @@ func _resolve_resource(
|
||||
definition: ActionDefinition,
|
||||
simulation_manager: Node,
|
||||
active_world_adapter: Node
|
||||
) -> Dictionary:
|
||||
last_resource_query_stats = {}
|
||||
if (
|
||||
active_world_adapter.has_method("get_resource_candidates_in_radius")
|
||||
and active_world_adapter.has_method("get_resource_query_profile")
|
||||
):
|
||||
return _resolve_resource_spatial(
|
||||
npc, origin, definition, simulation_manager, active_world_adapter
|
||||
)
|
||||
return _resolve_resource_linear(
|
||||
npc, origin, definition, simulation_manager, active_world_adapter
|
||||
)
|
||||
|
||||
|
||||
func _resolve_resource_linear(
|
||||
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 candidates: Array[Dictionary] = active_world_adapter.get_resource_candidates(
|
||||
definition.resource_action_id
|
||||
)
|
||||
for candidate in candidates:
|
||||
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):
|
||||
@@ -41,12 +66,108 @@ func _resolve_resource(
|
||||
if score < best_score:
|
||||
best_score = score
|
||||
best = candidate
|
||||
if best.is_empty():
|
||||
last_resource_query_stats = {
|
||||
"mode": "linear",
|
||||
"loaded_candidate_count": candidates.size(),
|
||||
"candidates_inspected": candidates.size(),
|
||||
"range_pass_count": 1,
|
||||
}
|
||||
return _reserve_resource_candidate(best, npc, simulation_manager)
|
||||
|
||||
|
||||
func _resolve_resource_spatial(
|
||||
npc: SimNPC,
|
||||
origin: Vector3,
|
||||
definition: ActionDefinition,
|
||||
simulation_manager: Node,
|
||||
active_world_adapter: Node
|
||||
) -> Dictionary:
|
||||
var profile: Dictionary = active_world_adapter.get_resource_query_profile(
|
||||
definition.resource_action_id, origin
|
||||
)
|
||||
var loaded_count := int(profile.get("candidate_count", 0))
|
||||
if loaded_count == 0:
|
||||
last_resource_query_stats = {
|
||||
"mode": "spatial",
|
||||
"loaded_candidate_count": 0,
|
||||
"candidates_inspected": 0,
|
||||
"range_pass_count": 0,
|
||||
}
|
||||
return {}
|
||||
var target_id := StringName(best["target_id"])
|
||||
|
||||
var best: Dictionary = {}
|
||||
var best_score := INF
|
||||
var best_registration_order := 9223372036854775807
|
||||
var maximum_distance := maxf(float(profile["max_distance"]), 0.0)
|
||||
var radius := minf(maxf(float(profile["initial_radius"]), 0.1), maximum_distance)
|
||||
var previous_radius := -1.0
|
||||
var candidates_inspected := 0
|
||||
var range_pass_count := 0
|
||||
while true:
|
||||
var candidates: Array[Dictionary] = active_world_adapter.get_resource_candidates_in_radius(
|
||||
definition.resource_action_id, origin, radius, previous_radius
|
||||
)
|
||||
range_pass_count += 1
|
||||
candidates_inspected += candidates.size()
|
||||
for candidate in candidates:
|
||||
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)
|
||||
var registration_order := int(candidate["registration_order"])
|
||||
if (
|
||||
score < best_score
|
||||
or (score == best_score and registration_order < best_registration_order)
|
||||
):
|
||||
best_score = score
|
||||
best_registration_order = registration_order
|
||||
best = candidate
|
||||
|
||||
if radius >= maximum_distance:
|
||||
break
|
||||
if (
|
||||
not best.is_empty()
|
||||
and _minimum_resource_score_beyond(npc, radius, profile) > best_score
|
||||
):
|
||||
break
|
||||
previous_radius = radius
|
||||
var next_radius := minf(maximum_distance, radius * 2.0)
|
||||
if next_radius <= radius:
|
||||
break
|
||||
radius = next_radius
|
||||
|
||||
last_resource_query_stats = {
|
||||
"mode": "spatial",
|
||||
"loaded_candidate_count": loaded_count,
|
||||
"candidates_inspected": candidates_inspected,
|
||||
"range_pass_count": range_pass_count,
|
||||
"final_radius": radius,
|
||||
}
|
||||
return _reserve_resource_candidate(best, npc, simulation_manager)
|
||||
|
||||
|
||||
func _minimum_resource_score_beyond(npc: SimNPC, radius: float, profile: Dictionary) -> float:
|
||||
var comfort_overage := maxf(radius - float(profile["max_comfort_distance"]), 0.0)
|
||||
var risk_weight := 20.0 + maxf(100.0 - npc.energy, 0.0) * 0.2
|
||||
return (
|
||||
radius
|
||||
+ comfort_overage * 2.5
|
||||
+ float(profile["min_safety_risk"]) * risk_weight
|
||||
- float(profile["max_discovery_priority"])
|
||||
)
|
||||
|
||||
|
||||
func _reserve_resource_candidate(
|
||||
candidate: Dictionary, npc: SimNPC, simulation_manager: Node
|
||||
) -> Dictionary:
|
||||
if candidate.is_empty():
|
||||
return {}
|
||||
var target_id := StringName(candidate["target_id"])
|
||||
if not simulation_manager.reserve_resource(target_id, npc.id):
|
||||
return {}
|
||||
return best
|
||||
return candidate
|
||||
|
||||
|
||||
func _resolve_activity(
|
||||
|
||||
Reference in New Issue
Block a user