Lua behavior tree

This commit is contained in:
2026-05-01 13:54:37 +03:00
parent f918c5cefb
commit 3e7b0169d5
4 changed files with 1032 additions and 104 deletions

View File

@@ -2,17 +2,18 @@
-- Behavior Tree Lua API Examples
-- =============================================================================
-- This file demonstrates how to create custom behavior tree nodes using
-- Lua functions via the ecs.behavior_tree API.
-- Lua functions via the ecs.behavior_tree API, and how to use the
-- built-in C++ node types via ecs.behavior_tree.create_node().
--
-- The API allows you to:
-- 1. Register Lua functions as behavior tree node handlers
-- 2. Create behavior tree nodes that invoke those handlers
-- 2. Create behavior tree nodes (both Lua and built-in C++ types)
-- 3. Return "success", "failure", or "running" to control tree flow
-- 4. Pass parameters from the behavior tree editor to your Lua function
-- =============================================================================
-- =============================================================================
-- Registering Node Handlers
-- Registering Lua Node Handlers
-- =============================================================================
-- Use ecs.behavior_tree.register_node(name, function) to register a Lua
-- function as a behavior tree node handler.
@@ -30,7 +31,7 @@
-- =============================================================================
-- Example 1: Simple greeting node
-- =============================================================================
-- This node just prints a message and succeeds immediately.
-- Prints a message and succeeds immediately.
ecs.behavior_tree.register_node("say_hello", function(entity_id, params)
local message = params.message or "Hello!"
@@ -41,35 +42,30 @@ end)
-- =============================================================================
-- Example 2: Node that checks a blackboard value
-- =============================================================================
-- This node checks if a blackboard value meets a threshold.
-- It succeeds if the check passes, fails otherwise.
-- Checks if a blackboard integer value meets a minimum threshold.
-- Succeeds if value >= min, fails otherwise.
ecs.behavior_tree.register_node("check_blackboard_value", function(entity_id, params)
local key = params.key
local min_val = tonumber(params.min) or 0
if not key then
print("[BT] check_blackboard_value: no 'key' param provided")
return "failure"
end
local bb = ecs.get_component(entity_id, "GoapBlackboard")
if not bb then
print("[BT] Entity " .. entity_id .. " has no GoapBlackboard")
return "failure"
end
local value = bb.values[key]
if value == nil then
print("[BT] Blackboard has no key '" .. key .. "'")
return "failure"
end
if value >= min_val then
print("[BT] Check passed: " .. key .. " = " .. value .. " >= " .. min_val)
return "success"
else
print("[BT] Check failed: " .. key .. " = " .. value .. " < " .. min_val)
return "failure"
end
end)
@@ -77,8 +73,8 @@ end)
-- =============================================================================
-- Example 3: Node that runs over multiple frames (running state)
-- =============================================================================
-- This node waits for a specified duration, storing its progress in the
-- blackboard's floatValues. It returns "running" each frame until done.
-- Waits for a specified duration, storing progress in the blackboard's
-- floatValues. Returns "running" each frame until the duration elapses.
ecs.behavior_tree.register_node("wait_for_duration", function(entity_id, params)
local duration = tonumber(params.duration) or 1.0
@@ -89,35 +85,26 @@ ecs.behavior_tree.register_node("wait_for_duration", function(entity_id, params)
return "failure"
end
-- Get elapsed time from blackboard (or start at 0)
local elapsed = bb.floatValues[timer_key] or 0.0
-- Get delta time (requires ecs.get_delta_time to be available)
-- For now, use a fixed step approximation
local dt = 0.016 -- ~60 FPS
local dt = ecs.get_delta_time() or 0.016
elapsed = elapsed + dt
bb.floatValues[timer_key] = elapsed
-- Save updated blackboard
ecs.set_component(entity_id, "GoapBlackboard", bb)
if elapsed >= duration then
-- Done waiting - clean up timer
bb.floatValues[timer_key] = nil
ecs.set_component(entity_id, "GoapBlackboard", bb)
print("[BT] Wait complete after " .. duration .. " seconds")
return "success"
end
-- Still waiting
return "running"
end)
-- =============================================================================
-- Example 4: Node that modifies blackboard values
-- =============================================================================
-- This node adds a value to a blackboard integer value.
-- Adds a configurable amount to a blackboard integer value.
ecs.behavior_tree.register_node("add_blackboard_value", function(entity_id, params)
local key = params.key
@@ -134,15 +121,13 @@ ecs.behavior_tree.register_node("add_blackboard_value", function(entity_id, para
bb.values[key] = (bb.values[key] or 0) + amount
ecs.set_component(entity_id, "GoapBlackboard", bb)
print("[BT] Added " .. amount .. " to " .. key .. " (now " .. bb.values[key] .. ")")
return "success"
end)
-- =============================================================================
-- Example 5: Node that sets a blackboard bit
-- =============================================================================
-- This node sets or clears a named bit in the blackboard.
-- Sets or clears a named bit in the blackboard.
ecs.behavior_tree.register_node("set_blackboard_bit", function(entity_id, params)
local bit_name = params.bit
@@ -159,15 +144,13 @@ ecs.behavior_tree.register_node("set_blackboard_bit", function(entity_id, params
bb.bits[bit_name] = value
ecs.set_component(entity_id, "GoapBlackboard", bb)
print("[BT] Set bit '" .. bit_name .. "' to " .. tostring(value))
return "success"
end)
-- =============================================================================
-- Example 6: Node that checks a blackboard bit
-- =============================================================================
-- This node checks if a named bit is set to a specific value.
-- Checks if a named bit is set to a specific value.
ecs.behavior_tree.register_node("check_blackboard_bit", function(entity_id, params)
local bit_name = params.bit
@@ -193,55 +176,222 @@ end)
-- =============================================================================
-- Example 7: Random chance node
-- =============================================================================
-- This node succeeds with a configurable probability.
-- Succeeds with a configurable probability (0.0 to 1.0).
ecs.behavior_tree.register_node("random_chance", function(entity_id, params)
local probability = tonumber(params.probability) or 0.5
-- Simple pseudo-random using math.random
local roll = math.random()
if roll < probability then
print("[BT] Random chance succeeded (roll=" .. roll .. " < " .. probability .. ")")
return "success"
else
print("[BT] Random chance failed (roll=" .. roll .. " >= " .. probability .. ")")
return "failure"
end
end)
-- =============================================================================
-- Using the Nodes in Behavior Trees
-- Using Built-in Node Types via create_node()
-- =============================================================================
-- Once registered, you can use ecs.behavior_tree.create_node(name, params)
-- to create node tables for use in action behavior trees.
-- ecs.behavior_tree.create_node(type, name, params) creates a node table
-- for any built-in C++ node type. If the first argument matches a registered
-- Lua handler name, it creates a luaTask node instead.
--
-- The params string uses "key=val,key2=val2" format:
-- - Numbers (int or float) are parsed automatically
-- - Quoted strings preserve string values: msg="hello world"
-- - Unquoted non-numeric values are treated as strings
-- Built-in node types:
-- Control: sequence, selector, invert
-- Animation: setAnimationState, isAnimationEnded
-- Blackboard: setBit, checkBit, setValue, checkValue, blackboardDump
-- Timing: delay
-- Movement: teleportToChild
-- Physics: disablePhysics, enablePhysics
-- Events: sendEvent
-- Inventory: hasItem, hasItemByName, countItem, pickupItem, dropItem,
-- useItem, addItemToInventory
-- Debug: debugPrint
-- Lua: luaTask (auto-detected when name matches a registered handler)
-- =============================================================================
-- Example: Action that uses Lua behavior tree nodes
ecs.action_db.add_action("lua_demo_greet", 1,
{}, -- no preconditions
{}, -- no effects
-- =============================================================================
-- Example 8: Action using built-in animation nodes
-- =============================================================================
-- Uses setAnimationState and isAnimationEnded to play an animation and
-- wait for it to finish.
ecs.action_db.add_action("play_walk_animation", 1,
{}, -- preconditions
{}, -- effects
{ -- behavior tree
type = "sequence",
children = {
ecs.behavior_tree.create_node("say_hello", "message=Welcome to the game!"),
ecs.behavior_tree.create_node("wait_for_duration", "duration=2.0,timer_key=greet_timer"),
ecs.behavior_tree.create_node("say_hello", "message=How are you today?")
ecs.behavior_tree.create_node("setAnimationState", "locomotion/walk"),
ecs.behavior_tree.create_node("isAnimationEnded", "locomotion"),
ecs.behavior_tree.create_node("setAnimationState", "locomotion/idle")
}
}
)
-- Example: Action with conditional logic using Lua nodes
ecs.action_db.add_action("lua_demo_conditional", 2,
-- =============================================================================
-- Example 9: Action using built-in delay node
-- =============================================================================
-- Waits for a specified duration using the built-in delay node.
ecs.action_db.add_action("wait_and_greet", 1,
{},
{},
{
values = { experience = 10 }
type = "sequence",
children = {
ecs.behavior_tree.create_node("delay", "", "2.0"),
ecs.behavior_tree.create_node("say_hello", "message=Waited 2 seconds!")
}
}
)
-- =============================================================================
-- Example 10: Action using built-in blackboard nodes
-- =============================================================================
-- Sets a blackboard bit, checks it, and sets a value.
ecs.action_db.add_action("blackboard_demo", 1,
{},
{
bits = { has_sword = true },
values = { gold = 100 }
},
{
values = { experience = 20 }
type = "sequence",
children = {
ecs.behavior_tree.create_node("setBit", "has_sword", "1"),
ecs.behavior_tree.create_node("checkBit", "has_sword"),
ecs.behavior_tree.create_node("setValue", "gold", "100"),
ecs.behavior_tree.create_node("checkValue", "gold", ">= 50"),
ecs.behavior_tree.create_node("blackboardDump", "After setup")
}
}
)
-- =============================================================================
-- Example 11: Action using built-in sendEvent node
-- =============================================================================
-- Sends an event with parameters.
ecs.action_db.add_action("trigger_quest_event", 1,
{},
{},
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("sendEvent", "quest_started",
"quest_id=the_ancient_sword,quest_giver=elder"),
ecs.behavior_tree.create_node("debugPrint", "Quest event sent")
}
}
)
-- =============================================================================
-- Example 12: Action using built-in physics nodes
-- =============================================================================
-- Disables physics, waits, then re-enables.
ecs.action_db.add_action("physics_control", 1,
{},
{},
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("disablePhysics"),
ecs.behavior_tree.create_node("delay", "", "1.0"),
ecs.behavior_tree.create_node("enablePhysics")
}
}
)
-- =============================================================================
-- Example 13: Action using built-in inventory nodes
-- =============================================================================
-- Adds an item to inventory, checks for it, uses it, then drops it.
ecs.action_db.add_action("inventory_demo", 1,
{},
{},
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("addItemToInventory", "potion_01",
"Health Potion,misc,1,0.5,10"),
ecs.behavior_tree.create_node("hasItem", "potion_01"),
ecs.behavior_tree.create_node("countItem", "potion_01", "1"),
ecs.behavior_tree.create_node("useItem", "potion_01"),
ecs.behavior_tree.create_node("dropItem", "potion_01", "1")
}
}
)
-- =============================================================================
-- Example 14: Action using built-in teleport node
-- =============================================================================
-- Teleports the entity to a named child transform.
ecs.action_db.add_action("teleport_to_entrance", 1,
{},
{},
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("teleportToChild", "entrance"),
ecs.behavior_tree.create_node("debugPrint", "Teleported to entrance")
}
}
)
-- =============================================================================
-- Example 15: Complex action mixing Lua and built-in nodes
-- =============================================================================
-- A selector that first tries to use a sword, and if not available,
-- picks one up and equips it.
ecs.action_db.add_action("equip_sword", 2,
{},
{
bits = { has_sword = true }
},
{
type = "selector",
children = {
-- Try to use existing sword
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("checkBit", "has_sword"),
ecs.behavior_tree.create_node("debugPrint", "Already have a sword")
}
},
-- Pick up and equip a sword
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("setAnimationState", "locomotion/walk"),
ecs.behavior_tree.create_node("delay", "", "3.0"),
ecs.behavior_tree.create_node("addItemToInventory", "sword_01",
"Iron Sword,weapon,1,2.5,50"),
ecs.behavior_tree.create_node("setBit", "has_sword", "1"),
ecs.behavior_tree.create_node("setAnimationState", "locomotion/idle"),
ecs.behavior_tree.create_node("debugPrint", "Picked up the sword!")
}
}
}
}
)
-- =============================================================================
-- Example 16: Action with conditional logic using Lua nodes
-- =============================================================================
-- Uses Lua-registered nodes for blackboard checks and modifications.
ecs.action_db.add_action("gain_experience", 2,
{
values = { experience = 0 }
},
{
values = { experience = 15 }
},
{
type = "selector",
@@ -249,24 +399,33 @@ ecs.action_db.add_action("lua_demo_conditional", 2,
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("check_blackboard_value", "key=experience,min=50"),
ecs.behavior_tree.create_node("say_hello", "message=You are experienced!"),
ecs.behavior_tree.create_node("add_blackboard_value", "key=experience,amount=10")
ecs.behavior_tree.create_node("check_blackboard_value",
"key=experience,min=50"),
ecs.behavior_tree.create_node("say_hello",
"message=You are experienced!"),
ecs.behavior_tree.create_node("add_blackboard_value",
"key=experience,amount=10")
}
},
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("say_hello", "message=You are still learning..."),
ecs.behavior_tree.create_node("add_blackboard_value", "key=experience,amount=5")
ecs.behavior_tree.create_node("say_hello",
"message=You are still learning..."),
ecs.behavior_tree.create_node("add_blackboard_value",
"key=experience,amount=5")
}
}
}
}
)
-- Example: Action with random outcomes
ecs.action_db.add_action("lua_demo_gamble", 3,
-- =============================================================================
-- Example 17: Action with random outcomes
-- =============================================================================
-- Uses the Lua random_chance node for probabilistic behavior.
ecs.action_db.add_action("try_gamble", 3,
{},
{
values = { gold = 10 }
@@ -281,6 +440,45 @@ ecs.action_db.add_action("lua_demo_gamble", 3,
}
)
-- =============================================================================
-- Example 18: Action with running-state Lua node
-- =============================================================================
-- Uses the wait_for_duration Lua node which returns "running" each frame.
ecs.action_db.add_action("wait_and_continue", 1,
{},
{},
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("wait_for_duration",
"duration=2.5,timer_key=my_timer"),
ecs.behavior_tree.create_node("say_hello", "message=Done waiting!")
}
}
)
-- =============================================================================
-- Example 19: Action with blackboard bit operations via Lua nodes
-- =============================================================================
ecs.action_db.add_action("toggle_flag", 1,
{},
{
bits = { quest_complete = true }
},
{
type = "sequence",
children = {
ecs.behavior_tree.create_node("set_blackboard_bit",
"bit=quest_complete,value=1"),
ecs.behavior_tree.create_node("check_blackboard_bit",
"bit=quest_complete,value=1"),
ecs.behavior_tree.create_node("debugPrint", "Quest flag set and verified")
}
}
)
-- =============================================================================
-- Managing Registered Nodes
-- =============================================================================

View File

@@ -343,25 +343,75 @@ static int luaListNodeHandlers(lua_State *L)
}
// ---------------------------------------------------------------------------
// Lua: ecs.behavior_tree.create_node(name, params) -> table
// Lua: ecs.behavior_tree.create_node(type, name, params) -> table
// ecs.behavior_tree.create_node(type, params) -> table
// ecs.behavior_tree.create_node(luaHandlerName, params) -> table (backward compat)
// ---------------------------------------------------------------------------
// Creates a behavior tree node table suitable for use in action behavior trees.
//
// If the first argument matches a registered Lua node handler name, it creates
// a luaTask node (backward compatible behavior).
// Otherwise, the first argument is treated as the node type string.
//
// Examples:
// ecs.behavior_tree.create_node("setAnimationState", "locomotion/walk")
// -> { type = "setAnimationState", name = "locomotion/walk" }
//
// ecs.behavior_tree.create_node("delay", "2.0")
// -> { type = "delay", params = "2.0" }
//
// ecs.behavior_tree.create_node("delay", "", "2.0")
// -> { type = "delay", params = "2.0" }
//
// ecs.behavior_tree.create_node("say_hello", "message=Hi")
// -> { type = "luaTask", name = "say_hello", params = "message=Hi" }
static int luaCreateNode(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
const char *params = luaL_optstring(L, 2, "");
const char *arg1 = luaL_checkstring(L, 1);
// Check if arg1 is a registered Lua node handler name (backward compat)
bool isLuaHandler = g_luaNodeHandlers.find(arg1) !=
g_luaNodeHandlers.end();
// Save arg2 and arg3 before pushing the result table (which shifts indices)
const char *arg2 = NULL;
const char *arg3 = NULL;
if (lua_gettop(L) >= 2 && lua_isstring(L, 2))
arg2 = lua_tostring(L, 2);
if (lua_gettop(L) >= 3 && lua_isstring(L, 3))
arg3 = lua_tostring(L, 3);
lua_newtable(L);
lua_pushstring(L, "luaTask");
lua_setfield(L, -2, "type");
if (isLuaHandler) {
// Backward compatible: create a luaTask node
lua_pushstring(L, "luaTask");
lua_setfield(L, -2, "type");
lua_pushstring(L, name);
lua_setfield(L, -2, "name");
lua_pushstring(L, arg1);
lua_setfield(L, -2, "name");
if (params && params[0]) {
lua_pushstring(L, params);
lua_setfield(L, -2, "params");
if (arg2 && arg2[0]) {
lua_pushstring(L, arg2);
lua_setfield(L, -2, "params");
}
} else {
// arg1 is the node type
lua_pushstring(L, arg1);
lua_setfield(L, -2, "type");
// arg2 is either name or params depending on the node type
if (arg2 && arg2[0]) {
lua_pushstring(L, arg2);
lua_setfield(L, -2, "name");
}
// arg3 is params (optional)
if (arg3 && arg3[0]) {
lua_pushstring(L, arg3);
lua_setfield(L, -2, "params");
}
}
return 1;

View File

@@ -28,29 +28,52 @@
* ecs.behavior_tree.list_nodes() -> table of registered node names
* Returns an array of all registered node handler names.
*
* ecs.behavior_tree.create_node(name, params) -> table
* ecs.behavior_tree.create_node(type, name, params) -> table
* Creates a behavior tree node table suitable for use in
* ecs.action_db.add_action() behavior trees.
* Returns { type = "luaTask", name = name, params = params }
*
* If the first argument matches a registered Lua node handler name,
* it creates a luaTask node (backward compatible).
* Otherwise, the first argument is the node type string.
*
* Examples:
* ecs.behavior_tree.create_node("setAnimationState", "locomotion/walk")
* -> { type = "setAnimationState", name = "locomotion/walk" }
*
* ecs.behavior_tree.create_node("delay", "", "2.0")
* -> { type = "delay", params = "2.0" }
*
* ecs.behavior_tree.create_node("say_hello", "message=Hi")
* -> { type = "luaTask", name = "say_hello", params = "message=Hi" }
*
* Supported node types (see BehaviorTree.hpp for full list):
* sequence, selector, invert, task, check, debugPrint,
* setAnimationState, isAnimationEnded, setBit, checkBit,
* setValue, checkValue, blackboardDump, delay, teleportToChild,
* disablePhysics, enablePhysics, sendEvent, hasItem, hasItemByName,
* countItem, pickupItem, dropItem, useItem, addItemToInventory,
* luaTask
*
* Example:
* -- Register a node handler
* -- Register a Lua node handler
* ecs.behavior_tree.register_node("say_hello", function(entity_id, params)
* print("Hello from entity " .. entity_id .. "! " .. (params.message or ""))
* return "success"
* end)
*
* -- Use it in an action's behavior tree
* -- Build a behavior tree using both built-in C++ nodes and Lua nodes
* ecs.action_db.add_action("greet", 1, {}, {}, {
* type = "sequence",
* children = {
* ecs.behavior_tree.create_node("setAnimationState", "locomotion/walk"),
* ecs.behavior_tree.create_node("delay", "", "2.0"),
* ecs.behavior_tree.create_node("setAnimationState", "locomotion/idle"),
* ecs.behavior_tree.create_node("say_hello", "message=Hello World")
* }
* })
*
* -- A node that runs for a while:
* -- A Lua node that runs for a while:
* ecs.behavior_tree.register_node("wait_random", function(entity_id, params)
* -- Use the blackboard to store state across frames
* local bb = ecs.get_component(entity_id, "GoapBlackboard")
* if not bb then return "failure" end
*

View File

@@ -270,7 +270,13 @@ static int testCreateNodeNoParams(lua_State *L)
{
TEST("create_node without params");
bool ok = runLua(
// Register a Lua handler first so create_node creates a luaTask node
bool ok = runLua(L, "ecs.behavior_tree.register_node('simple_node', "
" function(e, p) return 'success' end)\n");
if (!ok)
FAIL("failed to register simple_node handler");
ok = runLua(
L, "local node = ecs.behavior_tree.create_node('simple_node')\n"
"no_params_type = node.type\n"
"no_params_name = node.name\n"
@@ -312,26 +318,34 @@ static int testCreateNodeInTree(lua_State *L)
{
TEST("create_node used in behavior tree structure");
bool ok = runLua(
L, "local tree = {\n"
" type = 'sequence',\n"
" children = {\n"
" ecs.behavior_tree.create_node('say_hello', "
" 'message=Hello'),\n"
" ecs.behavior_tree.create_node('wait_for_duration', "
" 'duration=2.0,timer_key=test'),\n"
" ecs.behavior_tree.create_node('say_hello', "
" 'message=Done')\n"
" }\n"
"}\n"
"tree_type = tree.type\n"
"child_count = #tree.children\n"
"child0_type = tree.children[1].type\n"
"child0_name = tree.children[1].name\n"
"child0_params = tree.children[1].params\n"
"child1_type = tree.children[2].type\n"
"child1_name = tree.children[2].name\n"
"child1_params = tree.children[2].params\n");
// Register Lua handlers first so create_node creates luaTask nodes
bool ok = runLua(L,
"ecs.behavior_tree.register_node('say_hello', "
" function(e, p) return 'success' end)\n"
"ecs.behavior_tree.register_node('wait_for_duration', "
" function(e, p) return 'success' end)\n");
if (!ok)
FAIL("failed to register Lua handlers");
ok = runLua(L, "local tree = {\n"
" type = 'sequence',\n"
" children = {\n"
" ecs.behavior_tree.create_node('say_hello', "
" 'message=Hello'),\n"
" ecs.behavior_tree.create_node('wait_for_duration', "
" 'duration=2.0,timer_key=test'),\n"
" ecs.behavior_tree.create_node('say_hello', "
" 'message=Done')\n"
" }\n"
"}\n"
"tree_type = tree.type\n"
"child_count = #tree.children\n"
"child0_type = tree.children[1].type\n"
"child0_name = tree.children[1].name\n"
"child0_params = tree.children[1].params\n"
"child1_type = tree.children[2].type\n"
"child1_name = tree.children[2].name\n"
"child1_params = tree.children[2].params\n");
if (!ok)
FAIL("failed to create tree with Lua nodes");
@@ -408,13 +422,17 @@ static int testEmptyList(lua_State *L)
{
TEST("list_nodes returns empty when no nodes");
// Unregister all nodes we created
bool ok = runLua(L, "ecs.behavior_tree.unregister_node('test_node')\n"
"ecs.behavior_tree.unregister_node('node_b')\n"
"ecs.behavior_tree.unregister_node('node_c')\n"
"ecs.behavior_tree.unregister_node('replaceable')\n"
"local nodes = ecs.behavior_tree.list_nodes()\n"
"empty_count = #nodes\n");
// Unregister all nodes we created (including those from tests 6 and 7)
bool ok = runLua(
L, "ecs.behavior_tree.unregister_node('test_node')\n"
"ecs.behavior_tree.unregister_node('node_b')\n"
"ecs.behavior_tree.unregister_node('node_c')\n"
"ecs.behavior_tree.unregister_node('replaceable')\n"
"ecs.behavior_tree.unregister_node('simple_node')\n"
"ecs.behavior_tree.unregister_node('say_hello')\n"
"ecs.behavior_tree.unregister_node('wait_for_duration')\n"
"local nodes = ecs.behavior_tree.list_nodes()\n"
"empty_count = #nodes\n");
if (!ok)
FAIL("failed to unregister all nodes");
@@ -631,6 +649,625 @@ static int testFullPipelineUnregistered(lua_State *L)
return 0;
}
// ---------------------------------------------------------------------------
// Test 17: create_node with built-in node type (setAnimationState)
// ---------------------------------------------------------------------------
static int testCreateNodeBuiltinType(lua_State *L)
{
TEST("create_node with built-in setAnimationState type");
bool ok = runLua(
L,
"local node = ecs.behavior_tree.create_node('setAnimationState', "
" 'locomotion/walk')\n"
"anim_type = node.type\n"
"anim_name = node.name\n"
"anim_has_params = (node.params ~= nil)\n");
if (!ok)
FAIL("failed to create setAnimationState node");
std::string type = getGlobalString(L, "anim_type");
std::string name = getGlobalString(L, "anim_name");
if (type != "setAnimationState")
FAIL("expected type 'setAnimationState', got '" + type + "'");
if (name != "locomotion/walk")
FAIL("expected name 'locomotion/walk', got '" + name + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 18: create_node with built-in delay type and params
// ---------------------------------------------------------------------------
static int testCreateNodeDelay(lua_State *L)
{
TEST("create_node with built-in delay type and params");
bool ok = runLua(
L,
"local node = ecs.behavior_tree.create_node('delay', '', '2.0')\n"
"delay_type = node.type\n"
"delay_params = node.params\n"
"delay_has_name = (node.name ~= nil and node.name ~= '')\n");
if (!ok)
FAIL("failed to create delay node");
std::string type = getGlobalString(L, "delay_type");
std::string params = getGlobalString(L, "delay_params");
if (type != "delay")
FAIL("expected type 'delay', got '" + type + "'");
if (params != "2.0")
FAIL("expected params '2.0', got '" + params + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 19: create_node with built-in isAnimationEnded type
// ---------------------------------------------------------------------------
static int testCreateNodeAnimEnded(lua_State *L)
{
TEST("create_node with built-in isAnimationEnded type");
bool ok = runLua(
L,
"local node = ecs.behavior_tree.create_node('isAnimationEnded', "
" 'locomotion')\n"
"anim_end_type = node.type\n"
"anim_end_name = node.name\n");
if (!ok)
FAIL("failed to create isAnimationEnded node");
std::string type = getGlobalString(L, "anim_end_type");
std::string name = getGlobalString(L, "anim_end_name");
if (type != "isAnimationEnded")
FAIL("expected type 'isAnimationEnded', got '" + type + "'");
if (name != "locomotion")
FAIL("expected name 'locomotion', got '" + name + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 20: create_node with built-in debugPrint type
// ---------------------------------------------------------------------------
static int testCreateNodeDebugPrint(lua_State *L)
{
TEST("create_node with built-in debugPrint type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('debugPrint', "
" 'Hello World')\n"
"dbg_type = node.type\n"
"dbg_name = node.name\n");
if (!ok)
FAIL("failed to create debugPrint node");
std::string type = getGlobalString(L, "dbg_type");
std::string name = getGlobalString(L, "dbg_name");
if (type != "debugPrint")
FAIL("expected type 'debugPrint', got '" + type + "'");
if (name != "Hello World")
FAIL("expected name 'Hello World', got '" + name + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 21: create_node with built-in sendEvent type
// ---------------------------------------------------------------------------
static int testCreateNodeSendEvent(lua_State *L)
{
TEST("create_node with built-in sendEvent type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('sendEvent', "
" 'quest_started', 'quest_id=42')\n"
"ev_type = node.type\n"
"ev_name = node.name\n"
"ev_params = node.params\n");
if (!ok)
FAIL("failed to create sendEvent node");
std::string type = getGlobalString(L, "ev_type");
std::string name = getGlobalString(L, "ev_name");
std::string params = getGlobalString(L, "ev_params");
if (type != "sendEvent")
FAIL("expected type 'sendEvent', got '" + type + "'");
if (name != "quest_started")
FAIL("expected name 'quest_started', got '" + name + "'");
if (params != "quest_id=42")
FAIL("expected params 'quest_id=42', got '" + params + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 22: create_node with built-in setBit type
// ---------------------------------------------------------------------------
static int testCreateNodeSetBit(lua_State *L)
{
TEST("create_node with built-in setBit type");
bool ok = runLua(L,
"local node = ecs.behavior_tree.create_node('setBit', "
" 'has_sword', '1')\n"
"bit_type = node.type\n"
"bit_name = node.name\n"
"bit_params = node.params\n");
if (!ok)
FAIL("failed to create setBit node");
std::string type = getGlobalString(L, "bit_type");
std::string name = getGlobalString(L, "bit_name");
std::string params = getGlobalString(L, "bit_params");
if (type != "setBit")
FAIL("expected type 'setBit', got '" + type + "'");
if (name != "has_sword")
FAIL("expected name 'has_sword', got '" + name + "'");
if (params != "1")
FAIL("expected params '1', got '" + params + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 23: create_node with built-in checkBit type
// ---------------------------------------------------------------------------
static int testCreateNodeCheckBit(lua_State *L)
{
TEST("create_node with built-in checkBit type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('checkBit', "
" 'has_sword')\n"
"cbit_type = node.type\n"
"cbit_name = node.name\n");
if (!ok)
FAIL("failed to create checkBit node");
std::string type = getGlobalString(L, "cbit_type");
std::string name = getGlobalString(L, "cbit_name");
if (type != "checkBit")
FAIL("expected type 'checkBit', got '" + type + "'");
if (name != "has_sword")
FAIL("expected name 'has_sword', got '" + name + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 24: create_node with built-in setValue type
// ---------------------------------------------------------------------------
static int testCreateNodeSetValue(lua_State *L)
{
TEST("create_node with built-in setValue type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('setValue', "
" 'gold', '42')\n"
"sv_type = node.type\n"
"sv_name = node.name\n"
"sv_params = node.params\n");
if (!ok)
FAIL("failed to create setValue node");
std::string type = getGlobalString(L, "sv_type");
std::string name = getGlobalString(L, "sv_name");
std::string params = getGlobalString(L, "sv_params");
if (type != "setValue")
FAIL("expected type 'setValue', got '" + type + "'");
if (name != "gold")
FAIL("expected name 'gold', got '" + name + "'");
if (params != "42")
FAIL("expected params '42', got '" + params + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 25: create_node with built-in checkValue type
// ---------------------------------------------------------------------------
static int testCreateNodeCheckValue(lua_State *L)
{
TEST("create_node with built-in checkValue type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('checkValue', "
" 'gold', '> 10')\n"
"cv_type = node.type\n"
"cv_name = node.name\n"
"cv_params = node.params\n");
if (!ok)
FAIL("failed to create checkValue node");
std::string type = getGlobalString(L, "cv_type");
std::string name = getGlobalString(L, "cv_name");
std::string params = getGlobalString(L, "cv_params");
if (type != "checkValue")
FAIL("expected type 'checkValue', got '" + type + "'");
if (name != "gold")
FAIL("expected name 'gold', got '" + name + "'");
if (params != "> 10")
FAIL("expected params '> 10', got '" + params + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 26: create_node with built-in teleportToChild type
// ---------------------------------------------------------------------------
static int testCreateNodeTeleport(lua_State *L)
{
TEST("create_node with built-in teleportToChild type");
bool ok = runLua(
L,
"local node = ecs.behavior_tree.create_node('teleportToChild', "
" 'entrance')\n"
"tel_type = node.type\n"
"tel_name = node.name\n");
if (!ok)
FAIL("failed to create teleportToChild node");
std::string type = getGlobalString(L, "tel_type");
std::string name = getGlobalString(L, "tel_name");
if (type != "teleportToChild")
FAIL("expected type 'teleportToChild', got '" + type + "'");
if (name != "entrance")
FAIL("expected name 'entrance', got '" + name + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 27: create_node with built-in disablePhysics type
// ---------------------------------------------------------------------------
static int testCreateNodeDisablePhysics(lua_State *L)
{
TEST("create_node with built-in disablePhysics type");
bool ok = runLua(
L,
"local node = ecs.behavior_tree.create_node('disablePhysics')\n"
"dphys_type = node.type\n");
if (!ok)
FAIL("failed to create disablePhysics node");
std::string type = getGlobalString(L, "dphys_type");
if (type != "disablePhysics")
FAIL("expected type 'disablePhysics', got '" + type + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 28: create_node with built-in enablePhysics type
// ---------------------------------------------------------------------------
static int testCreateNodeEnablePhysics(lua_State *L)
{
TEST("create_node with built-in enablePhysics type");
bool ok = runLua(
L,
"local node = ecs.behavior_tree.create_node('enablePhysics')\n"
"ephys_type = node.type\n");
if (!ok)
FAIL("failed to create enablePhysics node");
std::string type = getGlobalString(L, "ephys_type");
if (type != "enablePhysics")
FAIL("expected type 'enablePhysics', got '" + type + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 29: create_node with built-in blackboardDump type
// ---------------------------------------------------------------------------
static int testCreateNodeBlackboardDump(lua_State *L)
{
TEST("create_node with built-in blackboardDump type");
bool ok = runLua(
L,
"local node = ecs.behavior_tree.create_node('blackboardDump', "
" 'Debug')\n"
"bbdump_type = node.type\n"
"bbdump_name = node.name\n");
if (!ok)
FAIL("failed to create blackboardDump node");
std::string type = getGlobalString(L, "bbdump_type");
std::string name = getGlobalString(L, "bbdump_name");
if (type != "blackboardDump")
FAIL("expected type 'blackboardDump', got '" + type + "'");
if (name != "Debug")
FAIL("expected name 'Debug', got '" + name + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 30: create_node with built-in hasItem type
// ---------------------------------------------------------------------------
static int testCreateNodeHasItem(lua_State *L)
{
TEST("create_node with built-in hasItem type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('hasItem', "
" 'sword_01')\n"
"hi_type = node.type\n"
"hi_name = node.name\n");
if (!ok)
FAIL("failed to create hasItem node");
std::string type = getGlobalString(L, "hi_type");
std::string name = getGlobalString(L, "hi_name");
if (type != "hasItem")
FAIL("expected type 'hasItem', got '" + type + "'");
if (name != "sword_01")
FAIL("expected name 'sword_01', got '" + name + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 31: create_node with built-in pickupItem type
// ---------------------------------------------------------------------------
static int testCreateNodePickupItem(lua_State *L)
{
TEST("create_node with built-in pickupItem type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('pickupItem', "
" 'sword_01')\n"
"pi_type = node.type\n"
"pi_name = node.name\n");
if (!ok)
FAIL("failed to create pickupItem node");
std::string type = getGlobalString(L, "pi_type");
std::string name = getGlobalString(L, "pi_name");
if (type != "pickupItem")
FAIL("expected type 'pickupItem', got '" + type + "'");
if (name != "sword_01")
FAIL("expected name 'sword_01', got '" + name + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 32: create_node with built-in addItemToInventory type
// ---------------------------------------------------------------------------
static int testCreateNodeAddItem(lua_State *L)
{
TEST("create_node with built-in addItemToInventory type");
bool ok = runLua(
L,
"local node = ecs.behavior_tree.create_node('addItemToInventory', "
" 'potion_01', 'Potion of Healing,misc,1,0.5,10')\n"
"ai_type = node.type\n"
"ai_name = node.name\n"
"ai_params = node.params\n");
if (!ok)
FAIL("failed to create addItemToInventory node");
std::string type = getGlobalString(L, "ai_type");
std::string name = getGlobalString(L, "ai_name");
std::string params = getGlobalString(L, "ai_params");
if (type != "addItemToInventory")
FAIL("expected type 'addItemToInventory', got '" + type + "'");
if (name != "potion_01")
FAIL("expected name 'potion_01', got '" + name + "'");
if (params != "Potion of Healing,misc,1,0.5,10")
FAIL("expected params 'Potion of Healing,misc,1,0.5,10', got '" +
params + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 33: create_node with built-in sequence type (composite node)
// ---------------------------------------------------------------------------
static int testCreateNodeSequence(lua_State *L)
{
TEST("create_node with built-in sequence type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('sequence')\n"
"seq_type = node.type\n");
if (!ok)
FAIL("failed to create sequence node");
std::string type = getGlobalString(L, "seq_type");
if (type != "sequence")
FAIL("expected type 'sequence', got '" + type + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 34: create_node with built-in selector type (composite node)
// ---------------------------------------------------------------------------
static int testCreateNodeSelector(lua_State *L)
{
TEST("create_node with built-in selector type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('selector')\n"
"sel_type = node.type\n");
if (!ok)
FAIL("failed to create selector node");
std::string type = getGlobalString(L, "sel_type");
if (type != "selector")
FAIL("expected type 'selector', got '" + type + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 35: create_node with built-in invert type (decorator node)
// ---------------------------------------------------------------------------
static int testCreateNodeInvert(lua_State *L)
{
TEST("create_node with built-in invert type");
bool ok = runLua(
L, "local node = ecs.behavior_tree.create_node('invert')\n"
"inv_type = node.type\n");
if (!ok)
FAIL("failed to create invert node");
std::string type = getGlobalString(L, "inv_type");
if (type != "invert")
FAIL("expected type 'invert', got '" + type + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 36: create_node with built-in nodes in a full behavior tree
// ---------------------------------------------------------------------------
static int testCreateNodeFullTree(lua_State *L)
{
TEST("create_node with built-in nodes in a full behavior tree");
bool ok = runLua(
L,
"local tree = {\n"
" type = 'selector',\n"
" children = {\n"
" {\n"
" type = 'sequence',\n"
" children = {\n"
" ecs.behavior_tree.create_node('checkBit', 'has_sword'),\n"
" ecs.behavior_tree.create_node('debugPrint', 'Already have sword'),\n"
" ecs.behavior_tree.create_node('setAnimationState', 'locomotion/idle')\n"
" }\n"
" },\n"
" {\n"
" type = 'sequence',\n"
" children = {\n"
" ecs.behavior_tree.create_node('setAnimationState', 'locomotion/walk'),\n"
" ecs.behavior_tree.create_node('delay', '', '2.0'),\n"
" ecs.behavior_tree.create_node('addItemToInventory', 'sword_01', "
"'Iron Sword,weapon,1,2.5,50'),\n"
" ecs.behavior_tree.create_node('setAnimationState', 'locomotion/idle'),\n"
" ecs.behavior_tree.create_node('debugPrint', 'Got the sword!')\n"
" }\n"
" }\n"
" }\n"
"}\n"
"full_tree_type = tree.type\n"
"full_tree_children = #tree.children\n"
"full_c0_type = tree.children[1].type\n"
"full_c0_children = #tree.children[1].children\n"
"full_c0_c0_type = tree.children[1].children[1].type\n"
"full_c0_c0_name = tree.children[1].children[1].name\n"
"full_c1_type = tree.children[2].type\n"
"full_c1_children = #tree.children[2].children\n"
"full_c1_c2_type = tree.children[2].children[3].type\n"
"full_c1_c2_name = tree.children[2].children[3].name\n");
if (!ok)
FAIL("failed to create full behavior tree");
std::string treeType = getGlobalString(L, "full_tree_type");
int childCount = getGlobalInt(L, "full_tree_children");
if (treeType != "selector")
FAIL("expected tree type 'selector'");
if (childCount != 2)
FAIL("expected 2 children");
std::string c0type = getGlobalString(L, "full_c0_type");
int c0children = getGlobalInt(L, "full_c0_children");
if (c0type != "sequence")
FAIL("expected child 0 type 'sequence'");
if (c0children != 3)
FAIL("expected child 0 to have 3 children");
std::string c0c0type = getGlobalString(L, "full_c0_c0_type");
std::string c0c0name = getGlobalString(L, "full_c0_c0_name");
if (c0c0type != "checkBit")
FAIL("expected child 0.0 type 'checkBit'");
if (c0c0name != "has_sword")
FAIL("expected child 0.0 name 'has_sword'");
std::string c1type = getGlobalString(L, "full_c1_type");
int c1children = getGlobalInt(L, "full_c1_children");
if (c1type != "sequence")
FAIL("expected child 1 type 'sequence'");
if (c1children != 5)
FAIL("expected child 1 to have 5 children");
std::string c1c2type = getGlobalString(L, "full_c1_c2_type");
std::string c1c2name = getGlobalString(L, "full_c1_c2_name");
if (c1c2type != "addItemToInventory")
FAIL("expected child 1.2 type 'addItemToInventory'");
if (c1c2name != "sword_01")
FAIL("expected child 1.2 name 'sword_01'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
@@ -669,6 +1306,26 @@ int main()
failures += testFullPipelineParams(L);
failures += testFullPipelineEntityId(L);
failures += testFullPipelineUnregistered(L);
failures += testCreateNodeBuiltinType(L);
failures += testCreateNodeDelay(L);
failures += testCreateNodeAnimEnded(L);
failures += testCreateNodeDebugPrint(L);
failures += testCreateNodeSendEvent(L);
failures += testCreateNodeSetBit(L);
failures += testCreateNodeCheckBit(L);
failures += testCreateNodeSetValue(L);
failures += testCreateNodeCheckValue(L);
failures += testCreateNodeTeleport(L);
failures += testCreateNodeDisablePhysics(L);
failures += testCreateNodeEnablePhysics(L);
failures += testCreateNodeBlackboardDump(L);
failures += testCreateNodeHasItem(L);
failures += testCreateNodePickupItem(L);
failures += testCreateNodeAddItem(L);
failures += testCreateNodeSequence(L);
failures += testCreateNodeSelector(L);
failures += testCreateNodeInvert(L);
failures += testCreateNodeFullTree(L);
// Cleanup
lua_close(L);