Better handling of lua tasks
This commit is contained in:
@@ -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 ¶ms)
|
||||
{
|
||||
// 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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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 ¶ms);
|
||||
|
||||
/**
|
||||
* @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 ¶ms);
|
||||
|
||||
} // namespace editScene
|
||||
|
||||
#endif // EDITSCENE_LUA_BEHAVIOR_TREE_API_HPP
|
||||
|
||||
@@ -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 ¶ms);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -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);
|
||||
|
||||
@@ -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", ¤t,
|
||||
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())
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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", ¤tIdx,
|
||||
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())
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user