diff --git a/src/features/editScene/components/BehaviorTree.hpp b/src/features/editScene/components/BehaviorTree.hpp index 5f5145a..abfd1c4 100644 --- a/src/features/editScene/components/BehaviorTree.hpp +++ b/src/features/editScene/components/BehaviorTree.hpp @@ -22,6 +22,7 @@ * "setValue" - Leaf: sets blackboard value (name=key, params=val) * "checkValue" - Leaf check: blackboard comparison (name=key, params="op val") * "blackboardDump" - Leaf: dumps entire blackboard to log + * "delay" - Leaf: waits for N seconds (params=seconds as float) */ struct BehaviorTreeNode { Ogre::String type = "task"; @@ -61,7 +62,8 @@ struct BehaviorTreeNode { type == "debugPrint" || type == "setAnimationState" || type == "isAnimationEnded" || type == "setBit" || type == "checkBit" || type == "setValue" || - type == "checkValue" || type == "blackboardDump"; + type == "checkValue" || type == "blackboardDump" || + type == "delay"; } }; diff --git a/src/features/editScene/systems/BehaviorTreeSystem.cpp b/src/features/editScene/systems/BehaviorTreeSystem.cpp index 677df66..5b40045 100644 --- a/src/features/editScene/systems/BehaviorTreeSystem.cpp +++ b/src/features/editScene/systems/BehaviorTreeSystem.cpp @@ -29,8 +29,7 @@ static bool parseBitIndex(const Ogre::String &str, int &out) } static bool parseValueString(const Ogre::String &str, int &outInt, - float &outFloat, Ogre::Vector3 &outVec3, - int &type) + float &outFloat, Ogre::Vector3 &outVec3, int &type) { // type: 0=int, 1=float, 2=vec3 if (str.find(',') != Ogre::String::npos) { @@ -76,26 +75,25 @@ static bool parseComparison(const Ogre::String &str, Comparison &out) s++; const char *ops[] = { "==", "!=", ">=", "<=", ">", "<" }; - Comparison::Op opTypes[] = { - Comparison::Eq, Comparison::Ne, Comparison::Ge, - Comparison::Le, Comparison::Gt, Comparison::Lt - }; + Comparison::Op opTypes[] = { Comparison::Eq, Comparison::Ne, + Comparison::Ge, Comparison::Le, + Comparison::Gt, Comparison::Lt }; for (int i = 0; i < 6; i++) { size_t len = strlen(ops[i]); if (strncmp(s, ops[i], len) == 0) { out.op = opTypes[i]; Ogre::String valStr(s + len); - return parseValueString(valStr, out.intVal, out.floatVal, - out.vec3Val, out.valueType); + return parseValueString(valStr, out.intVal, + out.floatVal, out.vec3Val, + out.valueType); } } return false; } static bool compareValues(const Comparison &cmp, int actualInt, - float actualFloat, - const Ogre::Vector3 &actualVec3, + float actualFloat, const Ogre::Vector3 &actualVec3, int actualType) { // If types differ, promote int to float, or fail for vec3 @@ -123,9 +121,10 @@ static bool compareValues(const Comparison &cmp, int actualInt, a = actualFloat; b = cmp.floatVal; } else if (type == 2) { - bool eq = (std::abs(actualVec3.x - cmp.vec3Val.x) < g_epsilon) && - (std::abs(actualVec3.y - cmp.vec3Val.y) < g_epsilon) && - (std::abs(actualVec3.z - cmp.vec3Val.z) < g_epsilon); + bool eq = + (std::abs(actualVec3.x - cmp.vec3Val.x) < g_epsilon) && + (std::abs(actualVec3.y - cmp.vec3Val.y) < g_epsilon) && + (std::abs(actualVec3.z - cmp.vec3Val.z) < g_epsilon); switch (cmp.op) { case Comparison::Eq: return eq; @@ -170,8 +169,7 @@ bool BehaviorTreeSystem::isNewlyActive(RunnerState &state, char key[32]; snprintf(key, sizeof(key), "%p", (void *)node); return state.firstRun || - state.lastActiveLeaves.find(key) == - state.lastActiveLeaves.end(); + state.lastActiveLeaves.find(key) == state.lastActiveLeaves.end(); } void BehaviorTreeSystem::markActive(RunnerState &state, @@ -182,13 +180,13 @@ void BehaviorTreeSystem::markActive(RunnerState &state, state.currentActiveLeaves.insert(key); } -BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode( - const BehaviorTreeNode &node, flecs::entity e, - RunnerState &state) +BehaviorTreeSystem::Status +BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e, + RunnerState &state, float deltaTime) { if (node.type == "sequence") { for (const auto &child : node.children) { - Status s = evaluateNode(child, e, state); + Status s = evaluateNode(child, e, state, deltaTime); if (s == Status::failure) return Status::failure; if (s == Status::running) @@ -199,7 +197,7 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode( if (node.type == "selector") { for (const auto &child : node.children) { - Status s = evaluateNode(child, e, state); + Status s = evaluateNode(child, e, state, deltaTime); if (s == Status::success) return Status::success; if (s == Status::running) @@ -211,7 +209,7 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode( if (node.type == "invert") { if (node.children.empty()) return Status::failure; - Status s = evaluateNode(node.children[0], e, state); + Status s = evaluateNode(node.children[0], e, state, deltaTime); if (s == Status::success) return Status::failure; if (s == Status::failure) @@ -227,11 +225,38 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode( return Status::success; } + if (node.type == "delay") { + char key[32]; + snprintf(key, sizeof(key), "%p", (void *)&node); + + /* Parse delay duration from params (seconds as float) */ + float duration = 1.0f; /* default 1 second */ + if (!node.params.empty()) { + char *end = nullptr; + float val = strtof(node.params.c_str(), &end); + if (end != node.params.c_str() && *end == '\0' && + val > 0.0f) + duration = val; + } + + /* Initialize timer on first activation */ + if (isNewlyActive(state, &node)) + state.nodeTimers[key] = 0.0f; + + /* Accumulate time */ + state.nodeTimers[key] += deltaTime; + + if (state.nodeTimers[key] >= duration) + return Status::success; + + return Status::running; + } + if (node.type == "debugPrint") { if (isNewlyActive(state, &node)) { std::cout << "[BT] " << node.name << std::endl; - Ogre::LogManager::getSingleton().logMessage( - "[BT] " + node.name); + Ogre::LogManager::getSingleton().logMessage("[BT] " + + node.name); } return Status::success; } @@ -244,8 +269,7 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode( if (slash != Ogre::String::npos) { Ogre::String smName = name.substr(0, slash); Ogre::String stName = name.substr(slash + 1); - m_animSystem->setState(e, smName, stName, - true); + m_animSystem->setState(e, smName, stName, true); } } return Status::success; @@ -261,8 +285,7 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode( return Status::failure; /* Find the animation for this state and check time */ - Ogre::Entity *ent = - AnimationTreeSystem::findAnimatedEntity(e); + Ogre::Entity *ent = AnimationTreeSystem::findAnimatedEntity(e); if (!ent || !ent->hasSkeleton()) return Status::failure; @@ -303,12 +326,15 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode( auto leaf = st.findAnimationLeaf(); if (leaf) { - Ogre::AnimationStateSet * - set = ent->getAllAnimationStates(); + Ogre::AnimationStateSet *set = + ent->getAllAnimationStates(); if (set) { - auto it = set->getAnimationStates().find( - leaf->animationName); - if (it != set->getAnimationStates().end()) + auto it = + set->getAnimationStates() + .find(leaf->animationName); + if (it != + set->getAnimationStates() + .end()) as = it->second; } } @@ -411,8 +437,8 @@ BehaviorTreeSystem::Status BehaviorTreeSystem::evaluateNode( if (actualType < 0) return Status::failure; - bool ok = compareValues(cmp, actualInt, actualFloat, - actualVec3, actualType); + bool ok = compareValues(cmp, actualInt, actualFloat, actualVec3, + actualType); return ok ? Status::success : Status::failure; } @@ -434,18 +460,16 @@ void BehaviorTreeSystem::update(float deltaTime) { /* --- BehaviorTreeComponent instances --- */ m_world.query().each( - [this](flecs::entity e, - BehaviorTreeComponent &bt) { + [this, deltaTime](flecs::entity e, BehaviorTreeComponent &bt) { if (!bt.enabled) return; auto &state = m_runnerStates[e.id()]; state.currentActiveLeaves.clear(); - evaluateNode(bt.root, e, state); + evaluateNode(bt.root, e, state, deltaTime); - state.lastActiveLeaves = - state.currentActiveLeaves; + state.lastActiveLeaves = state.currentActiveLeaves; state.firstRun = false; }); @@ -460,10 +484,8 @@ void BehaviorTreeSystem::update(float deltaTime) return; m_world.query().each( - [this, db, deltaTime](flecs::entity e, - ActionDebug &debug) { - if (!debug.isRunning || - debug.currentActionName.empty()) + [this, db, deltaTime](flecs::entity e, ActionDebug &debug) { + if (!debug.isRunning || debug.currentActionName.empty()) return; const GoapAction *action = @@ -479,12 +501,14 @@ void BehaviorTreeSystem::update(float deltaTime) if (debug.runTimer <= 0.0f) { state.lastActiveLeaves.clear(); state.firstRun = true; + state.nodeTimers.clear(); + state.treeResult = Status::running; } - evaluateNode(action->behaviorTree, e, state); + state.treeResult = evaluateNode(action->behaviorTree, e, + state, deltaTime); - state.lastActiveLeaves = - state.currentActiveLeaves; + state.lastActiveLeaves = state.currentActiveLeaves; state.firstRun = false; debug.runTimer += deltaTime; }); diff --git a/src/features/editScene/systems/BehaviorTreeSystem.hpp b/src/features/editScene/systems/BehaviorTreeSystem.hpp index b761b5d..67a5527 100644 --- a/src/features/editScene/systems/BehaviorTreeSystem.hpp +++ b/src/features/editScene/systems/BehaviorTreeSystem.hpp @@ -22,16 +22,18 @@ class AnimationTreeSystem; */ class BehaviorTreeSystem { public: - BehaviorTreeSystem(flecs::world &world, - Ogre::SceneManager *sceneMgr, + BehaviorTreeSystem(flecs::world &world, Ogre::SceneManager *sceneMgr, AnimationTreeSystem *animSystem); ~BehaviorTreeSystem(); void update(float deltaTime); -private: + /** Result of a single behavior tree evaluation tick. */ enum class Status { success, failure, running }; + /** Per-entity state for tracking behavior tree evaluation. + * Each entity gets its own RunnerState so the same tree + * definition can be shared across multiple characters. */ struct RunnerState { /* Tracks which leaf nodes were active last frame, * keyed by node pointer address string */ @@ -39,15 +41,36 @@ private: /* Current frame's active leaves */ std::unordered_set currentActiveLeaves; bool firstRun = true; + + /* Per-node timers for delay nodes, keyed by node pointer + * address string. This allows the same tree to be shared + * across multiple characters, each with independent timers. */ + std::unordered_map nodeTimers; + + /* Result of the root node evaluation from last frame. + * Set to Status::running while the tree is active, + * Status::success or Status::failure when complete. */ + Status treeResult = Status::running; }; - Status evaluateNode(const BehaviorTreeNode &node, - flecs::entity e, - RunnerState &state); + /** Access the per-entity runner state for ActionDebug-driven trees. + * Used by SmartObjectSystem to detect when a behavior tree + * has finished evaluating. */ + const RunnerState &getActionDebugState(flecs::entity_t id) const + { + static RunnerState defaultState; + auto it = m_actionDebugStates.find(id); + if (it != m_actionDebugStates.end()) + return it->second; + return defaultState; + } + +private: + Status evaluateNode(const BehaviorTreeNode &node, flecs::entity e, + RunnerState &state, float deltaTime); /* Returns true if this leaf just became active this frame */ - bool isNewlyActive(RunnerState &state, - const BehaviorTreeNode *node); + bool isNewlyActive(RunnerState &state, const BehaviorTreeNode *node); /* Mark leaf as active for this frame */ void markActive(RunnerState &state, const BehaviorTreeNode *node); diff --git a/src/features/editScene/systems/SmartObjectSystem.cpp b/src/features/editScene/systems/SmartObjectSystem.cpp index 7c4c9f4..8ae46a0 100644 --- a/src/features/editScene/systems/SmartObjectSystem.cpp +++ b/src/features/editScene/systems/SmartObjectSystem.cpp @@ -599,7 +599,14 @@ void SmartObjectSystem::update(float deltaTime) auto &state = m_states[e.id()]; - if (state.state == State::Idle) { + // When the behavior tree is running (via ActionDebug), + // do NOT override animation states - the behavior tree's + // setAnimationState nodes should be the sole controller. + if (debug.isRunning && + !debug.currentActionName.empty()) { + // Behavior tree controls animations, skip + // locomotion state override + } else if (state.state == State::Idle) { setLocomotionState(e, "idle"); return; } @@ -892,8 +899,13 @@ void SmartObjectSystem::update(float deltaTime) if (state.state == State::Executing) { cc.linearVelocity = Ogre::Vector3::ZERO; - // Set idle animation while executing - setLocomotionState(e, "idle"); + // When the behavior tree is running (via ActionDebug), + // do NOT override animation states - the behavior tree's + // setAnimationState nodes should be the sole controller. + if (!debug.isRunning || + debug.currentActionName.empty()) { + setLocomotionState(e, "idle"); + } if (state.target.isExecuting && db) { const GoapAction *action = @@ -908,11 +920,18 @@ void SmartObjectSystem::update(float deltaTime) debug.currentActionName = state.target.actionName; - state.target.executionTimer += - deltaTime; - if (state.target.executionTimer > - 3.0f) { - // Stop behavior tree + // Check if the behavior tree + // has finished (sequence + // completed or failed) + auto &btState = + m_btSystem + ->getActionDebugState( + e.id()); + if (btState.treeResult != + BehaviorTreeSystem::Status:: + running) { + // Behavior tree + // completed - stop // evaluation debug.isRunning = false; debug.currentActionName diff --git a/src/features/editScene/ui/BehaviorTreeEditor.cpp b/src/features/editScene/ui/BehaviorTreeEditor.cpp index 002ad0d..4781b0b 100644 --- a/src/features/editScene/ui/BehaviorTreeEditor.cpp +++ b/src/features/editScene/ui/BehaviorTreeEditor.cpp @@ -19,32 +19,29 @@ static ImU32 nodeTypeColorU32(const Ogre::String &type) return IM_COL32(0x88, 0xDD, 0xFF, 0xFF); if (type == "isAnimationEnded") return IM_COL32(0xDD, 0x88, 0xFF, 0xFF); + if (type == "delay") + return IM_COL32(0xFF, 0xD7, 0x00, 0xFF); return IM_COL32(0xFF, 0xFF, 0xFF, 0xFF); } -static void makeLabel(const BehaviorTreeNode &node, char *buf, - size_t bufSize) +static void makeLabel(const BehaviorTreeNode &node, char *buf, size_t bufSize) { if (node.isLeaf()) { if (node.type == "debugPrint") snprintf(buf, bufSize, "[print] \"%s\"", - node.name.empty() ? "" : - node.name.c_str()); + node.name.empty() ? "" : node.name.c_str()); else if (node.type == "setAnimationState") snprintf(buf, bufSize, "[anim] %s", - node.name.empty() ? - "(none)" : - node.name.c_str()); + node.name.empty() ? "(none)" : + node.name.c_str()); else if (node.type == "isAnimationEnded") snprintf(buf, bufSize, "[ended?] %s", - node.name.empty() ? - "(none)" : - node.name.c_str()); + node.name.empty() ? "(none)" : + node.name.c_str()); else snprintf(buf, bufSize, "[%s] %s", node.type.c_str(), - node.name.empty() ? - "(none)" : - node.name.c_str()); + node.name.empty() ? "(none)" : + node.name.c_str()); } else { snprintf(buf, bufSize, "[%s]", node.type.c_str()); } @@ -118,7 +115,8 @@ void BehaviorTreeEditor::applyEditOps(BehaviorTreeComponent &bt) if (op.childIndex < vec.size()) { int newIdx = (int)op.childIndex + op.moveDelta; if (newIdx >= 0 && newIdx < (int)vec.size()) { - std::swap(vec[op.childIndex], vec[newIdx]); + std::swap(vec[op.childIndex], + vec[newIdx]); changed = true; } } @@ -134,14 +132,12 @@ void BehaviorTreeEditor::applyEditOps(BehaviorTreeComponent &bt) void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node, BehaviorTreeNode *parent, BehaviorTreeNode *&selectedNode, - BehaviorTreeNode *&selectedParent, - int &id) + BehaviorTreeNode *&selectedParent, int &id) { int myId = id++; bool isLeaf = node.isLeaf(); - ImGuiTreeNodeFlags flags = - ImGuiTreeNodeFlags_OpenOnArrow | - ImGuiTreeNodeFlags_OpenOnDoubleClick; + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | + ImGuiTreeNodeFlags_OpenOnDoubleClick; if (isLeaf) flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; @@ -151,12 +147,12 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node, char label[256]; makeLabel(node, label, sizeof(label)); - ImGui::PushStyleColor(ImGuiCol_Text, - ImGui::ColorConvertU32ToFloat4( - nodeTypeColorU32(node.type))); - bool open = ImGui::TreeNodeEx( - ("##btnode" + std::to_string(myId)).c_str(), flags, "%s", - label); + ImGui::PushStyleColor( + ImGuiCol_Text, + ImGui::ColorConvertU32ToFloat4(nodeTypeColorU32(node.type))); + bool open = + ImGui::TreeNodeEx(("##btnode" + std::to_string(myId)).c_str(), + flags, "%s", label); ImGui::PopStyleColor(); if (ImGui::IsItemClicked()) { @@ -194,6 +190,8 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node, queueAddChild(&node, "checkValue"); if (ImGui::MenuItem("Add Blackboard Dump")) queueAddChild(&node, "blackboardDump"); + if (ImGui::MenuItem("Add Delay")) + queueAddChild(&node, "delay"); } if (parent) { size_t idx = findChildIndex(*parent, &node); @@ -239,6 +237,8 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node, queueAddChild(&node, "checkValue"); if (ImGui::MenuItem("Blackboard Dump")) queueAddChild(&node, "blackboardDump"); + if (ImGui::MenuItem("Delay")) + queueAddChild(&node, "delay"); ImGui::EndPopup(); } } @@ -276,11 +276,20 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node) ImGui::Separator(); ImGui::Text("Node Properties"); - const char *types[] = { "sequence", "selector", "invert", - "task", "check", - "debugPrint", "setAnimationState", - "isAnimationEnded", "setBit", "checkBit", - "setValue", "checkValue", "blackboardDump" }; + const char *types[] = { "sequence", + "selector", + "invert", + "task", + "check", + "debugPrint", + "setAnimationState", + "isAnimationEnded", + "setBit", + "checkBit", + "setValue", + "checkValue", + "blackboardDump", + "delay" }; int typeIdx = 0; for (int i = 0; i < IM_ARRAYSIZE(types); i++) { if (node->type == types[i]) { @@ -330,9 +339,10 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node) // Params input for nodes that need it if (node->type == "setBit" || node->type == "setValue" || - node->type == "checkValue") { + node->type == "checkValue" || node->type == "delay") { char pbuf[256]; - snprintf(pbuf, sizeof(pbuf), "%s", node->params.c_str()); + snprintf(pbuf, sizeof(pbuf), "%s", + node->params.c_str()); const char *phint = nullptr; if (node->type == "setBit") phint = "1 or 0 (default 1)"; @@ -340,6 +350,8 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node) phint = "Value: 5, 3.14, or 1,2,3"; else if (node->type == "checkValue") phint = "Comparison: == 5, > 3.14, != 1,2,3"; + else if (node->type == "delay") + phint = "Duration in seconds (float, default 1.0)"; if (ImGui::InputText("Params", pbuf, sizeof(pbuf))) node->params = pbuf; if (phint && ImGui::IsItemHovered()) @@ -374,8 +386,7 @@ bool BehaviorTreeEditor::renderComponent(flecs::entity entity, static BehaviorTreeNode *selectedNode = nullptr; static BehaviorTreeNode *selectedParent = nullptr; int id = 0; - renderTree(bt.root, nullptr, selectedNode, selectedParent, - id); + renderTree(bt.root, nullptr, selectedNode, selectedParent, id); bool hadEdits = !m_editOps.empty(); applyEditOps(bt); diff --git a/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp b/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp index 92fd268..396f42d 100644 --- a/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp +++ b/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp @@ -20,24 +20,31 @@ void InlineBehaviorTreeEditor::applyOps(BehaviorTreeNode &root) if (op.type == EditOp::Add && op.parent) { if (op.childIndex <= op.parent->children.size()) op.parent->children.insert( - op.parent->children.begin() + op.childIndex, - BehaviorTreeNode{.type = op.addType}); + 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()) op.parent->children.erase( - op.parent->children.begin() + op.childIndex); + op.parent->children.begin() + + op.childIndex); } else if (op.type == EditOp::Move && op.parent) { if (op.childIndex < op.parent->children.size()) { - size_t newIndex = (int)op.childIndex + op.moveDelta; + size_t newIndex = + (int)op.childIndex + op.moveDelta; if (newIndex >= 0 && newIndex < op.parent->children.size() && newIndex != op.childIndex) { - auto child = op.parent->children[op.childIndex]; + auto child = + op.parent + ->children[op.childIndex]; op.parent->children.erase( - op.parent->children.begin() + op.childIndex); + op.parent->children.begin() + + op.childIndex); op.parent->children.insert( - op.parent->children.begin() + newIndex, - child); + op.parent->children.begin() + + newIndex, + child); } } } @@ -65,27 +72,35 @@ static void makeLabel(const BehaviorTreeNode &node, char *buf, size_t sz) static ImVec4 typeColorVec(const char *type) { - if (!strcmp(type, "sequence")) return ImVec4(0.53f, 1.0f, 0.53f, 1.0f); - if (!strcmp(type, "selector")) return ImVec4(1.0f, 1.0f, 0.53f, 1.0f); - if (!strcmp(type, "invert")) return ImVec4(1.0f, 0.53f, 0.53f, 1.0f); - if (!strcmp(type, "task")) return ImVec4(0.53f, 0.53f, 1.0f, 1.0f); - if (!strcmp(type, "check")) return ImVec4(0.53f, 1.0f, 1.0f, 1.0f); - if (!strcmp(type, "debugPrint")) return ImVec4(1.0f, 0.53f, 1.0f, 1.0f); - if (!strcmp(type, "setAnimationState")) return ImVec4(1.0f, 0.67f, 0.27f, 1.0f); - if (!strcmp(type, "isAnimationEnded")) return ImVec4(0.27f, 1.0f, 0.67f, 1.0f); + if (!strcmp(type, "sequence")) + return ImVec4(0.53f, 1.0f, 0.53f, 1.0f); + if (!strcmp(type, "selector")) + return ImVec4(1.0f, 1.0f, 0.53f, 1.0f); + if (!strcmp(type, "invert")) + return ImVec4(1.0f, 0.53f, 0.53f, 1.0f); + if (!strcmp(type, "task")) + return ImVec4(0.53f, 0.53f, 1.0f, 1.0f); + if (!strcmp(type, "check")) + return ImVec4(0.53f, 1.0f, 1.0f, 1.0f); + if (!strcmp(type, "debugPrint")) + return ImVec4(1.0f, 0.53f, 1.0f, 1.0f); + if (!strcmp(type, "setAnimationState")) + return ImVec4(1.0f, 0.67f, 0.27f, 1.0f); + if (!strcmp(type, "isAnimationEnded")) + return ImVec4(0.27f, 1.0f, 0.67f, 1.0f); + if (!strcmp(type, "delay")) + return ImVec4(1.0f, 0.84f, 0.0f, 1.0f); return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); } void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node, BehaviorTreeNode *parent, - BehaviorTreeNode *&selected, - int &id) + BehaviorTreeNode *&selected, int &id) { int myId = id++; bool isLeaf = node.isLeaf(); - ImGuiTreeNodeFlags flags = - ImGuiTreeNodeFlags_OpenOnArrow | - ImGuiTreeNodeFlags_OpenOnDoubleClick; + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | + ImGuiTreeNodeFlags_OpenOnDoubleClick; if (isLeaf) flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; @@ -107,18 +122,23 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node, if (ImGui::BeginPopupContextItem()) { if (ImGui::MenuItem("Add Child")) { - queueOp(EditOp{.type = EditOp::Add, - .parent = &node, - .childIndex = node.children.size(), - .addType = "task"}); + queueOp(EditOp{ .type = EditOp::Add, + .parent = &node, + .childIndex = node.children.size(), + .addType = "task" }); + } + if (ImGui::MenuItem("Add Delay")) { + queueOp(EditOp{ .type = EditOp::Add, + .parent = &node, + .childIndex = node.children.size(), + .addType = "delay" }); } if (parent && ImGui::MenuItem("Remove")) { for (size_t i = 0; i < parent->children.size(); i++) { if (&parent->children[i] == &node) { - queueOp(EditOp{ - .type = EditOp::Remove, - .parent = parent, - .childIndex = i}); + queueOp(EditOp{ .type = EditOp::Remove, + .parent = parent, + .childIndex = i }); if (selected == &node) selected = nullptr; break; @@ -128,10 +148,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node, if (parent && ImGui::MenuItem("Move Up")) { for (size_t i = 0; i < parent->children.size(); i++) { if (&parent->children[i] == &node) { - queueOp(EditOp{.type = EditOp::Move, - .parent = parent, - .childIndex = i, - .moveDelta = -1}); + queueOp(EditOp{ .type = EditOp::Move, + .parent = parent, + .childIndex = i, + .moveDelta = -1 }); break; } } @@ -139,10 +159,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node, if (parent && ImGui::MenuItem("Move Down")) { for (size_t i = 0; i < parent->children.size(); i++) { if (&parent->children[i] == &node) { - queueOp(EditOp{.type = EditOp::Move, - .parent = parent, - .childIndex = i, - .moveDelta = 1}); + queueOp(EditOp{ .type = EditOp::Move, + .parent = parent, + .childIndex = i, + .moveDelta = 1 }); break; } } @@ -154,10 +174,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node, if (!isLeaf) { ImGui::SameLine(); if (ImGui::SmallButton("+")) { - queueOp(EditOp{.type = EditOp::Add, - .parent = &node, - .childIndex = node.children.size(), - .addType = "task"}); + queueOp(EditOp{ .type = EditOp::Add, + .parent = &node, + .childIndex = node.children.size(), + .addType = "task" }); } } if (parent) { @@ -165,10 +185,9 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node, if (ImGui::SmallButton("-")) { for (size_t i = 0; i < parent->children.size(); i++) { if (&parent->children[i] == &node) { - queueOp(EditOp{ - .type = EditOp::Remove, - .parent = parent, - .childIndex = i}); + queueOp(EditOp{ .type = EditOp::Remove, + .parent = parent, + .childIndex = i }); if (selected == &node) selected = nullptr; break; @@ -179,10 +198,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node, if (ImGui::SmallButton("^")) { for (size_t i = 0; i < parent->children.size(); i++) { if (&parent->children[i] == &node) { - queueOp(EditOp{.type = EditOp::Move, - .parent = parent, - .childIndex = i, - .moveDelta = -1}); + queueOp(EditOp{ .type = EditOp::Move, + .parent = parent, + .childIndex = i, + .moveDelta = -1 }); break; } } @@ -191,10 +210,10 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node, if (ImGui::SmallButton("v")) { for (size_t i = 0; i < parent->children.size(); i++) { if (&parent->children[i] == &node) { - queueOp(EditOp{.type = EditOp::Move, - .parent = parent, - .childIndex = i, - .moveDelta = 1}); + queueOp(EditOp{ .type = EditOp::Move, + .parent = parent, + .childIndex = i, + .moveDelta = 1 }); break; } } @@ -213,12 +232,20 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node) { if (!node) return; - const char *types[] = { - "sequence", "selector", "invert", "task", "check", - "debugPrint", "setAnimationState", "isAnimationEnded", - "setBit", "checkBit", "setValue", "checkValue", - "blackboardDump" - }; + const char *types[] = { "sequence", + "selector", + "invert", + "task", + "check", + "debugPrint", + "setAnimationState", + "isAnimationEnded", + "setBit", + "checkBit", + "setValue", + "checkValue", + "blackboardDump", + "delay" }; int current = 0; for (int i = 0; i < IM_ARRAYSIZE(types); i++) { if (node->type == types[i]) { @@ -269,9 +296,10 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node) // Params input for nodes that need it if (node->type == "setBit" || node->type == "setValue" || - node->type == "checkValue") { + node->type == "checkValue" || node->type == "delay") { char pbuf[256]; - snprintf(pbuf, sizeof(pbuf), "%s", node->params.c_str()); + snprintf(pbuf, sizeof(pbuf), "%s", + node->params.c_str()); const char *phint = nullptr; if (node->type == "setBit") phint = "1 or 0 (default 1)"; @@ -279,6 +307,8 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node) phint = "Value: 5, 3.14, or 1,2,3"; else if (node->type == "checkValue") phint = "Comparison: == 5, > 3.14, != 1,2,3"; + else if (node->type == "delay") + phint = "Duration in seconds (float, default 1.0)"; if (ImGui::InputText("Params", pbuf, sizeof(pbuf))) node->params = pbuf; if (phint && ImGui::IsItemHovered()) @@ -303,15 +333,13 @@ bool InlineBehaviorTreeEditor::render(BehaviorTreeNode &root) int id = 0; getOps().clear(); - if (root.children.empty() && root.type == "task" && - root.name.empty()) { + if (root.children.empty() && root.type == "task" && root.name.empty()) { ImGui::TextDisabled("(empty - right-click to add nodes)"); if (ImGui::Button("Add Root Child")) { - queueOp(EditOp{ - .type = EditOp::Add, - .parent = &root, - .childIndex = 0, - .addType = "task"}); + queueOp(EditOp{ .type = EditOp::Add, + .parent = &root, + .childIndex = 0, + .addType = "task" }); modified = true; } } else {