Don't stack Ai packages (Fixes #3101, Fixes #3080, Fixes #2697)

This commit is contained in:
scrawl 2016-01-19 14:51:42 +01:00
parent 7aeafd3bb9
commit d3b76b7006
6 changed files with 101 additions and 63 deletions

View file

@ -26,6 +26,9 @@ namespace MWMechanics
virtual unsigned int getPriority() const; virtual unsigned int getPriority() const;
virtual bool canCancel() const { return false; }
virtual bool shouldCancelPreviousAi() const { return false; }
private: private:
float mDuration; float mDuration;
MWWorld::ConstPtr mDoorPtr; MWWorld::ConstPtr mDoorPtr;

View file

@ -53,6 +53,9 @@ namespace MWMechanics
virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual void writeState(ESM::AiSequence::AiSequence &sequence) const;
virtual bool canCancel() const { return false; }
virtual bool shouldCancelPreviousAi() const { return false; }
protected: protected:
virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell); virtual bool doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell);

View file

@ -35,6 +35,16 @@ bool MWMechanics::AiPackage::followTargetThroughDoors() const
return false; 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 MWMechanics::AiPackage::AiPackage() : mTimer(0.26f) { //mTimer starts at .26 to force initial pathbuild
} }

View file

@ -39,6 +39,8 @@ namespace MWMechanics
TypeIdEscort = 2, TypeIdEscort = 2,
TypeIdFollow = 3, TypeIdFollow = 3,
TypeIdActivate = 4, TypeIdActivate = 4,
// These 3 are not really handled as Ai Packages in the MW engine
TypeIdCombat = 5, TypeIdCombat = 5,
TypeIdPursue = 6, TypeIdPursue = 6,
TypeIdAvoidDoor = 7 TypeIdAvoidDoor = 7
@ -78,6 +80,12 @@ namespace MWMechanics
/// Return true if the actor should follow the target through teleport doors (default false) /// Return true if the actor should follow the target through teleport doors (default false)
virtual bool followTargetThroughDoors() const; 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); bool isTargetMagicallyHidden(const MWWorld::Ptr& target);
protected: protected:

View file

@ -38,6 +38,9 @@ namespace MWMechanics
virtual void writeState (ESM::AiSequence::AiSequence& sequence) const; virtual void writeState (ESM::AiSequence::AiSequence& sequence) const;
virtual bool canCancel() const { return false; }
virtual bool shouldCancelPreviousAi() const { return false; }
private: private:
int mTargetActorId; // The actor to pursue int mTargetActorId; // The actor to pursue

View file

@ -153,83 +153,86 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
{ {
if(actor != getPlayer()) if(actor != getPlayer())
{ {
if (!mPackages.empty()) if (mPackages.empty())
{ {
MWMechanics::AiPackage* package = mPackages.front(); mLastAiPackage = -1;
mLastAiPackage = package->getTypeId(); return;
}
// if active package is combat one, choose nearest target MWMechanics::AiPackage* package = mPackages.front();
if (mLastAiPackage == AiPackage::TypeIdCombat) mLastAiPackage = package->getTypeId();
// if active package is combat one, choose nearest target
if (mLastAiPackage == AiPackage::TypeIdCombat)
{
std::list<AiPackage *>::iterator itActualCombat;
float nearestDist = std::numeric_limits<float>::max();
osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3();
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end();)
{ {
std::list<AiPackage *>::iterator itActualCombat; if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break;
float nearestDist = std::numeric_limits<float>::max(); MWWorld::Ptr target = static_cast<const AiCombat *>(*it)->getTarget();
osg::Vec3f vActorPos = actor.getRefData().getPosition().asVec3();
for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end();) // target disappeared (e.g. summoned creatures)
if (target.isEmpty())
{ {
if ((*it)->getTypeId() != AiPackage::TypeIdCombat) break; delete *it;
it = mPackages.erase(it);
MWWorld::Ptr target = static_cast<const AiCombat *>(*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;
}
} }
else
if (!mPackages.empty())
{ {
if (nearestDist < std::numeric_limits<float>::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 nearestDist = distTo;
mPackages.splice(mPackages.begin(), mPackages, itActualCombat); itActualCombat = it;
} }
++it;
package = mPackages.front();
mLastAiPackage = package->getTypeId();
}
else
{
mDone = true;
return;
} }
} }
if (package->execute (actor,characterController,state,duration)) if (!mPackages.empty())
{ {
// To account for the rare case where AiPackage::execute() queued another AI package if (nearestDist < std::numeric_limits<float>::max() && mPackages.begin() != itActualCombat)
// (e.g. AiPursue executing a dialogue script that uses startCombat) {
std::list<MWMechanics::AiPackage*>::iterator toRemove = // move combat package with nearest target to the front
std::find(mPackages.begin(), mPackages.end(), package); mPackages.splice(mPackages.begin(), mPackages, itActualCombat);
mPackages.erase(toRemove); }
delete package;
mDone = true; package = mPackages.front();
mLastAiPackage = package->getTypeId();
} }
else 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<MWMechanics::AiPackage*>::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 // Notify AiWander of our current position so we can return to it after combat finished
for (std::list<AiPackage *>::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter) for (std::list<AiPackage *>::const_iterator iter (mPackages.begin()); iter!=mPackages.end(); ++iter)
{ {
if((*iter)->getTypeId() == AiPackage::TypeIdPursue && package.getTypeId() == AiPackage::TypeIdPursue
&& static_cast<const AiPursue*>(*iter)->getTarget() == static_cast<const AiPursue*>(&package)->getTarget())
{
return; // target is already pursued
}
if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat if((*iter)->getTypeId() == AiPackage::TypeIdCombat && package.getTypeId() == AiPackage::TypeIdCombat
&& static_cast<const AiCombat*>(*iter)->getTarget() == static_cast<const AiCombat*>(&package)->getTarget()) && static_cast<const AiCombat*>(*iter)->getTarget() == static_cast<const AiCombat*>(&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<AiPackage *>::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<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); ++it) for(std::list<AiPackage *>::iterator it = mPackages.begin(); it != mPackages.end(); ++it)
{ {
if((*it)->getPriority() <= package.getPriority()) if((*it)->getPriority() <= package.getPriority())