feat: add player resource node harvesting

This commit is contained in:
2026-07-04 19:47:31 +02:00
parent f5e7326d49
commit 326df51cbe
6 changed files with 117 additions and 18 deletions
+1 -3
View File
@@ -77,13 +77,11 @@ skeleton = NodePath("../../..")
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.18380833, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.18380833, 0)
shape = SubResource("BoxShape3D_lquwl") shape = SubResource("BoxShape3D_lquwl")
[node name="Player" type="CharacterBody3D" parent="." unique_id=2022843760 node_paths=PackedStringArray("camera_rig", "simulation_manager", "farm_zone", "forest_zone", "food_zone", "guard_zone", "study_zone")] [node name="Player" type="CharacterBody3D" parent="." unique_id=2022843760 node_paths=PackedStringArray("camera_rig", "simulation_manager", "food_zone", "guard_zone", "study_zone")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0) transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.05, 0)
script = ExtResource("1_h2yge") script = ExtResource("1_h2yge")
camera_rig = NodePath("../CameraRig") camera_rig = NodePath("../CameraRig")
simulation_manager = NodePath("../SimulationManager") simulation_manager = NodePath("../SimulationManager")
farm_zone = NodePath("../TaskZone/FarmZone")
forest_zone = NodePath("../TaskZone/ForestZone")
food_zone = NodePath("../TaskZone/FoodZone") food_zone = NodePath("../TaskZone/FoodZone")
guard_zone = NodePath("../TaskZone/GuardZone") guard_zone = NodePath("../TaskZone/GuardZone")
study_zone = NodePath("../TaskZone/StudyZone") study_zone = NodePath("../TaskZone/StudyZone")
+29 -10
View File
@@ -7,8 +7,6 @@ extends CharacterBody3D
@export var simulation_manager: Node @export var simulation_manager: Node
@export var farm_zone: Marker3D
@export var forest_zone: Marker3D
@export var food_zone: Marker3D @export var food_zone: Marker3D
@export var guard_zone: Marker3D @export var guard_zone: Marker3D
@export var study_zone: Marker3D @export var study_zone: Marker3D
@@ -62,13 +60,10 @@ func try_interact() -> void:
if simulation_manager == null: if simulation_manager == null:
return return
if is_near(farm_zone): if try_harvest_resource_node():
simulation_manager.add_food(5.0) return
print("Player gathered food for the village.")
elif is_near(forest_zone): if is_near(guard_zone):
simulation_manager.add_wood(5.0)
print("Player gathered wood for the village.")
elif is_near(guard_zone):
simulation_manager.add_safety(3.0) simulation_manager.add_safety(3.0)
print("Player helped guard the village.") print("Player helped guard the village.")
elif is_near(study_zone): elif is_near(study_zone):
@@ -76,7 +71,31 @@ func try_interact() -> void:
print("Player shared knowledge.") print("Player shared knowledge.")
elif is_near(food_zone): elif is_near(food_zone):
simulation_manager.eat_food(stomach_capacity_for_food) simulation_manager.eat_food(stomach_capacity_for_food)
print("Player eating shit up.") print("Player ate food from the village supply.")
func try_harvest_resource_node() -> bool:
var node := ResourceNode.find_nearest_for_player(global_position, interaction_range)
if node == null:
return false
if not node.enabled:
print("%s is unavailable." % node.node_id)
return true
if node.is_depleted():
print("%s is depleted." % node.node_id)
return true
if not simulation_manager.has_method("harvest_resource_node"):
push_error("Player: SimulationManager cannot harvest ResourceNodes")
return true
var extracted: float = simulation_manager.harvest_resource_node(node)
if extracted > 0.0:
print("Player gathered %s %s from %s." % [extracted, node.resource_id, node.node_id])
else:
print("%s could not be harvested." % node.node_id)
return true
func is_near(zone: Marker3D) -> bool: func is_near(zone: Marker3D) -> bool:
if zone == null: if zone == null:
+21 -5
View File
@@ -208,24 +208,40 @@ func notify_npc_navigation_failed(npc_id: int) -> void:
func notify_npc_target_unavailable(npc_id: int) -> void: func notify_npc_target_unavailable(npc_id: int) -> void:
notify_npc_navigation_failed(npc_id) notify_npc_navigation_failed(npc_id)
func harvest_resource_node(node: ResourceNode) -> float:
if node == null or not node.can_player_use:
return 0.0
var extracted := node.extract()
if extracted <= 0.0:
return 0.0
village.apply_resource_delta(node.resource_id, extracted)
village_changed.emit(village)
if DEBUG_LOGS:
print("[SimulationManager] Player extracted ", extracted, " ", node.resource_id, " from ", node.node_id)
return extracted
func eat_food(amount: float) -> void: func eat_food(amount: float) -> void:
village.food -= amount village.apply_resource_delta(&"food", -amount)
village_changed.emit(village) village_changed.emit(village)
func add_food(amount: float) -> void: func add_food(amount: float) -> void:
village.food += amount village.apply_resource_delta(&"food", amount)
village_changed.emit(village) village_changed.emit(village)
func add_wood(amount: float) -> void: func add_wood(amount: float) -> void:
village.wood += amount village.apply_resource_delta(&"wood", amount)
village_changed.emit(village) village_changed.emit(village)
func add_safety(amount: float) -> void: func add_safety(amount: float) -> void:
village.safety = clamp(village.safety + amount, 0.0, 100.0) village.apply_resource_delta(&"safety", amount)
village_changed.emit(village) village_changed.emit(village)
func add_knowledge(amount: float) -> void: func add_knowledge(amount: float) -> void:
village.knowledge += amount village.apply_resource_delta(&"knowledge", amount)
village_changed.emit(village) village_changed.emit(village)
func get_starving_count() -> int: func get_starving_count() -> int:
+50
View File
@@ -0,0 +1,50 @@
extends SceneTree
var main_scene: Node
var failures: Array[String] = []
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
main_scene = load("res://main.tscn").instantiate()
root.add_child(main_scene)
await process_frame
await physics_frame
var simulation_manager := main_scene.get_node("SimulationManager")
var player := main_scene.get_node("Player")
var bush := main_scene.get_node("ResourceNodes/BerryBush_01") as ResourceNode
var tree := main_scene.get_node("ResourceNodes/Tree_01") as ResourceNode
simulation_manager.set_process(false)
bush.amount_remaining = 1.0
_check(bush.reserve(999), "Player contention setup should reserve the bush")
player.global_position = bush.interaction_point.global_position
var food_before: float = simulation_manager.village.food
player.try_interact()
_check(is_equal_approx(bush.amount_remaining, 0.0), "Player should deplete the nearby bush")
_check(is_equal_approx(simulation_manager.village.food, food_before + 1.0), "Village should receive exactly the bush's remaining food")
_check(bush.reserved_by == -1, "Depletion should release the NPC reservation")
player.global_position = tree.interaction_point.global_position
var wood_before: float = simulation_manager.village.wood
var tree_before := tree.amount_remaining
player.try_interact()
_check(is_equal_approx(tree.amount_remaining, tree_before - tree.yield_per_action), "Player should extract the tree's configured yield")
_check(is_equal_approx(simulation_manager.village.wood, wood_before + tree.yield_per_action), "Village should receive exactly the extracted wood")
if failures.is_empty():
print("[TEST] ResourceNode player parity passed")
quit(0)
return
for failure in failures:
push_error("[TEST] " + failure)
quit(1)
func _check(condition: bool, message: String) -> void:
if not condition:
failures.append(message)
@@ -0,0 +1 @@
uid://dunn1mc6772fq
+15
View File
@@ -67,6 +67,9 @@ func extract(requested_amount: float = yield_per_action) -> float:
amount_remaining -= extracted amount_remaining -= extracted
amount_changed.emit(node_id, amount_remaining) amount_changed.emit(node_id, amount_remaining)
if is_depleted(): if is_depleted():
if reserved_by >= 0:
reserved_by = -1
reservation_changed.emit(node_id, -1)
depleted.emit(node_id) depleted.emit(node_id)
if hide_visual_when_depleted and has_node("Visual"): if hide_visual_when_depleted and has_node("Visual"):
$Visual.visible = false $Visual.visible = false
@@ -104,3 +107,15 @@ static func find_available(action: StringName, agent_id: int, from_position: Vec
best_dist = dist best_dist = dist
best = node best = node
return best 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