From 75ba39895f7fe9a60afafae652f50cd00479d023 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Sat, 25 Apr 2026 21:55:21 +0300 Subject: [PATCH] Teleport node works --- .../editScene/components/BehaviorTree.hpp | 7 +- .../editScene/systems/BehaviorTreeSystem.cpp | 139 ++++++++++++++++++ .../editScene/ui/BehaviorTreeEditor.cpp | 12 +- .../editScene/ui/InlineBehaviorTreeEditor.cpp | 14 +- 4 files changed, 169 insertions(+), 3 deletions(-) diff --git a/src/features/editScene/components/BehaviorTree.hpp b/src/features/editScene/components/BehaviorTree.hpp index abfd1c4..130bf71 100644 --- a/src/features/editScene/components/BehaviorTree.hpp +++ b/src/features/editScene/components/BehaviorTree.hpp @@ -23,6 +23,11 @@ * "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) + * "teleportToChild" - Leaf: teleports character to a named child entity + * of the Smart Object being interacted with. + * name = child entity name to teleport to. + * The character is positioned at the child's absolute + * world transform (position + orientation). */ struct BehaviorTreeNode { Ogre::String type = "task"; @@ -63,7 +68,7 @@ struct BehaviorTreeNode { type == "isAnimationEnded" || type == "setBit" || type == "checkBit" || type == "setValue" || type == "checkValue" || type == "blackboardDump" || - type == "delay"; + type == "delay" || type == "teleportToChild"; } }; diff --git a/src/features/editScene/systems/BehaviorTreeSystem.cpp b/src/features/editScene/systems/BehaviorTreeSystem.cpp index 5b40045..8a1d245 100644 --- a/src/features/editScene/systems/BehaviorTreeSystem.cpp +++ b/src/features/editScene/systems/BehaviorTreeSystem.cpp @@ -1,8 +1,13 @@ #include "BehaviorTreeSystem.hpp" #include "AnimationTreeSystem.hpp" +#include "SmartObjectSystem.hpp" #include "../components/BehaviorTree.hpp" #include "../components/ActionDatabase.hpp" #include "../components/GoapBlackboard.hpp" +#include "../components/SmartObject.hpp" +#include "../components/Transform.hpp" +#include "../components/EntityName.hpp" +#include "../components/Relationship.hpp" #include #include #include @@ -453,6 +458,140 @@ BehaviorTreeSystem::evaluateNode(const BehaviorTreeNode &node, flecs::entity e, return Status::success; } + /* --- Teleport to Smart Object child node --- */ + if (node.type == "teleportToChild") { + if (isNewlyActive(state, &node)) { + Ogre::String childName = node.name; + if (childName.empty()) { + std::cout << "[BT] teleportToChild: no child " + "name specified" + << std::endl; + return Status::failure; + } + + // Get the SmartObjectSystem to find the current + // smart object target for this character + SmartObjectSystem *soSystem = + SmartObjectSystem::getInstance(); + if (!soSystem) { + std::cout << "[BT] teleportToChild: " + "SmartObjectSystem not available" + << std::endl; + return Status::failure; + } + + // Find the smart object entity that this character + // is currently interacting with. We query the + // SmartObjectSystem's internal state by looking + // for the nearest smart object with a matching + // action in the character's blackboard. + flecs::entity smartObject = flecs::entity::null(); + + // Walk all SmartObjectComponent entities to find + // the one this character is executing + m_world.query() + .each([&](flecs::entity so, + SmartObjectComponent &soComp, + TransformComponent &) { + (void)soComp; + // Check if this character has an + // ActionDebug with a matching action + // name from this smart object + if (e.has()) { + auto &debug = + e.get(); + for (const auto &an : + soComp.actionNames) { + if (an == + debug.currentActionName) { + smartObject = + so; + return; + } + } + } + }); + + if (!smartObject.is_alive()) { + std::cout << "[BT] teleportToChild: no smart " + "object found for character " + << e.id() << std::endl; + return Status::failure; + } + + // Find the child entity by name under the smart + // object using Flecs' built-in child_of relationship + flecs::entity childEntity = flecs::entity::null(); + smartObject.children([&](flecs::entity child) { + if (childEntity.is_alive()) + return; + if (child.has()) { + auto &nameComp = + child.get(); + if (nameComp.name == childName) { + childEntity = child; + } + } + }); + + if (!childEntity.is_alive()) { + std::cout << "[BT] teleportToChild: child '" + << childName + << "' not found under smart object " + << smartObject.id() << std::endl; + return Status::failure; + } + + // Get the child's absolute world transform + Ogre::Vector3 targetPos = Ogre::Vector3::ZERO; + Ogre::Quaternion targetRot = Ogre::Quaternion::IDENTITY; + + if (childEntity.has()) { + auto &childTrans = + childEntity.get(); + if (childTrans.node) { + targetPos = + childTrans.node + ->_getDerivedPosition(); + targetRot = + childTrans.node + ->_getDerivedOrientation(); + } else { + targetPos = childTrans.position; + targetRot = childTrans.rotation; + } + } + + // Teleport the character to the target position + if (e.has()) { + auto &charTrans = + e.get_mut(); + if (charTrans.node) { + charTrans.node->setPosition(targetPos); + charTrans.node->setOrientation( + targetRot); + } + charTrans.position = targetPos; + charTrans.rotation = targetRot; + charTrans.markChanged(); + + std::cout << "[BT] teleportToChild: teleported " + << "character " << e.id() + << " to child '" << childName + << "' at (" << targetPos.x << ", " + << targetPos.y << ", " << targetPos.z + << ")" << std::endl; + } else { + std::cout << "[BT] teleportToChild: character " + << e.id() + << " has no TransformComponent" + << std::endl; + return Status::failure; + } + } + return Status::success; + } + return Status::success; } diff --git a/src/features/editScene/ui/BehaviorTreeEditor.cpp b/src/features/editScene/ui/BehaviorTreeEditor.cpp index 4781b0b..7061615 100644 --- a/src/features/editScene/ui/BehaviorTreeEditor.cpp +++ b/src/features/editScene/ui/BehaviorTreeEditor.cpp @@ -21,6 +21,8 @@ static ImU32 nodeTypeColorU32(const Ogre::String &type) return IM_COL32(0xDD, 0x88, 0xFF, 0xFF); if (type == "delay") return IM_COL32(0xFF, 0xD7, 0x00, 0xFF); + if (type == "teleportToChild") + return IM_COL32(0xFF, 0x69, 0xB4, 0xFF); return IM_COL32(0xFF, 0xFF, 0xFF, 0xFF); } @@ -192,6 +194,8 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node, queueAddChild(&node, "blackboardDump"); if (ImGui::MenuItem("Add Delay")) queueAddChild(&node, "delay"); + if (ImGui::MenuItem("Add Teleport To Child")) + queueAddChild(&node, "teleportToChild"); } if (parent) { size_t idx = findChildIndex(*parent, &node); @@ -239,6 +243,8 @@ void BehaviorTreeEditor::renderTree(BehaviorTreeNode &node, queueAddChild(&node, "blackboardDump"); if (ImGui::MenuItem("Delay")) queueAddChild(&node, "delay"); + if (ImGui::MenuItem("Teleport To Child")) + queueAddChild(&node, "teleportToChild"); ImGui::EndPopup(); } } @@ -289,7 +295,8 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node) "setValue", "checkValue", "blackboardDump", - "delay" }; + "delay", + "teleportToChild" }; int typeIdx = 0; for (int i = 0; i < IM_ARRAYSIZE(types); i++) { if (node->type == types[i]) { @@ -330,6 +337,9 @@ void BehaviorTreeEditor::renderProperties(BehaviorTreeNode *node) } 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"; } if (ImGui::InputText(label, buf, sizeof(buf))) diff --git a/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp b/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp index 396f42d..bb4e500 100644 --- a/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp +++ b/src/features/editScene/ui/InlineBehaviorTreeEditor.cpp @@ -90,6 +90,8 @@ static ImVec4 typeColorVec(const char *type) return ImVec4(0.27f, 1.0f, 0.67f, 1.0f); if (!strcmp(type, "delay")) return ImVec4(1.0f, 0.84f, 0.0f, 1.0f); + if (!strcmp(type, "teleportToChild")) + return ImVec4(1.0f, 0.41f, 0.71f, 1.0f); return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); } @@ -133,6 +135,12 @@ void InlineBehaviorTreeEditor::renderNode(BehaviorTreeNode &node, .childIndex = node.children.size(), .addType = "delay" }); } + if (ImGui::MenuItem("Add Teleport To Child")) { + queueOp(EditOp{ .type = EditOp::Add, + .parent = &node, + .childIndex = node.children.size(), + .addType = "teleportToChild" }); + } if (parent && ImGui::MenuItem("Remove")) { for (size_t i = 0; i < parent->children.size(); i++) { if (&parent->children[i] == &node) { @@ -245,7 +253,8 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node) "setValue", "checkValue", "blackboardDump", - "delay" }; + "delay", + "teleportToChild" }; int current = 0; for (int i = 0; i < IM_ARRAYSIZE(types); i++) { if (node->type == types[i]) { @@ -287,6 +296,9 @@ void InlineBehaviorTreeEditor::renderProps(BehaviorTreeNode *node) } 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"; } if (ImGui::InputText(label, buf, sizeof(buf)))