Better handling of lua tasks

This commit is contained in:
2026-05-01 04:02:47 +03:00
parent 976ced3731
commit f918c5cefb
7 changed files with 580 additions and 107 deletions

View File

@@ -4,6 +4,7 @@
#include <OgreLogManager.h>
#include <unordered_map>
#include <string>
#include <vector>
namespace editScene
{
@@ -170,7 +171,6 @@ int callLuaBehaviorTreeNode(void *L_, const std::string &nodeName,
L = g_luaState;
if (!L)
return -1;
auto it = g_luaNodeHandlers.find(nodeName);
if (it == g_luaNodeHandlers.end())
@@ -218,6 +218,66 @@ int callLuaBehaviorTreeNode(void *L_, const std::string &nodeName,
return -1;
}
// ---------------------------------------------------------------------------
// Convenience overload: call with raw entity ID (for tests)
// ---------------------------------------------------------------------------
int callLuaBehaviorTreeNode(void *L_, const std::string &nodeName,
uint64_t entityId, const std::string &params)
{
// Use the global Lua state if none was passed
lua_State *L = static_cast<lua_State *>(L_);
if (!L)
L = g_luaState;
if (!L)
return -1;
auto it = g_luaNodeHandlers.find(nodeName);
if (it == g_luaNodeHandlers.end())
return -1;
int ref = it->second;
// Push the callback function
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
// Push entity ID as first argument
lua_pushinteger(L, (lua_Integer)entityId);
// Push params table as second argument
pushParamsTable(L, params);
// Call the function
if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
Ogre::LogManager::getSingleton().stream()
<< "[Lua BT] Error calling node '" << nodeName
<< "': " << lua_tostring(L, -1);
lua_pop(L, 1);
return -1;
}
// Read the result
if (!lua_isstring(L, -1)) {
lua_pop(L, 1);
return -1;
}
const char *result = lua_tostring(L, -1);
lua_pop(L, 1);
if (strcmp(result, "success") == 0)
return 0;
if (strcmp(result, "failure") == 0)
return 1;
if (strcmp(result, "running") == 0)
return 2;
Ogre::LogManager::getSingleton().stream()
<< "[Lua BT] Node '" << nodeName
<< "' returned invalid result: " << result;
return -1;
}
// ---------------------------------------------------------------------------
// Lua: ecs.behavior_tree.register_node(name, function)
// ---------------------------------------------------------------------------
@@ -307,6 +367,19 @@ static int luaCreateNode(lua_State *L)
return 1;
}
// ---------------------------------------------------------------------------
// Public C++ API: get list of registered Lua node names
// ---------------------------------------------------------------------------
std::vector<std::string> getRegisteredLuaNodeNames()
{
std::vector<std::string> names;
names.reserve(g_luaNodeHandlers.size());
for (const auto &kv : g_luaNodeHandlers)
names.push_back(kv.first);
return names;
}
// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------

View File

@@ -3,6 +3,10 @@
#pragma once
#include <lua.hpp>
#include <vector>
#include <string>
#include <cstdint>
#include <flecs.h>
/**
* @file LuaBehaviorTreeApi.hpp
@@ -78,6 +82,45 @@ namespace editScene
*/
void registerLuaBehaviorTreeApi(lua_State *L);
/**
* @brief Get the list of registered Lua node handler names.
*
* Used by the behavior tree editor UI to show registered Lua nodes
* in a dropdown instead of requiring manual entry.
*
* @return A vector of registered node handler names.
*/
std::vector<std::string> getRegisteredLuaNodeNames();
/**
* @brief Call a registered Lua behavior tree node handler.
*
* This is the primary evaluation function used by the behavior tree system.
* It looks up the registered handler by name, pushes entity and params
* as arguments, calls the Lua function, and interprets the result.
*
* @param L_ Pointer to the Lua state (can be nullptr to use global state).
* @param nodeName The name of the registered node handler.
* @param entity The flecs entity executing this node.
* @param params The params string (key=val,key2=val2 format).
* @return 0 = success, 1 = failure, 2 = running, -1 = error/not found.
*/
int callLuaBehaviorTreeNode(void *L_, const std::string &nodeName,
flecs::entity entity, const std::string &params);
/**
* @brief Convenience overload for calling a registered Lua node handler
* with a raw entity ID (for use in tests or contexts without flecs).
*
* @param L_ Pointer to the Lua state (can be nullptr to use global state).
* @param nodeName The name of the registered node handler.
* @param entityId The raw entity ID.
* @param params The params string (key=val,key2=val2 format).
* @return 0 = success, 1 = failure, 2 = running, -1 = error/not found.
*/
int callLuaBehaviorTreeNode(void *L_, const std::string &nodeName,
uint64_t entityId, const std::string &params);
} // namespace editScene
#endif // EDITSCENE_LUA_BEHAVIOR_TREE_API_HPP

View File

@@ -35,11 +35,14 @@ extern "C" {
// Include the components we need
#include "../components/BehaviorTree.hpp"
#include "../components/GoapBlackboard.hpp"
// Forward declare the registration function
namespace editScene
{
void registerLuaBehaviorTreeApi(lua_State *L);
int callLuaBehaviorTreeNode(void *L_, const std::string &nodeName,
uint64_t entityId, const std::string &params);
}
// ---------------------------------------------------------------------------
@@ -61,10 +64,10 @@ static int passCount = 0;
printf("PASS\n"); \
} while (0)
#define FAIL(msg) \
do { \
printf("FAIL: %s\n", msg); \
return 1; \
#define FAIL(msg) \
do { \
printf("FAIL: %s\n", std::string(msg).c_str()); \
return 1; \
} while (0)
// ---------------------------------------------------------------------------
@@ -449,6 +452,185 @@ static int testRegisterInvalidArgs(lua_State *L)
return 0;
}
// ---------------------------------------------------------------------------
// Test 11: Full pipeline - register, create, and evaluate a node
// ---------------------------------------------------------------------------
// This test exercises the complete flow: register a Lua handler,
// create a node table, then call callLuaBehaviorTreeNode() to
// evaluate it and verify the return value.
static int testFullPipeline(lua_State *L)
{
TEST("full pipeline: register, create, evaluate");
// Register a node that returns "success"
bool ok = runLua(L, "ecs.behavior_tree.register_node('pipeline_test', "
" function(entity_id, params) "
" return 'success' "
" end"
")");
if (!ok)
FAIL("failed to register pipeline_test node");
// Call the C++ evaluation function
int result = editScene::callLuaBehaviorTreeNode(L, "pipeline_test", 42,
"msg=hello");
if (result != 0)
FAIL("expected 0 (success), got " + std::to_string(result));
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 12: Full pipeline - node returning "failure"
// ---------------------------------------------------------------------------
static int testFullPipelineFailure(lua_State *L)
{
TEST("full pipeline: node returning failure");
bool ok = runLua(L, "ecs.behavior_tree.register_node('fail_test', "
" function(entity_id, params) "
" return 'failure' "
" end"
")");
if (!ok)
FAIL("failed to register fail_test node");
int result = editScene::callLuaBehaviorTreeNode(L, "fail_test", 42, "");
if (result != 1)
FAIL("expected 1 (failure), got " + std::to_string(result));
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 13: Full pipeline - node returning "running"
// ---------------------------------------------------------------------------
static int testFullPipelineRunning(lua_State *L)
{
TEST("full pipeline: node returning running");
bool ok = runLua(L, "ecs.behavior_tree.register_node('run_test', "
" function(entity_id, params) "
" return 'running' "
" end"
")");
if (!ok)
FAIL("failed to register run_test node");
int result = editScene::callLuaBehaviorTreeNode(L, "run_test", 42, "");
if (result != 2)
FAIL("expected 2 (running), got " + std::to_string(result));
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 14: Full pipeline - node that reads params
// ---------------------------------------------------------------------------
static int testFullPipelineParams(lua_State *L)
{
TEST("full pipeline: node reading params");
// Register a node that stores params in a global for verification
bool ok = runLua(L, "ecs.behavior_tree.register_node('param_test', "
" function(entity_id, params) "
" param_count = params.count "
" param_msg = params.msg "
" return 'success' "
" end"
")");
if (!ok)
FAIL("failed to register param_test node");
int result = editScene::callLuaBehaviorTreeNode(L, "param_test", 42,
"count=42,msg=hello");
if (result != 0)
FAIL("expected 0 (success), got " + std::to_string(result));
// Verify params were passed correctly
lua_getglobal(L, "param_count");
if (!lua_isnumber(L, -1))
FAIL("param_count should be a number");
int count = (int)lua_tointeger(L, -1);
lua_pop(L, 1);
if (count != 42)
FAIL("expected param_count=42, got " + std::to_string(count));
lua_getglobal(L, "param_msg");
const char *msg = lua_tostring(L, -1);
lua_pop(L, 1);
if (!msg || strcmp(msg, "hello") != 0)
FAIL(std::string("expected param_msg='hello', got '") +
(msg ? msg : "nil") + "'");
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 15: Full pipeline - node that reads entity_id
// ---------------------------------------------------------------------------
static int testFullPipelineEntityId(lua_State *L)
{
TEST("full pipeline: node reading entity_id");
bool ok = runLua(L, "ecs.behavior_tree.register_node('eid_test', "
" function(entity_id, params) "
" eid_result = entity_id "
" return 'success' "
" end"
")");
if (!ok)
FAIL("failed to register eid_test node");
int result =
editScene::callLuaBehaviorTreeNode(L, "eid_test", 12345, "");
if (result != 0)
FAIL("expected 0 (success), got " + std::to_string(result));
lua_getglobal(L, "eid_result");
int entityId = (int)lua_tointeger(L, -1);
lua_pop(L, 1);
if (entityId != 12345)
FAIL("expected entity_id=12345, got " +
std::to_string(entityId));
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Test 16: Full pipeline - unregistered node returns failure
// ---------------------------------------------------------------------------
static int testFullPipelineUnregistered(lua_State *L)
{
TEST("full pipeline: unregistered node returns failure");
int result = editScene::callLuaBehaviorTreeNode(L, "nonexistent_node",
42, "");
if (result != -1)
FAIL("expected -1 (not found) for unregistered node, got " +
std::to_string(result));
PASS();
return 0;
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
@@ -481,6 +663,12 @@ int main()
failures += testReregisterNode(L);
failures += testEmptyList(L);
failures += testRegisterInvalidArgs(L);
failures += testFullPipeline(L);
failures += testFullPipelineFailure(L);
failures += testFullPipelineRunning(L);
failures += testFullPipelineParams(L);
failures += testFullPipelineEntityId(L);
failures += testFullPipelineUnregistered(L);
// Cleanup
lua_close(L);

View File

@@ -1,4 +1,5 @@
#include "BehaviorTreeEditor.hpp"
#include "../lua/LuaBehaviorTreeApi.hpp"
#include <imgui.h>
static ImU32 nodeTypeColorU32(const Ogre::String &type)
@@ -29,6 +30,8 @@ static ImU32 nodeTypeColorU32(const Ogre::String &type)
return IM_COL32(0x4C, 0xCC, 0x4C, 0xFF);
if (type == "sendEvent")
return IM_COL32(0xFF, 0x99, 0x00, 0xFF);
if (type == "luaTask")
return IM_COL32(0xAA, 0x66, 0xFF, 0xFF);
return IM_COL32(0xFF, 0xFF, 0xFF, 0xFF);
}
@@ -106,12 +109,21 @@ void BehaviorTreeEditor::applyEditOps(BehaviorTreeComponent &bt)
case TreeEditOp::AddChild: {
BehaviorTreeNode child;
child.type = op.childType;
if (op.childType == "task" || op.childType == "check")
if (op.childType == "task" || op.childType == "check" ||
op.childType == "luaTask")
child.name = "unnamed";
vec.push_back(child);
changed = true;
break;
}
case TreeEditOp::AddLuaNode: {
BehaviorTreeNode child;
child.type = "luaTask";
child.name = op.luaNodeName;
vec.push_back(child);
changed = true;
break;
}
case TreeEditOp::RemoveNode: {
if (op.childIndex < vec.size()) {
vec.erase(vec.begin() + op.childIndex);
@@ -137,6 +149,33 @@ void BehaviorTreeEditor::applyEditOps(BehaviorTreeComponent &bt)
bt.markDirty();
}
// Helper: render a submenu of registered Lua node names.
// Returns true if a node was selected (via queueAddLuaNode).
static bool
renderLuaNodeSubmenu(BehaviorTreeNode *parent,
std::vector<BehaviorTreeEditor::TreeEditOp> &editOps)
{
auto names = editScene::getRegisteredLuaNodeNames();
if (names.empty()) {
ImGui::MenuItem("(no Lua nodes registered)", nullptr, false,
false);
return false;
}
bool selected = false;
for (const auto &name : names) {
if (ImGui::MenuItem(name.c_str())) {
BehaviorTreeEditor::TreeEditOp op;
op.type = BehaviorTreeEditor::TreeEditOp::AddLuaNode;
op.targetNode = parent;
op.luaNodeName = name;
editOps.push_back(op);
selected = true;
}
}
return selected;
}
void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node,
BehaviorTreeNode *parent,
BehaviorTreeNode *&selectedNode,
@@ -208,6 +247,10 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node,
queueAddChild(&node, "enablePhysics");
if (ImGui::MenuItem("Add Send Event"))
queueAddChild(&node, "sendEvent");
if (ImGui::BeginMenu("Add Lua Task")) {
renderLuaNodeSubmenu(&node, m_editOps);
ImGui::EndMenu();
}
}
if (parent) {
size_t idx = findChildIndex(*parent, &node);
@@ -263,6 +306,10 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node,
queueAddChild(&node, "enablePhysics");
if (ImGui::MenuItem("Send Event"))
queueAddChild(&node, "sendEvent");
if (ImGui::BeginMenu("Lua Task")) {
renderLuaNodeSubmenu(&node, m_editOps);
ImGui::EndMenu();
}
ImGui::EndPopup();
}
}
@@ -317,7 +364,8 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node)
"teleportToChild",
"disablePhysics",
"enablePhysics",
"sendEvent" };
"sendEvent",
"luaTask" };
int typeIdx = 0;
for (int i = 0; i < IM_ARRAYSIZE(types); i++) {
if (node->type == types[i]) {
@@ -329,52 +377,85 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node)
node->type = types[typeIdx];
if (node->isLeaf()) {
char buf[256];
snprintf(buf, sizeof(buf), "%s", node->name.c_str());
// For luaTask nodes, show a dropdown of registered Lua nodes
if (node->type == "luaTask") {
auto names = editScene::getRegisteredLuaNodeNames();
if (names.empty()) {
ImGui::TextDisabled("No Lua nodes registered");
} else {
// Find current index
int current = -1;
for (size_t i = 0; i < names.size(); i++) {
if (names[i] == node->name) {
current = (int)i;
break;
}
}
// Build a C-string array for ImGui Combo
std::vector<const char *> cstrs;
cstrs.reserve(names.size());
for (const auto &n : names)
cstrs.push_back(n.c_str());
const char *label = "Name";
const char *hint = nullptr;
if (node->type == "debugPrint") {
label = "Message";
hint = "Text to print when node runs";
} else if (node->type == "setAnimationState") {
label = "State (SM/State)";
hint = "Format: stateMachineName/stateName";
} else if (node->type == "isAnimationEnded") {
label = "State Machine";
hint = "Name of state machine to check";
} else if (node->type == "setBit") {
label = "Bit";
hint = "Bit index or name";
} else if (node->type == "checkBit") {
label = "Bit";
hint = "Bit index or name to check";
} else if (node->type == "setValue") {
label = "Key";
hint = "Variable name";
} else if (node->type == "checkValue") {
label = "Key";
hint = "Variable name to check";
} else if (node->type == "blackboardDump") {
label = "Label";
hint = "Optional label prefix";
} else if (node->type == "teleportToChild") {
label = "Child Name";
hint = "Name of child entity under Smart Object to teleport to";
} else if (node->type == "sendEvent") {
label = "Event Name";
hint = "Name of the event to send via EventBus";
if (ImGui::Combo("Lua Node", &current,
cstrs.data(),
(int)cstrs.size())) {
if (current >= 0 &&
current < (int)names.size())
node->name = names[current];
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip(
"Select a registered Lua node handler");
}
} else {
char buf[256];
snprintf(buf, sizeof(buf), "%s", node->name.c_str());
const char *label = "Name";
const char *hint = nullptr;
if (node->type == "debugPrint") {
label = "Message";
hint = "Text to print when node runs";
} else if (node->type == "setAnimationState") {
label = "State (SM/State)";
hint = "Format: stateMachineName/stateName";
} else if (node->type == "isAnimationEnded") {
label = "State Machine";
hint = "Name of state machine to check";
} else if (node->type == "setBit") {
label = "Bit";
hint = "Bit index or name";
} else if (node->type == "checkBit") {
label = "Bit";
hint = "Bit index or name to check";
} else if (node->type == "setValue") {
label = "Key";
hint = "Variable name";
} else if (node->type == "checkValue") {
label = "Key";
hint = "Variable name to check";
} else if (node->type == "blackboardDump") {
label = "Label";
hint = "Optional label prefix";
} else if (node->type == "teleportToChild") {
label = "Child Name";
hint = "Name of child entity under Smart Object to teleport to";
} else if (node->type == "sendEvent") {
label = "Event Name";
hint = "Name of the event to send via EventBus";
}
if (ImGui::InputText(label, buf, sizeof(buf)))
node->name = buf;
if (hint && ImGui::IsItemHovered())
ImGui::SetTooltip("%s", hint);
}
if (ImGui::InputText(label, buf, sizeof(buf)))
node->name = buf;
if (hint && ImGui::IsItemHovered())
ImGui::SetTooltip("%s", hint);
// Params input for nodes that need it
if (node->type == "setBit" || node->type == "setValue" ||
node->type == "checkValue" || node->type == "delay" ||
node->type == "sendEvent") {
node->type == "sendEvent" || node->type == "luaTask") {
char pbuf[256];
snprintf(pbuf, sizeof(pbuf), "%s",
node->params.c_str());
@@ -389,6 +470,8 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node)
phint = "Duration in seconds (float, default 1.0)";
else if (node->type == "sendEvent")
phint = "Event params: key=val key2=val2 ...";
else if (node->type == "luaTask")
phint = "Parameters: key=val,key2=val2 passed to Lua function";
if (ImGui::InputText("Params", pbuf, sizeof(pbuf)))
node->params = pbuf;
if (phint && ImGui::IsItemHovered())

View File

@@ -12,18 +12,21 @@
*/
class BehaviorTreeEditor : public ComponentEditor<BehaviorTreeComponent> {
public:
const char *getName() const override { return "Behavior Tree"; }
const char *getName() const override
{
return "Behavior Tree";
}
struct TreeEditOp {
enum OpType { AddChild, RemoveNode, MoveNode } type;
enum OpType { AddChild, RemoveNode, MoveNode, AddLuaNode } type;
BehaviorTreeNode *targetNode = nullptr;
size_t childIndex = 0;
Ogre::String childType;
Ogre::String luaNodeName;
int moveDelta = 0;
};
void queueAddChild(BehaviorTreeNode *parent,
const Ogre::String &type);
void queueAddChild(BehaviorTreeNode *parent, const Ogre::String &type);
void queueRemoveNode(BehaviorTreeNode *parent, size_t childIndex);
void queueMoveNode(BehaviorTreeNode *parent, size_t childIndex,
int delta);
@@ -33,11 +36,9 @@ protected:
BehaviorTreeComponent &bt) override;
private:
void renderTree(BehaviorTreeNode &node,
BehaviorTreeNode *parent,
void renderTree(BehaviorTreeNode &node, BehaviorTreeNode *parent,
BehaviorTreeNode *&selectedNode,
BehaviorTreeNode *&selectedParent,
int &id);
BehaviorTreeNode *&selectedParent, int &id);
void renderProperties(BehaviorTreeNode *node);
void applyEditOps(BehaviorTreeComponent &bt);

View File

@@ -1,4 +1,5 @@
#include "InlineBehaviorTreeEditor.hpp"
#include "../lua/LuaBehaviorTreeApi.hpp"
#include <string.h>
std::vector<InlineBehaviorTreeEditor::EditOp> &
@@ -17,19 +18,36 @@ void InlineBehaviorTreeEditor::applyOps(BehaviorTreeNode &root)
{
for (auto &op : getOps()) {
BehaviorTreeNode *parent = op.parent ? op.parent : &root;
if (op.type == EditOp::Add && op.parent) {
if (op.childIndex <= op.parent->children.size())
switch (op.type) {
case EditOp::Add:
if (op.parent &&
op.childIndex <= op.parent->children.size())
op.parent->children.insert(
op.parent->children.begin() +
op.childIndex,
BehaviorTreeNode{ .type = op.addType });
} else if (op.type == EditOp::Remove && op.parent) {
if (op.childIndex < op.parent->children.size())
break;
case EditOp::AddLuaNode:
if (op.parent) {
BehaviorTreeNode child;
child.type = "luaTask";
child.name = op.luaNodeName;
op.parent->children.insert(
op.parent->children.begin() +
op.childIndex,
child);
}
break;
case EditOp::Remove:
if (op.parent &&
op.childIndex < op.parent->children.size())
op.parent->children.erase(
op.parent->children.begin() +
op.childIndex);
} else if (op.type == EditOp::Move && op.parent) {
if (op.childIndex < op.parent->children.size()) {
break;
case EditOp::Move:
if (op.parent &&
op.childIndex < op.parent->children.size()) {
size_t newIndex =
(int)op.childIndex + op.moveDelta;
if (newIndex >= 0 &&
@@ -47,6 +65,7 @@ void InlineBehaviorTreeEditor::applyOps(BehaviorTreeNode &root)
child);
}
}
break;
}
}
getOps().clear();
@@ -98,9 +117,36 @@ static ImVec4 typeColorVec(const char *type)
return ImVec4(0.3f, 0.8f, 0.3f, 1.0f);
if (!strcmp(type, "sendEvent"))
return ImVec4(1.0f, 0.6f, 0.0f, 1.0f);
if (!strcmp(type, "luaTask"))
return ImVec4(0.67f, 0.4f, 1.0f, 1.0f);
return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
}
// Helper: render a submenu of registered Lua node names.
// Returns true if a node was selected.
bool InlineBehaviorTreeEditor::renderLuaNodeSubmenu(BehaviorTreeNode *parent,
size_t childIndex)
{
auto names = editScene::getRegisteredLuaNodeNames();
if (names.empty()) {
ImGui::MenuItem("(no Lua nodes registered)", nullptr, false,
false);
return false;
}
bool selected = false;
for (const auto &name : names) {
if (ImGui::MenuItem(name.c_str())) {
queueOp(EditOp{ .type = EditOp::AddLuaNode,
.parent = parent,
.childIndex = childIndex,
.luaNodeName = name });
selected = true;
}
}
return selected;
}
void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
BehaviorTreeNode *parent,
BehaviorTreeNode *&selected, int &id)
@@ -153,6 +199,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node,
.childIndex = node.children.size(),
.addType = "sendEvent" });
}
if (ImGui::BeginMenu("Add Lua Task")) {
renderLuaNodeSubmenu(&node, node.children.size());
ImGui::EndMenu();
}
if (parent && ImGui::MenuItem("Remove")) {
for (size_t i = 0; i < parent->children.size(); i++) {
if (&parent->children[i] == &node) {
@@ -269,7 +319,8 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node)
"teleportToChild",
"disablePhysics",
"enablePhysics",
"sendEvent" };
"sendEvent",
"luaTask" };
int current = 0;
for (int i = 0; i < IM_ARRAYSIZE(types); i++) {
if (node->type == types[i]) {
@@ -282,52 +333,83 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node)
}
if (node->isLeaf()) {
char buf[256];
snprintf(buf, sizeof(buf), "%s", node->name.c_str());
// For luaTask nodes, show a dropdown of registered Lua nodes
if (node->type == "luaTask") {
auto names = editScene::getRegisteredLuaNodeNames();
if (names.empty()) {
ImGui::TextDisabled("No Lua nodes registered");
} else {
int currentIdx = -1;
for (size_t i = 0; i < names.size(); i++) {
if (names[i] == node->name) {
currentIdx = (int)i;
break;
}
}
std::vector<const char *> cstrs;
cstrs.reserve(names.size());
for (const auto &n : names)
cstrs.push_back(n.c_str());
const char *label = "Name";
const char *hint = nullptr;
if (node->type == "debugPrint") {
label = "Message";
hint = "Text to print when node runs";
} else if (node->type == "setAnimationState") {
label = "State (SM/State)";
hint = "Format: stateMachineName/stateName";
} else if (node->type == "isAnimationEnded") {
label = "State Machine";
hint = "Name of state machine to check";
} else if (node->type == "setBit") {
label = "Bit";
hint = "Bit index or name";
} else if (node->type == "checkBit") {
label = "Bit";
hint = "Bit index or name to check";
} else if (node->type == "setValue") {
label = "Key";
hint = "Variable name";
} else if (node->type == "checkValue") {
label = "Key";
hint = "Variable name to check";
} else if (node->type == "blackboardDump") {
label = "Label";
hint = "Optional label prefix";
} else if (node->type == "teleportToChild") {
label = "Child Name";
hint = "Name of child entity under Smart Object to teleport to";
} else if (node->type == "sendEvent") {
label = "Event Name";
hint = "Name of the event to send via EventBus";
if (ImGui::Combo("Lua Node", &currentIdx,
cstrs.data(),
(int)cstrs.size())) {
if (currentIdx >= 0 &&
currentIdx < (int)names.size())
node->name = names[currentIdx];
}
if (ImGui::IsItemHovered())
ImGui::SetTooltip(
"Select a registered Lua node handler");
}
} else {
char buf[256];
snprintf(buf, sizeof(buf), "%s", node->name.c_str());
const char *label = "Name";
const char *hint = nullptr;
if (node->type == "debugPrint") {
label = "Message";
hint = "Text to print when node runs";
} else if (node->type == "setAnimationState") {
label = "State (SM/State)";
hint = "Format: stateMachineName/stateName";
} else if (node->type == "isAnimationEnded") {
label = "State Machine";
hint = "Name of state machine to check";
} else if (node->type == "setBit") {
label = "Bit";
hint = "Bit index or name";
} else if (node->type == "checkBit") {
label = "Bit";
hint = "Bit index or name to check";
} else if (node->type == "setValue") {
label = "Key";
hint = "Variable name";
} else if (node->type == "checkValue") {
label = "Key";
hint = "Variable name to check";
} else if (node->type == "blackboardDump") {
label = "Label";
hint = "Optional label prefix";
} else if (node->type == "teleportToChild") {
label = "Child Name";
hint = "Name of child entity under Smart Object to teleport to";
} else if (node->type == "sendEvent") {
label = "Event Name";
hint = "Name of the event to send via EventBus";
}
if (ImGui::InputText(label, buf, sizeof(buf)))
node->name = buf;
if (hint && ImGui::IsItemHovered())
ImGui::SetTooltip("%s", hint);
}
if (ImGui::InputText(label, buf, sizeof(buf)))
node->name = buf;
if (hint && ImGui::IsItemHovered())
ImGui::SetTooltip("%s", hint);
// Params input for nodes that need it
if (node->type == "setBit" || node->type == "setValue" ||
node->type == "checkValue" || node->type == "delay" ||
node->type == "sendEvent") {
node->type == "sendEvent" || node->type == "luaTask") {
char pbuf[256];
snprintf(pbuf, sizeof(pbuf), "%s",
node->params.c_str());
@@ -342,6 +424,8 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node)
phint = "Duration in seconds (float, default 1.0)";
else if (node->type == "sendEvent")
phint = "Event params: key=val key2=val2 ...";
else if (node->type == "luaTask")
phint = "Parameters: key=val,key2=val2 passed to Lua function";
if (ImGui::InputText("Params", pbuf, sizeof(pbuf)))
node->params = pbuf;
if (phint && ImGui::IsItemHovered())

View File

@@ -15,20 +15,21 @@ public:
private:
struct EditOp {
enum Type { Add, Remove, Move } type;
enum Type { Add, Remove, Move, AddLuaNode } type;
BehaviorTreeNode *parent = nullptr;
size_t childIndex = 0;
Ogre::String addType;
Ogre::String luaNodeName;
int moveDelta = 0;
};
static void queueOp(EditOp op);
static void applyOps(BehaviorTreeNode &root);
static void renderNode(BehaviorTreeNode &node,
BehaviorTreeNode *parent,
BehaviorTreeNode *&selected,
int &id);
static void renderNode(BehaviorTreeNode &node, BehaviorTreeNode *parent,
BehaviorTreeNode *&selected, int &id);
static void renderProps(BehaviorTreeNode *node);
static bool renderLuaNodeSubmenu(BehaviorTreeNode *parent,
size_t childIndex);
static std::vector<EditOp> &getOps();
};