diff --git a/apps/openmw/mwmechanics/aiavoiddoor.hpp b/apps/openmw/mwmechanics/aiavoiddoor.hpp index 9d63c63e08..41c41b1f57 100644 --- a/apps/openmw/mwmechanics/aiavoiddoor.hpp +++ b/apps/openmw/mwmechanics/aiavoiddoor.hpp @@ -26,6 +26,9 @@ namespace MWMechanics virtual unsigned int getPriority() const; + virtual bool canCancel() const { return false; } + virtual bool shouldCancelPreviousAi() const { return false; } + private: float mDuration; MWWorld::ConstPtr mDoorPtr; diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 93d6305291..2b364001ff 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -53,6 +53,9 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + virtual bool canCancel() const { return false; } + virtual bool shouldCancelPreviousAi() const { return false; } + protected: virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index cb2b002f6c..58ba7dfe8e 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -35,6 +35,16 @@ bool MWMechanics::AiPackage::followTargetThroughDoors() const return false; } +bool MWMechanics::AiPackage::canCancel() const +{ + return true; +} + +bool MWMechanics::AiPackage::shouldCancelPreviousAi() const +{ + return true; +} + MWMechanics::AiPackage::AiPackage() : mTimer(0.26f) { //mTimer starts at .26 to force initial pathbuild } diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 07df933e27..72bb4487ce 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -39,6 +39,8 @@ namespace MWMechanics TypeIdEscort = 2, TypeIdFollow = 3, TypeIdActivate = 4, + + // These 3 are not really handled as Ai Packages in the MW engine TypeIdCombat = 5, TypeIdPursue = 6, TypeIdAvoidDoor = 7 @@ -78,6 +80,12 @@ namespace MWMechanics /// Return true if the actor should follow the target through teleport doors (default false) virtual bool followTargetThroughDoors() const; + /// Can this Ai package be canceled? (default true) + virtual bool canCancel() const; + + /// Upon adding this Ai package, should the Ai Sequence attempt to cancel previous Ai packages (default true)? + virtual bool shouldCancelPreviousAi() const; + bool isTargetMagicallyHidden(const MWWorld::Ptr& target); protected: diff --git a/apps/openmw/mwmechanics/aipursue.hpp b/apps/openmw/mwmechanics/aipursue.hpp index 813b87cff0..cb93e9636a 100644 --- a/apps/openmw/mwmechanics/aipursue.hpp +++ b/apps/openmw/mwmechanics/aipursue.hpp @@ -38,6 +38,9 @@ namespace MWMechanics virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; + virtual bool canCancel() const { return false; } + virtual bool shouldCancelPreviousAi() const { return false; } + private: int mTargetActorId; // The actor to pursue diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index d55dc240e0..04ac96b11b 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -153,83 +153,86 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac { if(actor != getPlayer()) { - if (!mPackages.empty()) + if (mPackages.empty()) { - MWMechanics::AiPackage* package = mPackages.front(); - mLastAiPackage = package->getTypeId(); + mLastAiPackage = -1; + return; + } - // if active package is combat one, choose nearest target - if (mLastAiPackage == AiPackage::TypeIdCombat) + MWMechanics::AiPackage* package = mPackages.front(); + mLastAiPackage = package->getTypeId(); + + // if active package is combat one, choose nearest target + if (mLastAiPackage == AiPackage::TypeIdCombat) + { + std::list::iterator itActualCombat; + + float nearestDist = std::numeric_limits::max(); + osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); + + for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) { - std::list::iterator itActualCombat; + if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; - float nearestDist = std::numeric_limits::max(); - osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3(); + MWWorld::Ptr target = static_cast(*it)->getTarget(); - for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + // target disappeared (e.g. summoned creatures) + if (target.isEmpty()) { - if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; - - MWWorld::Ptr target = static_cast(*it)->getTarget(); - - // target disappeared (e.g. summoned creatures) - if (target.isEmpty()) - { - delete *it; - it = mPackages.erase(it); - } - else - { - const ESM::Position &targetPos = target.getRefData().getPosition(); - - float distTo = (targetPos.asVec3() - vActorPos).length(); - - // Small threshold for changing target - if (it == mPackages.begin()) - distTo = std::max(0.f, distTo - 50.f); - - if (distTo < nearestDist) - { - nearestDist = distTo; - itActualCombat = it; - } - ++it; - } + delete *it; + it = mPackages.erase(it); } - - if (!mPackages.empty()) + else { - if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + const ESM::Position &targetPos = target.getRefData().getPosition(); + + float distTo = (targetPos.asVec3() - vActorPos).length(); + + // Small threshold for changing target + if (it == mPackages.begin()) + distTo = std::max(0.f, distTo - 50.f); + + if (distTo < nearestDist) { - // move combat package with nearest target to the front - mPackages.splice(mPackages.begin(), mPackages, itActualCombat); + nearestDist = distTo; + itActualCombat = it; } - - package = mPackages.front(); - mLastAiPackage = package->getTypeId(); - } - else - { - mDone = true; - return; + ++it; } } - if (package->execute (actor,characterController,state,duration)) + if (!mPackages.empty()) { - // To account for the rare case where AiPackage::execute() queued another AI package - // (e.g. AiPursue executing a dialogue script that uses startCombat) - std::list::iterator toRemove = - std::find(mPackages.begin(), mPackages.end(), package); - mPackages.erase(toRemove); - delete package; - mDone = true; + if (nearestDist < std::numeric_limits::max() && mPackages.begin() != itActualCombat) + { + // move combat package with nearest target to the front + mPackages.splice(mPackages.begin(), mPackages, itActualCombat); + } + + package = mPackages.front(); + mLastAiPackage = package->getTypeId(); } else { - mDone = false; + mDone = true; + return; } } + + if (package->execute (actor,characterController,state,duration)) + { + // To account for the rare case where AiPackage::execute() queued another AI package + // (e.g. AiPursue executing a dialogue script that uses startCombat) + std::list::iterator toRemove = + std::find(mPackages.begin(), mPackages.end(), package); + mPackages.erase(toRemove); + delete package; + mDone = true; + } + else + { + mDone = false; + } } } @@ -251,11 +254,6 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) // Notify AiWander of our current position so we can return to it after combat finished for (std::list::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) { - if((*iter)->getTypeId() == AiPackage::TypeIdPursue && package.getTypeId() == AiPackage::TypeIdPursue - && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) - { - return; // target is already pursued - } if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat && static_cast(*iter)->getTarget() == static_cast(&package)->getTarget()) { @@ -266,6 +264,19 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor) } } + // remove previous packages if required + if (package.shouldCancelPreviousAi()) + { + for(std::list::iterator it = mPackages.begin(); it != mPackages.end();) + { + if((*it)->canCancel()) + it = mPackages.erase(it); + else + ++it; + } + } + + // insert new package in correct place depending on priority for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) { if((*it)->getPriority() <= package.getPriority())