feat: give the player a real carried inventory and deposit transactions
This commit is contained in:
@@ -298,17 +298,28 @@ func _run() -> void:
|
||||
opportunity_bush_state.set_amount_remaining(1.0)
|
||||
player.global_position = opportunity_bush.interaction_point.global_position
|
||||
player.call("try_interact")
|
||||
var carried_after_gather: float = simulation_manager.get_player_state().get_inventory_amount(
|
||||
SimulationIds.RESOURCE_FOOD
|
||||
)
|
||||
var pantry_position: Vector3 = (
|
||||
main_scene
|
||||
. get_node("JajceWorld/WorldObjects/StorageSites/VillagePantry")
|
||||
. get_interaction_position()
|
||||
)
|
||||
player.global_position = pantry_position
|
||||
player.call("try_interact")
|
||||
village_ui.selected_npc_index = contributor.id
|
||||
village_ui.call("_refresh_npc_inspector")
|
||||
_check(
|
||||
(
|
||||
player_opportunity.get_status() == OpportunityStateRecord.STATUS_RESOLVED
|
||||
is_equal_approx(carried_after_gather, 1.0)
|
||||
and player_opportunity.get_status() == OpportunityStateRecord.STATUS_RESOLVED
|
||||
and "Resolved village need" in inspector_label.text
|
||||
and "◆ Player restocked the pantry" in inspector_label.text
|
||||
and whisper_kicker.text == "NEED MET"
|
||||
and "The player restocked the pantry" in whisper_message.text
|
||||
),
|
||||
"Player gathering should resolve the restored need in debug detail and transient HUD"
|
||||
"Player gathering and depositing should resolve the restored need in debug detail and transient HUD"
|
||||
)
|
||||
|
||||
var woodpile_state: StorageStateRecord = simulation_manager.get_woodpile()
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
extends SceneTree
|
||||
|
||||
var failures: Array[String] = []
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
var manager := _create_manager()
|
||||
var actor: SimNPC = manager.npcs[0]
|
||||
var interested: SimNPC = manager.npcs[2]
|
||||
var berry := _register_resource(
|
||||
manager,
|
||||
&"carry_berry_bush",
|
||||
SimulationIds.ACTION_GATHER_FOOD,
|
||||
SimulationIds.RESOURCE_FOOD,
|
||||
3.0
|
||||
)
|
||||
var pantry: StorageStateRecord = manager.get_pantry()
|
||||
_set_pantry_amount(manager, 1.0)
|
||||
for npc in manager.npcs:
|
||||
npc.position = Vector3(40.0 + npc.id * 10.0, 0.0, 0.0)
|
||||
actor.position = Vector3.ZERO
|
||||
interested.position = Vector3(4.0, 0.0, 0.0)
|
||||
actor.hunger = 60.0
|
||||
interested.hunger = 90.0
|
||||
manager.economy.withdraw_to_inventory(actor, SimulationIds.RESOURCE_FOOD, 1.0)
|
||||
var quest: PlayerQuestRecord = manager.get_active_player_quest()
|
||||
_check(quest != null, "The carry loop needs an open player quest")
|
||||
if quest == null:
|
||||
berry.free()
|
||||
manager.free()
|
||||
_finish()
|
||||
return
|
||||
|
||||
var state: PlayerStateRecord = manager.get_player_state()
|
||||
var carried: float = manager.player_gather(berry)
|
||||
_check(
|
||||
(
|
||||
is_equal_approx(carried, 2.0)
|
||||
and is_equal_approx(state.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 2.0)
|
||||
and is_equal_approx(manager.get_pantry().get_amount(SimulationIds.RESOURCE_FOOD), 0.0)
|
||||
),
|
||||
"Gathering should fill the player's hands instead of the pantry directly",
|
||||
)
|
||||
var capacity_limited: float = manager.player_gather(berry)
|
||||
_check(
|
||||
(
|
||||
is_equal_approx(capacity_limited, 1.0)
|
||||
and is_equal_approx(state.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 3.0)
|
||||
),
|
||||
"Remaining resource amount should cap the second gather",
|
||||
)
|
||||
var hands_full: float = manager.player_gather(berry)
|
||||
_check(
|
||||
(
|
||||
is_equal_approx(hands_full, 0.0)
|
||||
and is_equal_approx(state.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 3.0)
|
||||
),
|
||||
"An empty resource should refuse further gathering",
|
||||
)
|
||||
|
||||
var pantry_before: float = manager.get_pantry().get_amount(SimulationIds.RESOURCE_FOOD)
|
||||
var deposited: float = manager.player_deposit(SimulationIds.RESOURCE_FOOD)
|
||||
_check(
|
||||
(
|
||||
is_equal_approx(deposited, 3.0)
|
||||
and is_equal_approx(
|
||||
manager.get_pantry().get_amount(SimulationIds.RESOURCE_FOOD), pantry_before + 3.0
|
||||
)
|
||||
and is_equal_approx(state.get_inventory_amount(SimulationIds.RESOURCE_FOOD), 0.0)
|
||||
and manager.get_active_player_quest() == null
|
||||
and (
|
||||
manager.get_latest_player_quest_for_requester(interested.id).get_status()
|
||||
== PlayerQuestRecord.STATUS_COMPLETED
|
||||
)
|
||||
),
|
||||
"Depositing carried food should resolve the quest through the real pantry transaction",
|
||||
)
|
||||
|
||||
var saved_json: String = manager.serialize_state()
|
||||
var restored := _create_manager(902)
|
||||
_check(
|
||||
restored.restore_state_from_json(saved_json),
|
||||
"The emptied carry state should restore through the current schema"
|
||||
)
|
||||
_check(
|
||||
(
|
||||
restored.get_state_checksum() == manager.get_state_checksum()
|
||||
and is_equal_approx(
|
||||
restored.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_FOOD), 0.0
|
||||
)
|
||||
),
|
||||
"Restore should preserve the player's empty carried inventory and checksum",
|
||||
)
|
||||
|
||||
var carry_state_saved: String = manager.serialize_state()
|
||||
manager.player_gather(berry)
|
||||
var mid_carry: String = manager.serialize_state()
|
||||
var mid_restored := _create_manager(903)
|
||||
_check(
|
||||
mid_restored.restore_state_from_json(mid_carry),
|
||||
"A partially carried inventory should restore through the current schema"
|
||||
)
|
||||
_check(
|
||||
(
|
||||
mid_restored.get_state_checksum() == manager.get_state_checksum()
|
||||
and is_equal_approx(
|
||||
mid_restored.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_FOOD),
|
||||
manager.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_FOOD)
|
||||
)
|
||||
),
|
||||
"Restore should preserve the player's carried inventory mid-transport",
|
||||
)
|
||||
|
||||
berry.free()
|
||||
manager.free()
|
||||
restored.free()
|
||||
mid_restored.free()
|
||||
_finish()
|
||||
|
||||
|
||||
func _register_resource(
|
||||
manager: Node,
|
||||
node_id: StringName,
|
||||
action_id: StringName,
|
||||
resource_id: StringName,
|
||||
amount: float
|
||||
) -> ResourceNode:
|
||||
var node := ResourceNode.new()
|
||||
node.name = "CarryResource"
|
||||
node.node_id = node_id
|
||||
node.action_id = action_id
|
||||
node.resource_id = resource_id
|
||||
node.initial_amount = amount
|
||||
node.yield_per_action = 2.0
|
||||
node.debug_label_enabled = false
|
||||
var interaction_point := Marker3D.new()
|
||||
interaction_point.name = "InteractionPoint"
|
||||
node.add_child(interaction_point)
|
||||
root.add_child(node)
|
||||
_check(
|
||||
manager.register_resource_node(node),
|
||||
"The carry loop should bind a real finite ResourceNode"
|
||||
)
|
||||
return node
|
||||
|
||||
|
||||
func _create_manager(seed_value: int = 901) -> Node:
|
||||
var manager: Node = load("res://simulation/SimulationManager.gd").new()
|
||||
manager.simulation_seed = seed_value
|
||||
manager.debug_logs = false
|
||||
var home_positions: Array[Vector3] = [
|
||||
Vector3(0.0, 0.0, 0.0),
|
||||
Vector3(2.0, 0.0, 0.0),
|
||||
Vector3(10.0, 0.0, 0.0),
|
||||
Vector3(12.0, 0.0, 0.0),
|
||||
Vector3(30.0, 0.0, 30.0),
|
||||
Vector3(40.0, 0.0, 40.0),
|
||||
]
|
||||
manager.home_positions = home_positions
|
||||
root.add_child(manager)
|
||||
manager.set_process(false)
|
||||
return manager
|
||||
|
||||
|
||||
func _set_pantry_amount(manager: Node, amount: float) -> void:
|
||||
var pantry: StorageStateRecord = manager.get_pantry()
|
||||
pantry.withdraw(SimulationIds.RESOURCE_FOOD, pantry.get_amount(SimulationIds.RESOURCE_FOOD))
|
||||
pantry.deposit(SimulationIds.RESOURCE_FOOD, amount)
|
||||
manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
|
||||
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
if not condition:
|
||||
failures.append(message)
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
if failures.is_empty():
|
||||
print("[TEST] Player carry and deposit loop passed")
|
||||
quit(0)
|
||||
return
|
||||
for failure in failures:
|
||||
push_error("[TEST] " + failure)
|
||||
quit(1)
|
||||
@@ -0,0 +1 @@
|
||||
uid://bwyg38d3ieiex
|
||||
@@ -35,11 +35,17 @@ func _run() -> void:
|
||||
|
||||
_check(
|
||||
is_equal_approx(bush_state.get_amount_remaining(), 0.0),
|
||||
"Player should deplete the nearby bush"
|
||||
"Player should deplete the nearby bush into their hands"
|
||||
)
|
||||
var carried_after_gather: float = simulation_manager.get_player_state().get_inventory_amount(
|
||||
SimulationIds.RESOURCE_FOOD
|
||||
)
|
||||
_check(
|
||||
is_equal_approx(simulation_manager.village.food, food_before + 1.0),
|
||||
"Village should receive exactly the bush's remaining food"
|
||||
(
|
||||
is_equal_approx(carried_after_gather, 1.0)
|
||||
and is_equal_approx(simulation_manager.village.food, food_before)
|
||||
),
|
||||
"Gathering should fill the player's hands without bypassing the village store",
|
||||
)
|
||||
_check(bush_state.get_reserved_by() == -1, "Depletion should release the NPC reservation")
|
||||
var player_extraction: EconomicEventRecord
|
||||
@@ -65,24 +71,41 @@ func _run() -> void:
|
||||
"Player depletion should preserve the resource interaction position"
|
||||
)
|
||||
|
||||
player.global_position = tree.interaction_point.global_position
|
||||
var wood_before: float = simulation_manager.village.wood
|
||||
var tree_before := tree_state.get_amount_remaining()
|
||||
player.try_interact()
|
||||
|
||||
_check(
|
||||
is_equal_approx(tree_state.get_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"
|
||||
)
|
||||
|
||||
var pantry := (
|
||||
main_scene.get_node("JajceWorld/WorldObjects/StorageSites/VillagePantry") as StorageNode
|
||||
)
|
||||
var pantry_state: StorageStateRecord = simulation_manager.get_pantry()
|
||||
player.global_position = pantry.get_interaction_position()
|
||||
player.try_interact()
|
||||
var food_after_deposit: float = simulation_manager.village.food
|
||||
_check(
|
||||
(
|
||||
is_equal_approx(food_after_deposit, food_before + 1.0)
|
||||
and is_equal_approx(
|
||||
simulation_manager.get_player_state().get_inventory_amount(
|
||||
SimulationIds.RESOURCE_FOOD
|
||||
),
|
||||
0.0
|
||||
)
|
||||
),
|
||||
"Depositing at the typed pantry should deliver the carried food to the village",
|
||||
)
|
||||
|
||||
player.global_position = tree.interaction_point.global_position
|
||||
var wood_before: float = simulation_manager.village.wood
|
||||
var tree_before := tree_state.get_amount_remaining()
|
||||
player.try_interact()
|
||||
var carried_wood: float = simulation_manager.get_player_state().get_inventory_amount(
|
||||
SimulationIds.RESOURCE_WOOD
|
||||
)
|
||||
_check(
|
||||
(
|
||||
is_equal_approx(tree_state.get_amount_remaining(), tree_before - tree.yield_per_action)
|
||||
and is_equal_approx(carried_wood, tree.yield_per_action)
|
||||
),
|
||||
"Player should extract the tree's configured yield into their hands",
|
||||
)
|
||||
|
||||
pantry_state.deposit(SimulationIds.RESOURCE_FOOD, pantry_state.get_available_capacity() - 0.5)
|
||||
simulation_manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
|
||||
bush_state.set_amount_remaining(1.0)
|
||||
@@ -90,20 +113,40 @@ func _run() -> void:
|
||||
player.try_interact()
|
||||
_check(
|
||||
(
|
||||
is_equal_approx(bush_state.get_amount_remaining(), 0.5)
|
||||
and is_equal_approx(pantry_state.get_available_capacity(), 0.0)
|
||||
is_equal_approx(bush_state.get_amount_remaining(), 0.0)
|
||||
and is_equal_approx(
|
||||
simulation_manager.get_player_state().get_inventory_amount(
|
||||
SimulationIds.RESOURCE_FOOD
|
||||
),
|
||||
1.0
|
||||
)
|
||||
),
|
||||
"Player harvesting should leave overflow at its source when storage fills"
|
||||
"Player gathering should still extract into their hands regardless of storage capacity",
|
||||
)
|
||||
|
||||
pantry_state.deposit(SimulationIds.RESOURCE_FOOD, 3.0)
|
||||
pantry_state.withdraw(
|
||||
SimulationIds.RESOURCE_FOOD, pantry_state.get_amount(SimulationIds.RESOURCE_FOOD)
|
||||
)
|
||||
pantry_state.deposit(SimulationIds.RESOURCE_FOOD, 5.0)
|
||||
simulation_manager.economy.sync_resource(SimulationIds.RESOURCE_FOOD)
|
||||
var pantry_food_before := pantry_state.get_amount(SimulationIds.RESOURCE_FOOD)
|
||||
player.global_position = pantry.get_interaction_position()
|
||||
player.try_interact()
|
||||
var food_after_first_deposit := pantry_state.get_amount(SimulationIds.RESOURCE_FOOD)
|
||||
var food_carried_after_first: float = (
|
||||
simulation_manager.get_player_state().get_inventory_amount(SimulationIds.RESOURCE_FOOD)
|
||||
)
|
||||
_check(
|
||||
pantry_state.get_amount(SimulationIds.RESOURCE_FOOD) < pantry_food_before,
|
||||
"Player should eat from the typed pantry StorageNode"
|
||||
(
|
||||
food_after_first_deposit > pantry_food_before
|
||||
and is_equal_approx(food_carried_after_first, 0.0)
|
||||
),
|
||||
"Player should deposit carried food at the typed pantry",
|
||||
)
|
||||
player.try_interact()
|
||||
_check(
|
||||
pantry_state.get_amount(SimulationIds.RESOURCE_FOOD) < food_after_first_deposit,
|
||||
"Player should eat from the pantry once their hands are empty",
|
||||
)
|
||||
var guard_site := (
|
||||
main_scene.get_node("JajceWorld/WorldObjects/ActivitySites/GuardPost") as ActivitySite
|
||||
|
||||
Reference in New Issue
Block a user