From 3bd2801d1df5f836f6f0b8378c348ca93ff5dbcd Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Sat, 25 Apr 2026 09:04:12 +0300 Subject: [PATCH] Smart objects work! --- .../editScene/systems/SmartObjectSystem.cpp | 251 ++++++++++++++++-- 1 file changed, 233 insertions(+), 18 deletions(-) diff --git a/src/features/editScene/systems/SmartObjectSystem.cpp b/src/features/editScene/systems/SmartObjectSystem.cpp index 3001f55..7c4c9f4 100644 --- a/src/features/editScene/systems/SmartObjectSystem.cpp +++ b/src/features/editScene/systems/SmartObjectSystem.cpp @@ -346,6 +346,9 @@ void SmartObjectSystem::update(float deltaTime) auto &soComp = state.target.smartObject .get(); + + // Always check range first - if we're close enough, + // execute the action regardless of path state if (isInRange(charPos, objPos, soComp.radius, soComp.height)) { state.state = State::Executing; @@ -376,8 +379,45 @@ void SmartObjectSystem::update(float deltaTime) } } + // Check if path is exhausted if (state.target.pathIndex >= (int)state.target.path.size()) { + float distToObj = + charPos.distance(objPos); + // If we're close enough to the object + // (within radius*2), teleport to range + // and execute + if (distToObj < soComp.radius * 2.0f) { + // Teleport character to be + // within range + Ogre::Vector3 dirToObj = + objPos - charPos; + dirToObj.y = 0; + float xzDist = + dirToObj.length(); + if (xzDist > 0.01f) { + dirToObj /= xzDist; + Ogre::Vector3 newPos = + objPos - + dirToObj * + (soComp.radius * + 0.5f); + newPos.y = charPos.y; + if (trans.node) { + trans.node->setPosition( + newPos); + } + trans.position = newPos; + } + state.state = State::Executing; + state.target.isExecuting = true; + state.target.executionTimer = + 0.0f; + cc.linearVelocity = + Ogre::Vector3::ZERO; + return; + } + // Too far - recalculate path state.target.path.clear(); state.target.path.push_back(objPos); state.target.pathIndex = 0; @@ -388,12 +428,45 @@ void SmartObjectSystem::update(float deltaTime) .path[state.target.pathIndex]; Ogre::Vector3 toTarget = targetPos - charPos; - if (toTarget.length() < 0.5f) { + // Waypoint arrival threshold - use generous + // value since waypoints are just guide points + const float WAYPOINT_THRESHOLD = 1.0f; + if (toTarget.length() < WAYPOINT_THRESHOLD) { state.target.pathIndex++; if (state.target.pathIndex >= (int)state.target.path.size()) { - toTarget = objPos - charPos; - if (toTarget.length() < 0.5f) { + // Path exhausted - teleport to + // range and execute if close + // enough + float distToObj = + charPos.distance( + objPos); + if (distToObj < + soComp.radius * 2.0f) { + Ogre::Vector3 dirToObj = + objPos - + charPos; + dirToObj.y = 0; + float xzDist = + dirToObj.length(); + if (xzDist > 0.01f) { + dirToObj /= + xzDist; + Ogre::Vector3 newPos = + objPos - + dirToObj * + (soComp.radius * + 0.5f); + newPos.y = + charPos.y; + if (trans.node) { + trans.node + ->setPosition( + newPos); + } + trans.position = + newPos; + } state.state = State::Executing; state.target @@ -407,13 +480,19 @@ void SmartObjectSystem::update(float deltaTime) ZERO; return; } - } else { - targetPos = - state.target.path - [state.target - .pathIndex]; - toTarget = targetPos - charPos; + // Too far - recalculate path + state.target.path.clear(); + state.target.path.push_back( + objPos); + state.target.pathIndex = 0; + cc.linearVelocity = + Ogre::Vector3::ZERO; + return; } + targetPos = + state.target.path + [state.target.pathIndex]; + toTarget = targetPos - charPos; } // Only set velocity direction - AnimationTreeSystem @@ -530,6 +609,9 @@ void SmartObjectSystem::update(float deltaTime) // --- State: Pathfinding --- if (state.state == State::Pathfinding) { if (!state.target.smartObject.is_alive()) { + std::cout + << "[SO] Pathfinding: target dead, going idle" + << std::endl; state.state = State::Idle; return; } @@ -543,6 +625,9 @@ void SmartObjectSystem::update(float deltaTime) .get(); if (isInRange(charPos, objPos, soComp.radius, soComp.height)) { + std::cout + << "[SO] Pathfinding: already in range, executing" + << std::endl; state.state = State::Executing; state.target.isExecuting = true; state.target.executionTimer = 0.0f; @@ -558,13 +643,25 @@ void SmartObjectSystem::update(float deltaTime) state.target.path); if (found && !state.target.path.empty()) { + std::cout + << "[SO] Pathfinding: path found with " + << state.target.path + .size() + << " waypoints" + << std::endl; state.state = State::Moving; } else { + std::cout + << "[SO] Pathfinding: no path found, going idle" + << std::endl; state.state = State::Idle; cc.linearVelocity = Ogre::Vector3::ZERO; } } else { + std::cout + << "[SO] Pathfinding: no navmesh, going direct" + << std::endl; state.target.path.clear(); state.target.path.push_back(objPos); state.target.pathIndex = 0; @@ -577,6 +674,9 @@ void SmartObjectSystem::update(float deltaTime) if (state.state == State::Moving) { if (!state.target.smartObject.is_alive() || state.target.path.empty()) { + std::cout + << "[SO] Moving: target dead or path empty, idle" + << std::endl; state.state = State::Idle; cc.linearVelocity = Ogre::Vector3::ZERO; return; @@ -589,8 +689,13 @@ void SmartObjectSystem::update(float deltaTime) auto &soComp = state.target.smartObject .get(); + + // Always check range first if (isInRange(charPos, objPos, soComp.radius, soComp.height)) { + std::cout + << "[SO] Moving: in range, executing" + << std::endl; state.state = State::Executing; state.target.isExecuting = true; state.target.executionTimer = 0.0f; @@ -619,8 +724,53 @@ void SmartObjectSystem::update(float deltaTime) } } + // Check if path is exhausted if (state.target.pathIndex >= (int)state.target.path.size()) { + float distToObj = + charPos.distance(objPos); + std::cout + << "[SO] Moving: path exhausted, dist=" + << distToObj << " radius*2=" + << soComp.radius * 2.0f + << std::endl; + // If close enough, teleport to range + // and execute + if (distToObj < soComp.radius * 2.0f) { + std::cout + << "[SO] Moving: close enough, teleporting" + << std::endl; + Ogre::Vector3 dirToObj = + objPos - charPos; + dirToObj.y = 0; + float xzDist = + dirToObj.length(); + if (xzDist > 0.01f) { + dirToObj /= xzDist; + Ogre::Vector3 newPos = + objPos - + dirToObj * + (soComp.radius * + 0.5f); + newPos.y = charPos.y; + if (trans.node) { + trans.node->setPosition( + newPos); + } + trans.position = newPos; + } + state.state = State::Executing; + state.target.isExecuting = true; + state.target.executionTimer = + 0.0f; + cc.linearVelocity = + Ogre::Vector3::ZERO; + return; + } + // Too far - recalculate path + std::cout + << "[SO] Moving: too far, recalc path" + << std::endl; state.target.path.clear(); state.target.path.push_back(objPos); state.target.pathIndex = 0; @@ -631,12 +781,54 @@ void SmartObjectSystem::update(float deltaTime) .path[state.target.pathIndex]; Ogre::Vector3 toTarget = targetPos - charPos; - if (toTarget.length() < 0.5f) { + // Waypoint arrival threshold - generous since + // waypoints are just guide points + const float WAYPOINT_THRESHOLD = 1.0f; + if (toTarget.length() < WAYPOINT_THRESHOLD) { state.target.pathIndex++; if (state.target.pathIndex >= (int)state.target.path.size()) { - toTarget = objPos - charPos; - if (toTarget.length() < 0.5f) { + // Path exhausted - teleport to + // range and execute if close + // enough + float distToObj = + charPos.distance( + objPos); + std::cout + << "[SO] Moving: last waypoint reached, dist=" + << distToObj + << " radius*2=" + << soComp.radius * 2.0f + << std::endl; + if (distToObj < + soComp.radius * 2.0f) { + std::cout + << "[SO] Moving: close enough at waypoint, teleporting" + << std::endl; + Ogre::Vector3 dirToObj = + objPos - + charPos; + dirToObj.y = 0; + float xzDist = + dirToObj.length(); + if (xzDist > 0.01f) { + dirToObj /= + xzDist; + Ogre::Vector3 newPos = + objPos - + dirToObj * + (soComp.radius * + 0.5f); + newPos.y = + charPos.y; + if (trans.node) { + trans.node + ->setPosition( + newPos); + } + trans.position = + newPos; + } state.state = State::Executing; state.target @@ -650,13 +842,22 @@ void SmartObjectSystem::update(float deltaTime) ZERO; return; } - } else { - targetPos = - state.target.path - [state.target - .pathIndex]; - toTarget = targetPos - charPos; + // Too far - recalculate path + std::cout + << "[SO] Moving: too far at waypoint, recalc" + << std::endl; + state.target.path.clear(); + state.target.path.push_back( + objPos); + state.target.pathIndex = 0; + cc.linearVelocity = + Ogre::Vector3::ZERO; + return; } + targetPos = + state.target.path + [state.target.pathIndex]; + toTarget = targetPos - charPos; } toTarget.y = 0; @@ -699,10 +900,24 @@ void SmartObjectSystem::update(float deltaTime) db->findAction( state.target.actionName); if (action) { + // Kick off the action's + // behavior tree via + // BehaviorTreeSystem's + // ActionDebug path + debug.isRunning = true; + debug.currentActionName = + state.target.actionName; + state.target.executionTimer += deltaTime; if (state.target.executionTimer > 3.0f) { + // Stop behavior tree + // evaluation + debug.isRunning = false; + debug.currentActionName + .clear(); + bb.apply( action->effects); state.state =