Lua behavior tree
This commit is contained in:
@@ -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
|
||||
-- =============================================================================
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user