From a54ac579a554d95823d3f8153b74c9600b800034 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 12 Jun 2014 23:27:04 +0200 Subject: [PATCH] Savegame: Store AiSequence --- apps/openmw/mwmechanics/aiactivate.cpp | 59 ++++-- apps/openmw/mwmechanics/aiactivate.hpp | 21 ++- apps/openmw/mwmechanics/aiavoiddoor.cpp | 8 +- apps/openmw/mwmechanics/aiavoiddoor.hpp | 2 + apps/openmw/mwmechanics/aicombat.cpp | 123 +++++-------- apps/openmw/mwmechanics/aicombat.hpp | 20 +- apps/openmw/mwmechanics/aiescort.cpp | 57 +++--- apps/openmw/mwmechanics/aiescort.hpp | 15 +- apps/openmw/mwmechanics/aifollow.cpp | 48 ++++- apps/openmw/mwmechanics/aifollow.hpp | 28 ++- apps/openmw/mwmechanics/aipackage.cpp | 2 - apps/openmw/mwmechanics/aipackage.hpp | 19 +- apps/openmw/mwmechanics/aipursue.cpp | 19 ++ apps/openmw/mwmechanics/aipursue.hpp | 12 ++ apps/openmw/mwmechanics/aisequence.cpp | 77 +++++++- apps/openmw/mwmechanics/aisequence.hpp | 11 ++ apps/openmw/mwmechanics/aitravel.cpp | 24 +++ apps/openmw/mwmechanics/aitravel.hpp | 16 +- apps/openmw/mwmechanics/aiwander.cpp | 113 ++++++++---- apps/openmw/mwmechanics/aiwander.hpp | 22 ++- apps/openmw/mwmechanics/creaturestats.cpp | 2 + apps/openmw/mwscript/aiextensions.cpp | 3 +- components/CMakeLists.txt | 1 + components/esm/aisequence.cpp | 211 ++++++++++++++++++++++ components/esm/aisequence.hpp | 154 ++++++++++++++++ components/esm/creaturestats.cpp | 2 + components/esm/creaturestats.hpp | 3 + 27 files changed, 869 insertions(+), 203 deletions(-) create mode 100644 components/esm/aisequence.cpp create mode 100644 components/esm/aisequence.hpp diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 3dfacb853..1cdda24c0 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -1,5 +1,7 @@ #include "aiactivate.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -19,26 +21,26 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const return new AiActivate(*this); } bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration) -{ - 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 - - if(target == MWWorld::Ptr()) - return true; //Target doesn't exist - - //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]) < 200) { //Stop when you get close - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); - target.getClass().activate(target,actor).get()->execute(actor); //Arrest player - return true; - } - else { - pathTo(actor, dest, duration); //Go to the destination - } - +{ + 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 + + if(target == MWWorld::Ptr()) + return true; //Target doesn't exist + + //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]) < 200) { //Stop when you get close + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false); + target.getClass().activate(target,actor).get()->execute(actor); //Arrest player + return true; + } + else { + pathTo(actor, dest, duration); //Go to the destination + } + return false; } @@ -46,3 +48,20 @@ int MWMechanics::AiActivate::getTypeId() const { return TypeIdActivate; } + +void MWMechanics::AiActivate::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + std::auto_ptr activate(new ESM::AiSequence::AiActivate()); + activate->mTargetId = mObjectId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Activate; + package.mPackage = activate.release(); + sequence.mPackages.push_back(package); +} + +MWMechanics::AiActivate::AiActivate(const ESM::AiSequence::AiActivate *activate) + : mObjectId(activate->mTargetId) +{ + +} diff --git a/apps/openmw/mwmechanics/aiactivate.hpp b/apps/openmw/mwmechanics/aiactivate.hpp index 0e660e967..8003c2a36 100644 --- a/apps/openmw/mwmechanics/aiactivate.hpp +++ b/apps/openmw/mwmechanics/aiactivate.hpp @@ -6,20 +6,33 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiActivate; +} +} + namespace MWMechanics -{ - /// \brief Causes actor to walk to activatable object and activate it +{ + /// \brief Causes actor to walk to activatable object and activate it /** Will activate when close to object **/ class AiActivate : public AiPackage { - public: - /// Constructor + public: + /// Constructor /** \param objectId Reference to object to activate **/ AiActivate(const std::string &objectId); + + AiActivate(const ESM::AiSequence::AiActivate* activate); + virtual AiActivate *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; + virtual void writeState(ESM::AiSequence::AiSequence& sequence) const; + private: std::string mObjectId; int mCellX; diff --git a/apps/openmw/mwmechanics/aiavoiddoor.cpp b/apps/openmw/mwmechanics/aiavoiddoor.cpp index ea6f296cc..bab8bca28 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.cpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.cpp @@ -78,8 +78,14 @@ MWMechanics::AiAvoidDoor *MWMechanics::AiAvoidDoor::clone() const return new AiAvoidDoor(*this); } - int MWMechanics::AiAvoidDoor::getTypeId() const +int MWMechanics::AiAvoidDoor::getTypeId() const { return TypeIdAvoidDoor; } +unsigned int MWMechanics::AiAvoidDoor::getPriority() const +{ + return 2; +} + + diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index d2a2e33a1..2374fbc1b 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -24,6 +24,8 @@ namespace MWMechanics virtual int getTypeId() const; + virtual unsigned int getPriority() const; + private: float mDuration; MWWorld::Ptr mDoorPtr; diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 9ec770c9d..f4aae14d5 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwworld/class.hpp" #include "../mwworld/timestamp.hpp" @@ -81,23 +82,22 @@ namespace MWMechanics // NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp AiCombat::AiCombat(const MWWorld::Ptr& actor) : - mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()), - mTimerAttack(0), - mTimerReact(0), - mTimerCombatMove(0), - mFollowTarget(false), - mReadyToAttack(false), - mAttack(false), - mCombatMove(false), - mMovement(), - mForceNoShortcut(false), - mShortcutFailPos(), - mBackOffDoor(false), - mCell(NULL), - mDoorIter(actor.getCell()->get().mList.end()), - mDoors(actor.getCell()->get()), - mDoorCheckDuration(0) + mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) { + init(); + } + + void AiCombat::init() + { + mTimerAttack = 0; + mTimerReact = 0; + mTimerCombatMove = 0; + mFollowTarget = false; + mReadyToAttack = false; + mAttack = false; + mCombatMove = false; + mForceNoShortcut = false; + mCell = NULL; } /* @@ -511,70 +511,22 @@ namespace MWMechanics // coded at 250ms or 1/4 second // // TODO: Add a parameter to vary DURATION_SAME_SPOT? - MWWorld::CellStore *cell = actor.getCell(); if((distToTarget > rangeAttack || mFollowTarget) && mObstacleCheck.check(actor, tReaction)) // check if evasive action needed { - // first check if we're walking into a door - mDoorCheckDuration += 1.0f; // add time taken for obstacle check - if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL && !cell->getCell()->isExterior()) - { - mDoorCheckDuration = 0; - // Check all the doors in this cell - mDoors = cell->get(); // update - mDoorIter = mDoors.mList.begin(); - for (; mDoorIter != mDoors.mList.end(); ++mDoorIter) - { - MWWorld::LiveCellRef& ref = *mDoorIter; - float minSqr = 1.3*1.3*MIN_DIST_TO_DOOR_SQUARED; // for legibility - if(vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.getPosition().pos)) < minSqr && - ref.mData.getLocalRotation().rot[2] < 0.4f) // even small opening - { - //std::cout<<"closed door id \""<getCell()->isExterior() && !mDoors.mList.empty()) - { - MWWorld::LiveCellRef& ref = *mDoorIter; - float minSqr = 1.6 * 1.6 * MIN_DIST_TO_DOOR_SQUARED; // for legibility - // TODO: add reaction to checking open doors - if(mBackOffDoor && - vActorPos.squaredDistance(Ogre::Vector3(ref.mRef.getPosition().pos)) < minSqr) - { - mMovement.mPosition[1] = -0.2; // back off, but slowly - } - else if(mBackOffDoor && - mDoorIter != mDoors.mList.end() && - ref.mData.getLocalRotation().rot[2] >= 1) - { - mDoorIter = mDoors.mList.end(); - mBackOffDoor = false; - //std::cout<<"open door id \""<mTargetActorId; + init(); + } + + void AiCombat::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr combat(new ESM::AiSequence::AiCombat()); + combat->mTargetActorId = mTargetActorId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Combat; + package.mPackage = combat.release(); + sequence.mPackages.push_back(package); + } } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 4b728ff22..061a6a3d3 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -12,6 +12,14 @@ #include "../mwbase/world.hpp" +namespace ESM +{ + namespace AiSequence + { + struct AiCombat; + } +} + namespace MWMechanics { /// \brief Causes the actor to fight another actor @@ -22,6 +30,10 @@ namespace MWMechanics /** \param actor Actor to fight **/ AiCombat(const MWWorld::Ptr& actor); + AiCombat (const ESM::AiSequence::AiCombat* combat); + + void init(); + virtual AiCombat *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); @@ -33,6 +45,8 @@ namespace MWMechanics ///Returns target ID MWWorld::Ptr getTarget() const; + virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + private: PathFinder mPathFinder; // controls duration of the actual strike @@ -46,7 +60,6 @@ namespace MWMechanics bool mReadyToAttack, mAttack; bool mFollowTarget; bool mCombatMove; - bool mBackOffDoor; bool mForceNoShortcut; ESM::Position mShortcutFailPos; @@ -57,11 +70,6 @@ namespace MWMechanics const MWWorld::CellStore* mCell; ObstacleCheck mObstacleCheck; - float mDoorCheckDuration; - // TODO: for some reason mDoors.searchViaHandle() returns - // null pointers, workaround by keeping an iterator - MWWorld::CellRefList::List::iterator mDoorIter; - MWWorld::CellRefList& mDoors; void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target); }; diff --git a/apps/openmw/mwmechanics/aiescort.cpp b/apps/openmw/mwmechanics/aiescort.cpp index 07bb9726e..3f5724077 100644 --- a/apps/openmw/mwmechanics/aiescort.cpp +++ b/apps/openmw/mwmechanics/aiescort.cpp @@ -1,12 +1,13 @@ #include "aiescort.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/timestamp.hpp" #include "steering.hpp" #include "movement.hpp" @@ -20,7 +21,7 @@ namespace MWMechanics { AiEscort::AiEscort(const std::string &actorId, int duration, float x, float y, float z) - : mActorId(actorId), mX(x), mY(y), mZ(z), mDuration(duration) + : mActorId(actorId), mX(x), mY(y), mZ(z), mRemainingDuration(duration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { @@ -29,32 +30,29 @@ namespace MWMechanics // The CS Help File states that if a duration is given, the AI package will run for that long // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) - mDuration = 0; - - else - { - MWWorld::TimeStamp startTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mStartingSecond = ((startTime.getHour() - int(startTime.getHour())) * 100); - } + mRemainingDuration = 0; } AiEscort::AiEscort(const std::string &actorId, const std::string &cellId,int duration, float x, float y, float z) - : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mDuration(duration) + : mActorId(actorId), mCellId(cellId), mX(x), mY(y), mZ(z), mRemainingDuration(duration) , mCellX(std::numeric_limits::max()) , mCellY(std::numeric_limits::max()) { mMaxDist = 470; // The CS Help File states that if a duration is given, the AI package will run for that long - // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. + // BUT if a location is given, it "trumps" the duration so it will simply escort to that location. if(mX != 0 || mY != 0 || mZ != 0) - mDuration = 0; + mRemainingDuration = 0; + } - else - { - MWWorld::TimeStamp startTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - mStartingSecond = ((startTime.getHour() - int(startTime.getHour())) * 100); - } + AiEscort::AiEscort(const ESM::AiSequence::AiEscort *escort) + : mActorId(escort->mTargetId), mX(escort->mData.mX), mY(escort->mData.mY), mZ(escort->mData.mZ) + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) + , mCellId(escort->mCellId) + , mRemainingDuration(escort->mRemainingDuration) + { } @@ -67,11 +65,10 @@ namespace MWMechanics { // If AiEscort has ran for as long or longer then the duration specified // and the duration is not infinite, the package is complete. - if(mDuration != 0) + if(mRemainingDuration != 0) { - MWWorld::TimeStamp current = MWBase::Environment::get().getWorld()->getTimeStamp(); - unsigned int currentSecond = ((current.getHour() - int(current.getHour())) * 100); - if(currentSecond - mStartingSecond >= mDuration) + mRemainingDuration -= duration; + if (duration <= 0) return true; } @@ -89,7 +86,7 @@ namespace MWMechanics if(distanceBetweenResult <= mMaxDist * mMaxDist) { - if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete + if(pathTo(actor,ESM::Pathgrid::Point(mX,mY,mZ),duration)) //Returns true on path complete return true; mMaxDist = 470; } @@ -108,5 +105,21 @@ namespace MWMechanics { return TypeIdEscort; } + + void AiEscort::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr escort(new ESM::AiSequence::AiEscort()); + escort->mData.mX = mX; + escort->mData.mY = mY; + escort->mData.mZ = mZ; + escort->mTargetId = mActorId; + escort->mRemainingDuration = mRemainingDuration; + escort->mCellId = mCellId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Escort; + package.mPackage = escort.release(); + sequence.mPackages.push_back(package); + } } diff --git a/apps/openmw/mwmechanics/aiescort.hpp b/apps/openmw/mwmechanics/aiescort.hpp index 3771417fa..820df969f 100644 --- a/apps/openmw/mwmechanics/aiescort.hpp +++ b/apps/openmw/mwmechanics/aiescort.hpp @@ -6,6 +6,14 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiEscort; +} +} + namespace MWMechanics { /// \brief AI Package to have an NPC lead the player to a specific point @@ -21,12 +29,16 @@ namespace MWMechanics \implement AiEscortCell **/ AiEscort(const std::string &actorId,const std::string &cellId,int duration, float x, float y, float z); + AiEscort(const ESM::AiSequence::AiEscort* escort); + virtual AiEscort *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; + void writeState(ESM::AiSequence::AiSequence &sequence) const; + private: std::string mActorId; std::string mCellId; @@ -34,8 +46,7 @@ namespace MWMechanics float mY; float mZ; float mMaxDist; - unsigned int mStartingSecond; - unsigned int mDuration; + float mRemainingDuration; // In seconds PathFinder mPathFinder; int mCellX; diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f1296a949..5ab7e1730 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -1,5 +1,9 @@ #include "aifollow.hpp" + #include + +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" @@ -12,16 +16,16 @@ #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(""), AiPackage() +: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("") { } 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), AiPackage() +: mAlwaysFollow(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId) { } MWMechanics::AiFollow::AiFollow(const std::string &actorId) -: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), AiPackage() +: mAlwaysFollow(true), mRemainingDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("") { } @@ -35,8 +39,13 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) if(!mAlwaysFollow) //Update if you only follow for a bit { - if(mTotalTime > mDuration && mDuration != 0) //Check if we've run out of time - return true; + //Check if we've run out of time + if (mRemainingDuration != 0) + { + mRemainingDuration -= duration; + if (duration <= 0) + return true; + } if((pos.pos[0]-mX)*(pos.pos[0]-mX) + (pos.pos[1]-mY)*(pos.pos[1]-mY) + @@ -55,7 +64,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) } } - //Set the target desition from the actor + //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]) < 100) //Stop when you get close @@ -83,7 +92,32 @@ MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const return new AiFollow(*this); } - int MWMechanics::AiFollow::getTypeId() const +int MWMechanics::AiFollow::getTypeId() const { return TypeIdFollow; } + +void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + std::auto_ptr follow(new ESM::AiSequence::AiFollow()); + follow->mData.mX = mX; + follow->mData.mY = mY; + follow->mData.mZ = mZ; + follow->mTargetId = mActorId; + follow->mRemainingDuration = mRemainingDuration; + follow->mCellId = mCellId; + follow->mAlwaysFollow = mAlwaysFollow; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Follow; + package.mPackage = follow.release(); + sequence.mPackages.push_back(package); +} + +MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) + : mAlwaysFollow(follow->mAlwaysFollow), mRemainingDuration(follow->mRemainingDuration) + , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) + , mActorId(follow->mTargetId), mCellId(follow->mCellId) +{ + +} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index 10a381410..e9587b36e 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -6,13 +6,21 @@ #include "pathfinding.hpp" #include +namespace ESM +{ +namespace AiSequence +{ + struct AiFollow; +} +} + namespace MWMechanics { /// \brief AiPackage for an actor to follow another actor/the PC /** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely - **/ - class AiFollow : public AiPackage - { + **/ + class AiFollow : public AiPackage + { public: /// Follow Actor for duration or until you arrive at a world position AiFollow(const std::string &ActorId,float duration, float X, float Y, float Z); @@ -21,6 +29,8 @@ namespace MWMechanics /// Follow Actor indefinitively AiFollow(const std::string &ActorId); + AiFollow(const ESM::AiSequence::AiFollow* follow); + virtual AiFollow *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); @@ -30,16 +40,18 @@ namespace MWMechanics /// Returns the actor being followed std::string getFollowedActor(); + virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ bool mAlwaysFollow; - float mDuration; + float mRemainingDuration; // Seconds float mX; float mY; float mZ; std::string mActorId; - std::string mCellId; - }; -} -#endif + std::string mCellId; + }; +} +#endif diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 2144aa11d..7790942b2 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -26,8 +26,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po //Update various Timers mTimer += duration; //Update timer mStuckTimer += duration; //Update stuck timer - mTotalTime += duration; //Update total time following - ESM::Position pos = actor.getRefData().getPosition(); //position of the actor diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 055958384..983777c0a 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -12,6 +12,14 @@ namespace MWWorld class Ptr; } +namespace ESM +{ + namespace AiSequence + { + class AiSequence; + } +} + namespace MWMechanics { /// \brief Base class for AI packages @@ -51,6 +59,8 @@ namespace MWMechanics /// Higher number is higher priority (0 being the lowest) virtual unsigned int getPriority() const {return 0;} + virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {} + protected: /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ @@ -60,12 +70,11 @@ namespace MWMechanics ObstacleCheck mObstacleCheck; float mDoorCheckDuration; - float mTimer; - float mStuckTimer; - float mTotalTime; + float mTimer; + float mStuckTimer; + + MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door - MWWorld::Ptr mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door - ESM::Position mStuckPos; ESM::Pathgrid::Point mPrevDest; }; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index cd9d34e08..60f671c12 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -1,5 +1,7 @@ #include "aipursue.hpp" +#include + #include "../mwbase/environment.hpp" #include "../mwworld/class.hpp" @@ -18,6 +20,12 @@ AiPursue::AiPursue(const MWWorld::Ptr& actor) : mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId()) { } + +AiPursue::AiPursue(const ESM::AiSequence::AiPursue *pursue) + : mTargetActorId(pursue->mTargetActorId) +{ +} + AiPursue *MWMechanics::AiPursue::clone() const { return new AiPursue(*this); @@ -57,4 +65,15 @@ MWWorld::Ptr AiPursue::getTarget() const return MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } +void AiPursue::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + std::auto_ptr pursue(new ESM::AiSequence::AiPursue()); + pursue->mTargetActorId = mTargetActorId; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Pursue; + package.mPackage = pursue.release(); + sequence.mPackages.push_back(package); +} + } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 27affc9ac..18a22b676 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -7,6 +7,14 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiPursue; +} +} + namespace MWMechanics { /// \brief Makes the actor very closely follow the actor @@ -20,12 +28,16 @@ namespace MWMechanics /** \param actor Actor to pursue **/ AiPursue(const MWWorld::Ptr& actor); + AiPursue(const ESM::AiSequence::AiPursue* pursue); + virtual AiPursue *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); virtual int getTypeId() const; MWWorld::Ptr getTarget() const; + virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + private: int mTargetActorId; // The actor to pursue diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 8a6a69b27..2d4dc2732 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -11,6 +11,8 @@ #include "aicombat.hpp" #include "aipursue.hpp" +#include + #include "../mwworld/class.hpp" #include "creaturestats.hpp" #include "npcstats.hpp" @@ -258,7 +260,8 @@ void AiSequence::fill(const ESM::AIPackageList &list) if (it->mType == ESM::AI_Wander) { ESM::AIWander data = it->mWander; - std::vector idles; + std::vector idles; + idles.reserve(8); for (int i=0; i<8; ++i) idles.push_back(data.mIdle[i]); package = new MWMechanics::AiWander(data.mDistance, data.mDuration, data.mTimeOfDay, idles, data.mShouldRepeat); @@ -287,4 +290,76 @@ void AiSequence::fill(const ESM::AIPackageList &list) } } +void AiSequence::writeState(ESM::AiSequence::AiSequence &sequence) const +{ + for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) + { + (*iter)->writeState(sequence); + } +} + +void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) +{ + clear(); + + for (std::vector::const_iterator it = sequence.mPackages.begin(); + it != sequence.mPackages.end(); ++it) + { + switch (it->mType) + { + case ESM::AiSequence::Ai_Wander: + { + MWMechanics::AiWander* wander = new AiWander( + dynamic_cast(it->mPackage)); + mPackages.push_back(wander); + break; + } + case ESM::AiSequence::Ai_Travel: + { + MWMechanics::AiTravel* travel = new AiTravel( + dynamic_cast(it->mPackage)); + mPackages.push_back(travel); + break; + } + case ESM::AiSequence::Ai_Escort: + { + MWMechanics::AiEscort* escort = new AiEscort( + dynamic_cast(it->mPackage)); + mPackages.push_back(escort); + break; + } + case ESM::AiSequence::Ai_Follow: + { + MWMechanics::AiFollow* follow = new AiFollow( + dynamic_cast(it->mPackage)); + mPackages.push_back(follow); + break; + } + case ESM::AiSequence::Ai_Activate: + { + MWMechanics::AiActivate* activate = new AiActivate( + dynamic_cast(it->mPackage)); + mPackages.push_back(activate); + break; + } + case ESM::AiSequence::Ai_Combat: + { + MWMechanics::AiCombat* combat = new AiCombat( + dynamic_cast(it->mPackage)); + mPackages.push_back(combat); + break; + } + case ESM::AiSequence::Ai_Pursue: + { + MWMechanics::AiPursue* pursue = new AiPursue( + dynamic_cast(it->mPackage)); + mPackages.push_back(pursue); + break; + } + default: + break; + } + } +} + } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 41a280da8..b789d33cd 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -10,6 +10,14 @@ namespace MWWorld class Ptr; } +namespace ESM +{ + namespace AiSequence + { + class AiSequence; + } +} + namespace MWMechanics { class AiPackage; @@ -90,6 +98,9 @@ namespace MWMechanics /** Typically used for loading from the ESM \see ESM::AIPackageList **/ void fill (const ESM::AIPackageList& list); + + void writeState (ESM::AiSequence::AiSequence& sequence) const; + void readState (const ESM::AiSequence::AiSequence& sequence); }; } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 024656b38..db137037d 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,5 +1,7 @@ #include "aitravel.hpp" +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -19,6 +21,15 @@ namespace MWMechanics { } + AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel) + : mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ) + , mPathFinder() + , mCellX(std::numeric_limits::max()) + , mCellY(std::numeric_limits::max()) + { + + } + AiTravel *MWMechanics::AiTravel::clone() const { return new AiTravel(*this); @@ -92,5 +103,18 @@ namespace MWMechanics { return TypeIdTravel; } + + void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr travel(new ESM::AiSequence::AiTravel()); + travel->mData.mX = mX; + travel->mData.mY = mY; + travel->mData.mZ = mZ; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Travel; + package.mPackage = travel.release(); + sequence.mPackages.push_back(package); + } } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index ea7f1dc32..91ee30253 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -5,14 +5,26 @@ #include "pathfinding.hpp" +namespace ESM +{ +namespace AiSequence +{ + struct AiTravel; +} +} + namespace MWMechanics -{ +{ /// \brief Causes the AI to travel to the specified point class AiTravel : public AiPackage { - public: + public: /// Default constructor AiTravel(float x, float y, float z); + AiTravel(const ESM::AiSequence::AiTravel* travel); + + void writeState(ESM::AiSequence::AiSequence &sequence) const; + virtual AiTravel *clone() const; virtual bool execute (const MWWorld::Ptr& actor,float duration); diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 1c870bda4..6a68397fd 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" @@ -21,31 +23,28 @@ namespace MWMechanics static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed static const float DOOR_CHECK_INTERVAL = 1.5f; - AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): + AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) - , mCellX(std::numeric_limits::max()) - , mCellY(std::numeric_limits::max()) - , mXCell(0) - , mYCell(0) - , mCell(NULL) - , mStuckCount(0) // TODO: maybe no longer needed - , mDoorCheckDuration(0) - , mTrimCurrentNode(false) - , mReaction(0) - , mGreetDistanceMultiplier(0) - , mGreetDistanceReset(0) - , mChance(0) - , mRotate(false) - , mTargetAngle(0) - , mSaidGreeting(false) - , mHasReturnPosition(false) - , mReturnPosition(0,0,0) { - for(unsigned short counter = 0; counter < mIdle.size(); counter++) - { - if(mIdle[counter] >= 127 || mIdle[counter] < 0) - mIdle[counter] = 0; - } + init(); + } + + void AiWander::init() + { + mCellX = std::numeric_limits::max(); + mCellY = std::numeric_limits::max(); + mXCell = 0; + mYCell = 0; + mCell = NULL; + mStuckCount = 0;// TODO: maybe no longer needed + mDoorCheckDuration = 0; + mTrimCurrentNode = false; + mReaction = 0; + mRotate = false; + mTargetAngle = 0; + mSaidGreeting = false; + mHasReturnPosition = false; + mReturnPosition = Ogre::Vector3(0,0,0); if(mDistance < 0) mDistance = 0; @@ -56,15 +55,6 @@ namespace MWMechanics mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mPlayedIdle = 0; - const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - mIdleChanceMultiplier = - store.get().find("fIdleChanceMultiplier")->getFloat(); - - mGreetDistanceMultiplier = - store.get().find("iGreetDistanceMultiplier")->getInt(); - mGreetDistanceReset = - store.get().find("fGreetDistanceReset")->getFloat(); - mChance = store.get().find("fVoiceIdleOdds")->getFloat(); mStoredAvailableNodes = false; mChooseAction = true; @@ -219,11 +209,12 @@ namespace MWMechanics } mReaction += duration; - if(mReaction > 0.25f) // FIXME: hard coded constant + if(mReaction < 0.25f) // FIXME: hard coded constant { - mReaction = 0; return false; } + else + mReaction = 0; // NOTE: everything below get updated every 0.25 seconds @@ -394,7 +385,10 @@ namespace MWMechanics MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // Don't bother if the player is out of hearing range - if (roll < mChance && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500) + static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() + .get().find("fVoiceIdleOdds")->getFloat(); + + if (roll < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500) MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); } } @@ -406,7 +400,10 @@ namespace MWMechanics // Play a random voice greeting if the player gets too close int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); float helloDistance = hello; - helloDistance *= mGreetDistanceMultiplier; + static int iGreetDistanceMultiplier =MWBase::Environment::get().getWorld()->getStore() + .get().find("iGreetDistanceMultiplier")->getInt(); + + helloDistance *= iGreetDistanceMultiplier; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); Ogre::Vector3 playerPos(player.getRefData().getPosition().pos); @@ -455,7 +452,10 @@ namespace MWMechanics } else { - if (playerDistSqr >= mGreetDistanceReset*mGreetDistanceReset * mGreetDistanceMultiplier*mGreetDistanceMultiplier) + static float fGreetDistanceReset = MWBase::Environment::get().getWorld()->getStore() + .get().find("fGreetDistanceReset")->getFloat(); + + if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset * iGreetDistanceMultiplier*iGreetDistanceMultiplier) mSaidGreeting = false; } @@ -632,8 +632,11 @@ namespace MWMechanics for(unsigned int counter = 0; counter < mIdle.size(); counter++) { - unsigned short idleChance = mIdleChanceMultiplier * mIdle[counter]; - unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mIdleChanceMultiplier)); + static float fIdleChanceMultiplier = MWBase::Environment::get().getWorld()->getStore() + .get().find("fIdleChanceMultiplier")->getFloat(); + + unsigned short idleChance = fIdleChanceMultiplier * mIdle[counter]; + unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / fIdleChanceMultiplier)); if(randSelect < idleChance && randSelect > idleRoll) { mPlayedIdle = counter+2; @@ -641,5 +644,37 @@ namespace MWMechanics } } } + + void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const + { + std::auto_ptr wander(new ESM::AiSequence::AiWander()); + wander->mData.mDistance = mDistance; + wander->mData.mDuration = mDuration; + wander->mData.mTimeOfDay = mTimeOfDay; + wander->mStartTime = mStartTime.toEsm(); + assert (mIdle.size() >= 8); + for (int i=0; i<8; ++i) + wander->mData.mIdle[i] = mIdle[i]; + wander->mData.mShouldRepeat = mRepeat; + + ESM::AiSequence::AiPackageContainer package; + package.mType = ESM::AiSequence::Ai_Wander; + package.mPackage = wander.release(); + sequence.mPackages.push_back(package); + } + + AiWander::AiWander (const ESM::AiSequence::AiWander* wander) + { + init(); + + mDistance = wander->mData.mDistance; + mDuration = wander->mData.mDuration; + mStartTime = MWWorld::TimeStamp(wander->mStartTime); + mTimeOfDay = wander->mData.mTimeOfDay; + for (int i=0; i<8; ++i) + mIdle.push_back(wander->mData.mIdle[i]); + + mRepeat = wander->mData.mShouldRepeat; + } } diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 6481b2a01..7abd19e27 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -12,6 +12,14 @@ #include "../mwworld/timestamp.hpp" +namespace ESM +{ + namespace AiSequence + { + struct AiWander; + } +} + namespace MWMechanics { /// \brief Causes the Actor to wander within a specified range @@ -24,7 +32,11 @@ namespace MWMechanics \param timeOfDay Start time of the package, if it has a duration. Currently unimplemented \param idle Chances of each idle to play (9 in total) \param repeat Repeat wander or not **/ - AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); + AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat); + + AiWander (const ESM::AiSequence::AiWander* wander); + + void init(); virtual AiPackage *clone() const; @@ -36,6 +48,8 @@ namespace MWMechanics /** In case another AI package moved the actor elsewhere **/ void setReturnPosition (const Ogre::Vector3& position); + virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + private: void stopWalking(const MWWorld::Ptr& actor); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); @@ -45,13 +59,10 @@ namespace MWMechanics int mDistance; // how far the actor can wander from the spawn point int mDuration; int mTimeOfDay; - std::vector mIdle; + std::vector mIdle; bool mRepeat; bool mSaidGreeting; - int mGreetDistanceMultiplier; - float mGreetDistanceReset; - float mChance; bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... @@ -74,7 +85,6 @@ namespace MWMechanics bool mMoveNow; bool mWalking; - float mIdleChanceMultiplier; unsigned short mPlayedIdle; MWWorld::TimeStamp mStartTime; diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 7fd26c25c..7396b2735 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -503,6 +503,7 @@ namespace MWMechanics mSpells.writeState(state.mSpells); mActiveSpells.writeState(state.mActiveSpells); + mAiSequence.writeState(state.mAiSequence); } void CreatureStats::readState (const ESM::CreatureStats& state) @@ -543,6 +544,7 @@ namespace MWMechanics mSpells.readState(state.mSpells); mActiveSpells.readState(state.mActiveSpells); + mAiSequence.readState(state.mAiSequence); } void CreatureStats::setLastRestockTime(MWWorld::TimeStamp tradeTime) diff --git a/apps/openmw/mwscript/aiextensions.cpp b/apps/openmw/mwscript/aiextensions.cpp index cc17905df..823d22a6a 100644 --- a/apps/openmw/mwscript/aiextensions.cpp +++ b/apps/openmw/mwscript/aiextensions.cpp @@ -186,7 +186,7 @@ namespace MWScript Interpreter::Type_Integer time = runtime[0].mFloat; runtime.pop(); - std::vector idleList; + std::vector idleList; bool repeat = false; for(int i=1; i < 10 && arg0; ++i) @@ -194,6 +194,7 @@ namespace MWScript if(!repeat) repeat = true; Interpreter::Type_Integer idleValue = runtime[0].mInteger; + idleValue = std::min(255, std::max(0, idleValue)); idleList.push_back(idleValue); runtime.pop(); --arg0; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e0166138e..060e3472d 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -42,6 +42,7 @@ add_component_dir (esm loadweap records aipackage effectlist spelllist variant variantimp loadtes3 cellref filter savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap lightstate inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate + aisequence ) add_component_dir (misc diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp new file mode 100644 index 000000000..80440bdd3 --- /dev/null +++ b/components/esm/aisequence.cpp @@ -0,0 +1,211 @@ +#include "aisequence.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +#include "defs.hpp" + +#include + +namespace ESM +{ +namespace AiSequence +{ + + void AiWander::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + esm.getHNT(mStartTime, "STAR"); + } + + void AiWander::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + esm.writeHNT ("STAR", mStartTime); + } + + void AiTravel::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + } + + void AiTravel::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + } + + void AiEscort::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + mTargetId = esm.getHNString("TARG"); + esm.getHNT (mRemainingDuration, "DURA"); + mCellId = esm.getHNOString ("CELL"); + } + + void AiEscort::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + esm.writeHNString ("TARG", mTargetId); + esm.writeHNT ("DURA", mRemainingDuration); + if (!mCellId.empty()) + esm.writeHNString ("CELL", mCellId); + } + + void AiFollow::load(ESMReader &esm) + { + esm.getHNT (mData, "DATA"); + mTargetId = esm.getHNString("TARG"); + esm.getHNT (mRemainingDuration, "DURA"); + mCellId = esm.getHNOString ("CELL"); + esm.getHNT (mAlwaysFollow, "ALWY"); + } + + void AiFollow::save(ESMWriter &esm) const + { + esm.writeHNT ("DATA", mData); + esm.writeHNString("TARG", mTargetId); + esm.writeHNT ("DURA", mRemainingDuration); + if (!mCellId.empty()) + esm.writeHNString ("CELL", mCellId); + esm.writeHNT ("ALWY", mAlwaysFollow); + } + + void AiActivate::load(ESMReader &esm) + { + mTargetId = esm.getHNString("TARG"); + } + + void AiActivate::save(ESMWriter &esm) const + { + esm.writeHNString("TARG", mTargetId); + } + + void AiCombat::load(ESMReader &esm) + { + esm.getHNT (mTargetActorId, "TARG"); + } + + void AiCombat::save(ESMWriter &esm) const + { + esm.writeHNT ("TARG", mTargetActorId); + } + + void AiPursue::load(ESMReader &esm) + { + esm.getHNT (mTargetActorId, "TARG"); + } + + void AiPursue::save(ESMWriter &esm) const + { + esm.writeHNT ("TARG", mTargetActorId); + } + + AiSequence::~AiSequence() + { + for (std::vector::iterator it = mPackages.begin(); it != mPackages.end(); ++it) + delete it->mPackage; + } + + void AiSequence::save(ESMWriter &esm) const + { + for (std::vector::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + esm.writeHNT ("AIPK", it->mType); + switch (it->mType) + { + case Ai_Wander: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Travel: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Escort: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Follow: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Activate: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Combat: + static_cast(it->mPackage)->save(esm); + break; + case Ai_Pursue: + static_cast(it->mPackage)->save(esm); + break; + + default: + break; + } + } + } + + void AiSequence::load(ESMReader &esm) + { + while (esm.isNextSub("AIPK")) + { + int type; + esm.getHT(type); + + mPackages.push_back(AiPackageContainer()); + mPackages.back().mType = type; + + switch (type) + { + case Ai_Wander: + { + std::auto_ptr ptr (new AiWander()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Travel: + { + std::auto_ptr ptr (new AiTravel()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Escort: + { + std::auto_ptr ptr (new AiEscort()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Follow: + { + std::auto_ptr ptr (new AiFollow()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Activate: + { + std::auto_ptr ptr (new AiActivate()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Combat: + { + std::auto_ptr ptr (new AiCombat()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + case Ai_Pursue: + { + std::auto_ptr ptr (new AiPursue()); + ptr->load(esm); + mPackages.back().mPackage = ptr.release(); + break; + } + default: + return; + } + } + } +} +} diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp new file mode 100644 index 000000000..bf0e17fa0 --- /dev/null +++ b/components/esm/aisequence.hpp @@ -0,0 +1,154 @@ +#ifndef OPENMW_COMPONENTS_ESM_AISEQUENCE_H +#define OPENMW_COMPONENTS_ESM_AISEQUENCE_H + +#include +#include + +#include "defs.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + namespace AiSequence + { + + // format 0, saved games only + // As opposed to AiPackageList, this stores the "live" version of AI packages. + + enum AiPackages + { + Ai_Wander = ESM::FourCC<'W','A','N','D'>::value, + Ai_Travel = ESM::FourCC<'T','R','A','V'>::value, + Ai_Escort = ESM::FourCC<'E','S','C','O'>::value, + Ai_Follow = ESM::FourCC<'F','O','L','L'>::value, + Ai_Activate = ESM::FourCC<'A','C','T','I'>::value, + Ai_Combat = ESM::FourCC<'C','O','M','B'>::value, + Ai_Pursue = ESM::FourCC<'P','U','R','S'>::value + }; + + + struct AiPackage + { + virtual ~AiPackage() {} + }; + + +#pragma pack(push,1) + struct AiWanderData + { + short mDistance; + short mDuration; + unsigned char mTimeOfDay; + unsigned char mIdle[8]; + unsigned char mShouldRepeat; + }; + struct AiTravelData + { + float mX, mY, mZ; + }; + struct AiEscortData + { + float mX, mY, mZ; + short mDuration; + }; + +#pragma pack(pop) + + struct AiWander : AiPackage + { + AiWanderData mData; + ESM::TimeStamp mStartTime; + + /// \todo add more AiWander state + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiTravel : AiPackage + { + AiTravelData mData; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiEscort : AiPackage + { + AiEscortData mData; + + std::string mTargetId; + std::string mCellId; + float mRemainingDuration; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiFollow : AiPackage + { + AiEscortData mData; + + std::string mTargetId; + std::string mCellId; + float mRemainingDuration; + + bool mAlwaysFollow; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiActivate : AiPackage + { + std::string mTargetId; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiCombat : AiPackage + { + int mTargetActorId; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiPursue : AiPackage + { + int mTargetActorId; + + void load(ESMReader &esm); + void save(ESMWriter &esm) const; + }; + + struct AiPackageContainer + { + int mType; + + AiPackage* mPackage; + }; + + struct AiSequence + { + AiSequence() {} + ~AiSequence(); + + std::vector mPackages; + + void load (ESMReader &esm); + void save (ESMWriter &esm) const; + + private: + AiSequence(const AiSequence&); + AiSequence& operator=(const AiSequence&); + }; + + } + +} + +#endif diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 3860e9351..8151091b2 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -79,6 +79,7 @@ void ESM::CreatureStats::load (ESMReader &esm) mSpells.load(esm); mActiveSpells.load(esm); + mAiSequence.load(esm); } void ESM::CreatureStats::save (ESMWriter &esm) const @@ -160,4 +161,5 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mSpells.save(esm); mActiveSpells.save(esm); + mAiSequence.save(esm); } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 5ca3d071f..c8dc97403 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -11,6 +11,7 @@ #include "spellstate.hpp" #include "activespells.hpp" +#include "aisequence.hpp" namespace ESM { @@ -23,6 +24,8 @@ namespace ESM StatState mAttributes[8]; StatState mDynamic[3]; + AiSequence::AiSequence mAiSequence; + ESM::TimeStamp mTradeTime; int mGoldPool; int mActorId;