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)