diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 161c9700f1..98ac5ee149 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -12,17 +12,23 @@ #include "steering.hpp" MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("") { + mTimer = 0; + mStuckTimer = 0; } MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) -: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) { + mTimer = 0; + mStuckTimer = 0; } MWMechanics::AiFollow::AiFollow(const std::string &actorId) -: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) +: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("") { + mTimer = 0; + mStuckTimer = 0; } bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) @@ -31,10 +37,6 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) if(target == MWWorld::Ptr()) return true; //Target doesn't exist - mTimer = mTimer + duration; //Update timer - mStuckTimer = mStuckTimer + duration; //Update stuck timer - mTotalTime = mTotalTime + duration; //Update total time following - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor if(!mAlwaysFollow) //Update if you only follow for a bit @@ -60,74 +62,19 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) } //Set the target desition from the actor - ESM::Pathgrid::Point dest; - dest.mX = target.getRefData().getPosition().pos[0]; - dest.mY = target.getRefData().getPosition().pos[1]; - dest.mZ = target.getRefData().getPosition().pos[2]; + ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - //Current position, for pathfilding stuff - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; + pathTo(actor, dest, duration); //Go to the destination - //Build the path to get to the destination - if(mPathFinder.getPath().empty()) - mPathFinder.buildPath(start, dest, actor.getCell(), true); - - //*********************** - // Checks if you can't get to the end position at all - //*********************** - if(mTimer > 0.25) - { - if(!mPathFinder.getPath().empty()) //Path has points in it - { - ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path - - if((dest.mX - lastPos.mX)*(dest.mX - lastPos.mX) - +(dest.mY - lastPos.mY)*(dest.mY - lastPos.mY) - +(dest.mZ - lastPos.mZ)*(dest.mZ - lastPos.mZ) - > 100*100) //End of the path is far from the destination - mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go - } - - mTimer = 0; - } - - //************************ - // Checks if you aren't moving; you're stuck - //************************ - if(mStuckTimer>0.5) //Checks every half of a second - { - if((mStuckPos.pos[0] - pos.pos[0])*(mStuckPos.pos[0] - pos.pos[0]) - +(mStuckPos.pos[1] - pos.pos[1])*(mStuckPos.pos[1] - pos.pos[1]) - +(mStuckPos.pos[2] - pos.pos[2])*(mStuckPos.pos[2] - pos.pos[2]) < 100) //NPC is stuck - mPathFinder.buildPath(start, dest, actor.getCell(), true); - - mStuckTimer = 0; - mStuckPos = pos; - } - - //Checks if the path isn't over, turn tomards the direction that you're going - if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) - { - zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); - } - - if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2]) - < 100*100) + 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; else actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Check if you're far away - if((dest.mX - start.mX)*(dest.mX - start.mX) - +(dest.mY - start.mY)*(dest.mY - start.mY) - +(dest.mZ - start.mZ)*(dest.mZ - start.mZ) > 1000*1000) + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) > 1000) actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run - else if((dest.mX - start.mX)*(dest.mX - start.mX) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold - +(dest.mY - start.mY)*(dest.mY - start.mY) - +(dest.mZ - start.mZ)*(dest.mZ - start.mZ) < 800*800) + else if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 800) //Have a bit of a dead zone, otherwise npc will constantly flip between running and not when right on the edge of the running threshhold actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, false); //make NPC walk return false; diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 4eb3e3757a..531082357a 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -40,14 +40,6 @@ namespace MWMechanics float mZ; std::string mActorId; std::string mCellId; - - float mTimer; - float mStuckTimer; - float mTotalTime; - - ESM::Position mStuckPos; - - PathFinder mPathFinder; }; } #endif diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8193a670be..afe99e2c0b 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -1,4 +1,95 @@ #include "aipackage.hpp" +#include +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/cellstore.hpp" +#include "creaturestats.hpp" +#include "movement.hpp" + +#include + +#include "steering.hpp" + MWMechanics::AiPackage::~AiPackage() {} + + +bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration) +{ + //Update various Timers + mTimer = mTimer + duration; //Update timer + mStuckTimer = mStuckTimer + duration; //Update stuck timer + mTotalTime = mTotalTime + duration; //Update total time following + + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + + /// Stops the actor when it gets too close to a unloaded cell + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const ESM::Cell *cell = actor.getCell()->getCell(); + Movement &movement = actor.getClass().getMovementSettings(actor); + + //Ensure pursuer doesn't leave loaded cells + if(cell->mData.mX != player.getCell()->getCell()->mData.mX) + { + int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); + //check if actor is near the border of an inactive cell. If so, stop walking. + if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) + { + movement.mPosition[1] = 0; + return false; + } + } + if(cell->mData.mY != player.getCell()->getCell()->mData.mY) + { + int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); + //check if actor is near the border of an inactive cell. If so, stop walking. + if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) + { + movement.mPosition[1] = 0; + return false; + } + } + } + + //Start position + ESM::Pathgrid::Point start = pos.pos; + + //*********************** + /// 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 + //*********************** + if(mTimer > 0.25) + { + mPathFinder.buildPath(start, dest, actor.getCell(), true); //Rebuild path, in case the target has moved + if(!mPathFinder.getPath().empty()) //Path has points in it + { + ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path + + if(distance(dest, lastPos) > 100) //End of the path is far from the destination + mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go + } + + mTimer = 0; + } + + //************************ + /// Checks if you aren't moving; attempts to unstick you + //************************ + if(mStuckTimer>0.5) //Checks every half of a second + { + if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < 10) //NPC hasn't moved much is half a second, he's stuck + mPathFinder.buildPath(start, dest, actor.getCell(), true); + + mStuckTimer = 0; + mStuckPos = pos; + } + + //Checks if the path isn't over, turn tomards the direction that you're going + if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) + { + zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); + } +} diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index b3a7facee2..e36b51f691 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -1,6 +1,9 @@ #ifndef GAME_MWMECHANICS_AIPACKAGE_H #define GAME_MWMECHANICS_AIPACKAGE_H +#include "pathfinding.hpp" +#include "../../../components/esm/defs.hpp" + namespace MWWorld { class Ptr; @@ -38,8 +41,21 @@ namespace MWMechanics /// \see enum TypeId virtual int getTypeId() const = 0; - /// Higher number is higher priority (0 beeing the lowest) + /// Higher number is higher priority (0 being the lowest) virtual unsigned int getPriority() const {return 0;} + + 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); + + PathFinder mPathFinder; + + float mTimer; + float mStuckTimer; + float mTotalTime; + + ESM::Position mStuckPos; }; } diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 0c10bd81dd..4e0bc71046 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -21,75 +21,28 @@ MWMechanics::AiPursue *MWMechanics::AiPursue::clone() const } bool MWMechanics::AiPursue::execute (const MWWorld::Ptr& actor, float duration) { - MWBase::World *world = MWBase::Environment::get().getWorld(); - ESM::Position pos = actor.getRefData().getPosition(); - Movement &movement = actor.getClass().getMovementSettings(actor); - const ESM::Cell *cell = actor.getCell()->getCell(); - actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true); + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow - MWWorld::Ptr player = world->getPlayerPtr(); - if(cell->mData.mX != player.getCell()->getCell()->mData.mX) - { - int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) > - sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } - if(cell->mData.mY != player.getCell()->getCell()->mData.mY) - { - int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY); - //check if actor is near the border of an inactive cell. If so, stop walking. - if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) > - sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f)) - { - movement.mPosition[1] = 0; - return false; - } - } + if(target == MWWorld::Ptr()) + return true; //Target doesn't exist - // Big TODO: Sync this with current AiFollow. Move common code to a shared base class or helpers (applies to all AI packages, way too much duplicated code) + //Set the target desition from the actor + ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - MWWorld::Ptr target = world->getPtr(mObjectId,false); - ESM::Position targetPos = target.getRefData().getPosition(); + pathTo(actor, dest, duration); //Go to the destination - bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; - if(!mPathFinder.isPathConstructed() || cellChange || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) - { - mCellX = cell->mData.mX; - mCellY = cell->mData.mY; - - ESM::Pathgrid::Point dest; - dest.mX = targetPos.pos[0]; - dest.mY = targetPos.pos[1]; - dest.mZ = targetPos.pos[2]; - - ESM::Pathgrid::Point start; - start.mX = pos.pos[0]; - start.mY = pos.pos[1]; - start.mZ = pos.pos[2]; - - mPathFinder.buildPath(start, dest, actor.getCell(), true); - } - - if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+ - (pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+ - (pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 100*100) - { - movement.mPosition[1] = 0; - MWWorld::Ptr target = world->getPtr(mObjectId,false); - MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); + 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; + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); + MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); //Arrest player return true; } + else + actor.getClass().getMovementSettings(actor).mPosition[1] = 1; - float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); - zTurn(actor, Ogre::Degree(zAngle)); - MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; - movement.mPosition[1] = 1; + actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run return false; } diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 8a471228ed..7d1ea5bb9d 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -9,18 +9,12 @@ namespace MWMechanics { /// \brief Makes the actor very closely follow the actor - /** Used for getting closer to fight, or to arrest (I think?) **/ - class AiPersue : public AiPackage + /** Used for arresting players. Causes the actor to run to the pursued actor and activate them, to arrest them. **/ + class AiPursue : public AiPackage { public: ///Constructor /** \param objectId Actor to pursue **/ - AiPersue(const std::string &objectId); - virtual AiPersue *clone() const; - - class AiPursue : public AiPackage - { - public: AiPursue(const std::string &objectId); virtual AiPursue *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); @@ -28,8 +22,6 @@ namespace MWMechanics private: std::string mObjectId; - - PathFinder mPathFinder; int mCellX; int mCellY; }; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index cd03939bd0..d77a35ea48 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -11,30 +11,6 @@ namespace { - float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) - { - x -= point.mX; - y -= point.mY; - z -= point.mZ; - return sqrt(x * x + y * y + 0.1 * z * z); - } - - float distance(ESM::Pathgrid::Point point, float x, float y, float z) - { - x -= point.mX; - y -= point.mY; - z -= point.mZ; - return sqrt(x * x + y * y + z * z); - } - - float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) - { - float x = a.mX - b.mX; - float y = a.mY - b.mY; - float z = a.mZ - b.mZ; - return sqrt(x * x + y * y + z * z); - } - // Slightly cheaper version for comparisons. // Caller needs to be careful for very short distances (i.e. less than 1) // or when accumuating the results i.e. (a + b)^2 != a^2 + b^2 @@ -114,6 +90,30 @@ namespace namespace MWMechanics { + float distanceZCorrected(ESM::Pathgrid::Point point, float x, float y, float z) + { + x -= point.mX; + y -= point.mY; + z -= point.mZ; + return sqrt(x * x + y * y + 0.1 * z * z); + } + + float distance(ESM::Pathgrid::Point point, float x, float y, float z) + { + x -= point.mX; + y -= point.mY; + z -= point.mZ; + return sqrt(x * x + y * y + z * z); + } + + float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b) + { + float x = a.mX - b.mX; + float y = a.mY - b.mY; + float z = a.mZ - b.mZ; + return sqrt(x * x + y * y + z * z); + } + PathFinder::PathFinder() : mIsPathConstructed(false), mPathgrid(NULL), diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 29577542e6..603a04f8c5 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -13,6 +13,8 @@ namespace MWWorld namespace MWMechanics { + float distance(ESM::Pathgrid::Point point, float x, float y, float); + float distance(ESM::Pathgrid::Point a, ESM::Pathgrid::Point b); class PathFinder { public: diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 3b5330e9fd..5b8ce3eb2c 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -8,6 +8,20 @@ namespace ESM { unsigned int Pathgrid::sRecordId = REC_PGRD; + Pathgrid::Point& Pathgrid::Point::operator=(const float rhs[]) { + mX = rhs[0]; + mY = rhs[1]; + mZ = rhs[2]; + return *this; + } + Pathgrid::Point::Point(const float rhs[]) { + mX = rhs[0]; + mY = rhs[1]; + mZ = rhs[2]; + } + Pathgrid::Point::Point():mX(0),mY(0),mZ(0) { + } + void Pathgrid::load(ESMReader &esm) { esm.getHNT(mData, "DATA", 12); diff --git a/components/esm/loadpgrd.hpp b/components/esm/loadpgrd.hpp index 9ee49552db..b940c27e60 100644 --- a/components/esm/loadpgrd.hpp +++ b/components/esm/loadpgrd.hpp @@ -31,6 +31,9 @@ struct Pathgrid unsigned char mAutogenerated; // autogenerated vs. user coloring flag? unsigned char mConnectionNum; // number of connections for this point short mUnknown; + Point& operator=(const float[]); + Point(const float[]); + Point(); }; // 16 bytes struct Edge // path grid edge