diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 9e25084d3..aa937022d 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -34,20 +34,17 @@ bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, AiState& state ) return true; //Target doesn't exist - //Set the target desition from the actor + //Set the target destination for the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200) { //Stop when you get close - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + if (pathTo(actor, dest, duration, 200)) //Go to the destination + { + // activate when reached MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); - MWBase::Environment::get().getWorld()->activate(target, actor); return true; } - else { - pathTo(actor, dest, duration); //Go to the destination - } return false; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 8f43c6280..fb47c1109 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -37,46 +37,6 @@ namespace Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos, float duration, int weapType, float strength); - - float getZAngleToDir(const Ogre::Vector3& dir) - { - return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees(); - } - - float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f) - { - float len = (dirLen > 0.0f)? dirLen : dir.length(); - return -Ogre::Math::ASin(dir.z / len).valueDegrees(); - } - - - const float PATHFIND_Z_REACH = 50.0f; - // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid - const float PATHFIND_CAUTION_DIST = 500.0f; - // distance after which actor (failed previously to shortcut) will try again - const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; - - // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; - // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH - bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY) - { - if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH) - { - Ogre::Vector3 dir = to - from; - dir.z = 0; - dir.normalise(); - float verticalOffset = 200; // instead of '200' here we want the height of the actor - Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; - - // cast up-down ray and find height in world space of hit - float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); - - if(std::abs(from.z - h) <= PATHFIND_Z_REACH) - return true; - } - - return false; - } } namespace MWMechanics @@ -702,7 +662,7 @@ namespace MWMechanics if (doesPathNeedRecalc(newPathTarget, actor.getCell()->getCell())) { ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actor.getRefData().getPosition())); - mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell(), false); + mPathFinder.buildSyncedPath(start, newPathTarget, actor.getCell()); } } diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index ddfc14581..55414bfff 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -130,19 +130,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duratio //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < followDistance) //Stop when you get close - { - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - - // turn towards target anyway - float directionX = target.getRefData().getPosition().pos[0] - actor.getRefData().getPosition().pos[0]; - float directionY = target.getRefData().getPosition().pos[1] - actor.getRefData().getPosition().pos[1]; - zTurn(actor, Ogre::Math::ATan2(directionX,directionY), Ogre::Degree(5)); - } - else - { - pathTo(actor, dest, duration); //Go to the destination - } + pathTo(actor, dest, duration, followDistance); //Go to the destination //Check if you're far away if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 450) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 216bf7b09..425593624 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -16,12 +16,14 @@ MWMechanics::AiPackage::~AiPackage() {} -MWMechanics::AiPackage::AiPackage() : mTimer(0.26f), mStuckTimer(0) { //mTimer starts at .26 to force initial pathbuild - +MWMechanics::AiPackage::AiPackage() : + mTimer(0.26f), mStuckTimer(0), //mTimer starts at .26 to force initial pathbuild + mShortcutProhibited(false), mShortcutFailPos() +{ } -bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration) +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance) { //Update various Timers mTimer += duration; //Update timer @@ -63,13 +65,51 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //*********************** /// Checks if you can't get to the end position at all, adds end position to end of path - /// Rebuilds path every quarter of a second, in case the target has moved + /// Rebuilds path every [AI_REACTION_TIME] seconds, in case the target has moved //*********************** - if(mTimer > 0.25) + + bool isStuck = distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05 + && distance(dest, start) > 20; + + float distToNextWaypoint = distance(start, dest); + + bool isDestReached = (distToNextWaypoint <= destTolerance); + + if (!isDestReached && mTimer > AI_REACTION_TIME) { - if (doesPathNeedRecalc(dest, cell)) { //Only rebuild path if it's moved - mPathFinder.buildSyncedPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved - mPrevDest = dest; + bool needPathRecalc = doesPathNeedRecalc(dest, cell); + + bool isWayClear = true; + + if (!needPathRecalc) // TODO: add check if actor is actually shortcutting + { + isWayClear = checkWayIsClearForActor(start, dest, actor); // check if current shortcut is safe to follow + } + + if (!isWayClear || needPathRecalc) // Only rebuild path if the target has moved or can't follow current shortcut + { + bool destInLOS = false; + + if (isStuck || !isWayClear || !shortcutPath(start, dest, actor, &destInLOS)) + { + mPathFinder.buildSyncedPath(start, dest, actor.getCell()); + + // give priority to go directly on target if there is minimal opportunity + if (destInLOS && mPathFinder.getPath().size() > 1) + { + // get point just before dest + std::list::const_iterator pPointBeforeDest = mPathFinder.getPath().end(); + --pPointBeforeDest; + --pPointBeforeDest; + + // if start point is closer to the target then last point of path (excluding target itself) then go straight on the target + if (distance(start, dest) <= distance(dest, *pPointBeforeDest)) + { + mPathFinder.clearPath(); + mPathFinder.addPointToPath(dest); + } + } + } } if(!mPathFinder.getPath().empty()) //Path has points in it @@ -86,13 +126,22 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //************************ /// Checks if you aren't moving; attempts to unstick you //************************ - if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? + if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1])) //Path finished? + { + actor.getClass().getMovementSettings(actor).mPosition[0] = 0; // stop moving + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + actor.getClass().getMovementSettings(actor).mPosition[2] = 0; + + // turn to destination point + zTurn(actor, Ogre::Degree(getZAngleToPoint(start, dest))); + smoothTurn(actor, Ogre::Degree(getXAngleToPoint(start, dest)), 0); return true; + } else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something { /// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason //if(mObstacleCheck.check(actor, duration)) { - if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < actor.getClass().getSpeed(actor)*0.05 && distance(dest, start) > 20) { //Actually stuck, and far enough away from destination to care + if (isStuck) { //Actually stuck, and far enough away from destination to care // first check if we're walking into a door MWWorld::Ptr door = getNearbyDoor(actor); if(door != MWWorld::Ptr()) // NOTE: checks interior cells only @@ -119,12 +168,94 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time } + // turn to next path point by X,Z axes zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + smoothTurn(actor, Ogre::Degree(mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2])), 0); return false; } +bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) +{ + const MWWorld::Class& actorClass = actor.getClass(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + // check if actor can move along z-axis + bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || world->isFlying(actor); + + // don't use pathgrid when actor can move in 3 dimensions + bool isPathClear = actorCanMoveByZ; + + if (!isPathClear + && (!mShortcutProhibited || (PathFinder::MakeOgreVector3(mShortcutFailPos) - PathFinder::MakeOgreVector3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) + { + // take the direct path only if there aren't any obstacles + isPathClear = !MWBase::Environment::get().getWorld()->castRay( + static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), + static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ)); + + if (destInLOS != NULL) *destInLOS = isPathClear; + + if (!isPathClear) + return false; + + // check if an actor can move along the shortcut path + isPathClear = checkWayIsClearForActor(startPoint, endPoint, actor); + } + + if (isPathClear) // can shortcut the path + { + mPathFinder.clearPath(); + mPathFinder.addPointToPath(endPoint); + return true; + } + + return false; +} + +bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor) +{ + bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) + || MWBase::Environment::get().getWorld()->isFlying(actor); + + if (actorCanMoveByZ) + return true; + + float actorSpeed = actor.getClass().getSpeed(actor); + float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR.valueRadians() * 2; // *2 - for reliability + Ogre::Real distToTarget = Ogre::Vector3(static_cast(endPoint.mX), static_cast(endPoint.mY), 0).length(); + + float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2; + + bool isClear = checkWayIsClear(PathFinder::MakeOgreVector3(startPoint), PathFinder::MakeOgreVector3(endPoint), offsetXY); + + // update shortcut prohibit state + if (isClear) + { + if (mShortcutProhibited) + { + mShortcutProhibited = false; + mShortcutFailPos = ESM::Pathgrid::Point(); + } + } + if (!isClear) + { + if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0) + { + mShortcutProhibited = true; + mShortcutFailPos = startPoint; + } + } + + return isClear; +} + bool MWMechanics::AiPackage::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell) { - return distance(mPrevDest, dest) > 10; + bool needRecalc = distance(mPrevDest, dest) > 10; + if (needRecalc) + mPrevDest = dest; + + return needRecalc; } \ No newline at end of file diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 78a2bfd9f..a22ac9357 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -24,6 +24,7 @@ namespace ESM namespace MWMechanics { + const float AI_REACTION_TIME = 0.25f; /// \brief Base class for AI packages class AiPackage @@ -70,7 +71,16 @@ namespace MWMechanics protected: /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ - bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration); + bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f); + + /// Check if there aren't any obstacles along the path to make shortcut possible + /// If a shortcut is possible then path will be cleared and filled with the destination point. + /// \param destInLOS If not NULL function will return ray cast check result + /// \return If can shortcut the path + bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS); + + /// Check if the way to the destination is clear, taking into account actor speed + bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); @@ -83,6 +93,9 @@ namespace MWMechanics ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; + + bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt + ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail }; } diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 8c31a10db..af007231b 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -55,14 +55,10 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, AiState& state, float duratio //Set the target desition from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) { //Stop when you get close - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - target.getClass().activate(target,actor).get()->execute(actor); //Arrest player + if (pathTo(actor, dest, duration, 100)) { + target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached return true; } - else { - pathTo(actor, dest, duration); //Go to the destination - } actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 1f4133c0a..4566740e3 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -413,7 +413,7 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell()); if(storage.mPathFinder.isPathConstructed()) { @@ -520,7 +520,7 @@ namespace MWMechanics ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); // don't take shortcuts for wandering - storage.mPathFinder.buildPath(start, dest, actor.getCell(), false); + storage.mPathFinder.buildPath(start, dest, actor.getCell()); if(storage.mPathFinder.isPathConstructed()) { diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index fea993e23..c32899dde 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -113,6 +113,49 @@ namespace MWMechanics return sqrt(x * x + y * y + z * z); } + float getZAngleToDir(const Ogre::Vector3& dir) + { + return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees(); + } + + float getXAngleToDir(const Ogre::Vector3& dir, float dirLen) + { + float len = (dirLen > 0.0f)? dirLen : dir.length(); + return -Ogre::Math::ASin(dir.z / len).valueDegrees(); + } + + float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) + { + Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin); + return getZAngleToDir(dir); + } + + float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest) + { + Ogre::Vector3 dir = PathFinder::MakeOgreVector3(dest) - PathFinder::MakeOgreVector3(origin); + return getXAngleToDir(dir); + } + + bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY) + { + if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH) + { + Ogre::Vector3 dir = to - from; + dir.z = 0; + dir.normalise(); + float verticalOffset = 200; // instead of '200' here we want the height of the actor + Ogre::Vector3 _from = from + dir*offsetXY + Ogre::Vector3::UNIT_Z * verticalOffset; + + // cast up-down ray and find height in world space of hit + float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); + + if(std::abs(from.z - h) <= PATHFIND_Z_REACH) + return true; + } + + return false; + } + PathFinder::PathFinder() : mPathgrid(NULL), mCell(NULL) @@ -165,23 +208,10 @@ namespace MWMechanics */ void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, - bool allowShortcuts) + const MWWorld::CellStore* cell) { mPath.clear(); - if(allowShortcuts) - { - // if there's a ray cast hit, can't take a direct path - if (!MWBase::Environment::get().getWorld()->castRay( - static_cast(startPoint.mX), static_cast(startPoint.mY), static_cast(startPoint.mZ), - static_cast(endPoint.mX), static_cast(endPoint.mY), static_cast(endPoint.mZ))) - { - mPath.push_back(endPoint); - return; - } - } - if(mCell != cell || !mPathgrid) { mCell = cell; @@ -270,6 +300,19 @@ namespace MWMechanics return Ogre::Math::ATan2(directionX,directionY).valueDegrees(); } + float PathFinder::getXAngleToNext(float x, float y, float z) const + { + // This should never happen (programmers should have an if statement checking + // isPathConstructed that prevents this call if otherwise). + if(mPath.empty()) + return 0.; + + const ESM::Pathgrid::Point &nextPoint = *mPath.begin(); + Ogre::Vector3 dir = MakeOgreVector3(nextPoint) - Ogre::Vector3(x,y,z); + + return -Ogre::Math::ASin(dir.z / dir.length()).valueDegrees(); + } + bool PathFinder::checkPathCompleted(float x, float y, float tolerance) { if(mPath.empty()) @@ -291,19 +334,18 @@ namespace MWMechanics // see header for the rationale void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, - bool allowShortcuts) + const MWWorld::CellStore* cell) { if (mPath.size() < 2) { // if path has one point, then it's the destination. // don't need to worry about bad path for this case - buildPath(startPoint, endPoint, cell, allowShortcuts); + buildPath(startPoint, endPoint, cell); } else { const ESM::Pathgrid::Point oldStart(*getPath().begin()); - buildPath(startPoint, endPoint, cell, allowShortcuts); + buildPath(startPoint, endPoint, cell); if (mPath.size() >= 2) { // if 2nd waypoint of new path == 1st waypoint of old, diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 644d79236..c978c9afa 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -15,8 +15,24 @@ namespace MWWorld namespace MWMechanics { - float distance(ESM::Pathgrid::Point point, float x, float y, float); + float distance(ESM::Pathgrid::Point point, float x, float y, float z); float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b); + float getZAngleToDir(const Ogre::Vector3& dir); + float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f); + float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); + float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest); + + const float PATHFIND_Z_REACH = 50.0f; + //static const float sMaxSlope = 49.0f; // duplicate as in physicssystem + // distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid + const float PATHFIND_CAUTION_DIST = 500.0f; + // distance after which actor (failed previously to shortcut) will try again + const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f; + + // cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target; + // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH + bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY); + class PathFinder { public: @@ -39,13 +55,15 @@ namespace MWMechanics void clearPath(); void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts = true); + const MWWorld::CellStore* cell); bool checkPathCompleted(float x, float y, float tolerance=32.f); ///< \Returns true if we are within \a tolerance units of the last path point. float getZAngleToNext(float x, float y) const; + float getXAngleToNext(float x, float y, float z) const; + bool isPathConstructed() const { return !mPath.empty(); @@ -69,9 +87,9 @@ namespace MWMechanics Which results in NPC "running in a circle" back to the just passed waypoint. */ void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, - const MWWorld::CellStore* cell, bool allowShortcuts = true); + const MWWorld::CellStore* cell); - void addPointToPath(ESM::Pathgrid::Point &point) + void addPointToPath(const ESM::Pathgrid::Point &point) { mPath.push_back(point); }