Files
gamedev-the-steward/world/resource_nodes/ResourceNode.gd
T

139 lines
4.0 KiB
GDScript

class_name ResourceNode
extends Node3D
signal amount_changed(node_id: StringName, amount_remaining: float)
signal depleted(node_id: StringName)
signal reservation_changed(node_id: StringName, agent_id: int)
static var _all: Array[ResourceNode] = []
@export var node_id: StringName
@export var action_id: StringName = &"gather_food"
@export var resource_id: StringName = &"food"
@export var amount_remaining: float = 10.0
@export var yield_per_action: float = 2.0
@export var enabled: bool = true
@export var can_npcs_use: bool = true
@export var can_player_use: bool = true
@export var hide_visual_when_depleted: bool = false
@onready var interaction_point: Marker3D = $InteractionPoint
var reserved_by: int = -1
func _ready():
if node_id.is_empty():
push_error("ResourceNode at %s has empty node_id" % get_path())
enabled = false
else:
var existing := get_by_id(node_id)
if existing != null:
push_error("Duplicate ResourceNode node_id '%s' at %s; first registered at %s" % [node_id, get_path(), existing.get_path()])
enabled = false
_all.append(self)
add_to_group("resource_nodes")
amount_changed.connect(_update_debug_label)
reservation_changed.connect(_update_debug_label)
_update_debug_label()
func _exit_tree():
_all.erase(self)
func is_depleted() -> bool:
return amount_remaining <= 0.0
func can_extract() -> bool:
return enabled and not is_depleted()
func is_available_for(agent_id: int) -> bool:
return enabled and not is_depleted() and (reserved_by == -1 or reserved_by == agent_id)
func reserve(agent_id: int) -> bool:
if not is_available_for(agent_id):
return false
reserved_by = agent_id
reservation_changed.emit(node_id, agent_id)
return true
func release(agent_id: int) -> void:
if reserved_by == agent_id:
reserved_by = -1
reservation_changed.emit(node_id, -1)
func extract(requested_amount: float = yield_per_action) -> float:
if not can_extract():
return 0.0
var extracted := minf(requested_amount, amount_remaining)
amount_remaining -= extracted
amount_changed.emit(node_id, amount_remaining)
if is_depleted():
if reserved_by >= 0:
reserved_by = -1
reservation_changed.emit(node_id, -1)
depleted.emit(node_id)
if hide_visual_when_depleted and has_node("Visual"):
$Visual.visible = false
return extracted
func restore_persistent_state(
saved_amount: float,
saved_reserved_by: int,
saved_enabled: bool
) -> void:
amount_remaining = maxf(saved_amount, 0.0)
reserved_by = saved_reserved_by if amount_remaining > 0.0 else -1
enabled = saved_enabled
amount_changed.emit(node_id, amount_remaining)
reservation_changed.emit(node_id, reserved_by)
if has_node("Visual"):
$Visual.visible = not (hide_visual_when_depleted and is_depleted())
_update_debug_label()
func _update_debug_label(_a = null, _b = null) -> void:
if not has_node("DebugLabel"):
return
var label := $DebugLabel as Label3D
var text := "%s\n%s %.1f" % [node_id, resource_id, amount_remaining]
if reserved_by >= 0:
text += "\nheld:%d" % reserved_by
if is_depleted():
text += "\nDEPLETED"
label.text = text
static func get_by_id(node_id: StringName) -> ResourceNode:
for node in _all:
if node.node_id == node_id:
return node
return null
static func get_all() -> Array[ResourceNode]:
return _all.duplicate()
static func find_available(action: StringName, agent_id: int, from_position: Vector3) -> ResourceNode:
var best: ResourceNode
var best_dist := INF
for node in _all:
if node.action_id != action:
continue
if not node.can_npcs_use:
continue
if not node.is_available_for(agent_id):
continue
var dist := from_position.distance_squared_to(node.interaction_point.global_position)
if dist < best_dist:
best_dist = dist
best = node
return best
static func find_nearest_for_player(from_position: Vector3, max_distance: float) -> ResourceNode:
var best: ResourceNode
var best_dist := max_distance * max_distance
for node in _all:
if not node.can_player_use:
continue
var dist := from_position.distance_squared_to(node.interaction_point.global_position)
if dist <= best_dist:
best_dist = dist
best = node
return best