Implement AiWander fast-forward (Feature #1125)

moveref
scrawl 10 years ago
parent d26d5f6c26
commit a8ae0dec52

@ -1641,4 +1641,18 @@ namespace MWMechanics
return it->second->getCharacterController()->isReadyToBlock(); return it->second->getCharacterController()->isReadyToBlock();
} }
void Actors::fastForwardAi()
{
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
return;
for (PtrActorMap::iterator it = mActors.begin(); it != mActors.end(); ++it)
{
MWWorld::Ptr ptr = it->first;
if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr())
continue;
MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence();
seq.fastForward(ptr, it->second->getAiState());
}
}
} }

@ -101,6 +101,9 @@ namespace MWMechanics
int getHoursToRest(const MWWorld::Ptr& ptr) const; int getHoursToRest(const MWWorld::Ptr& ptr) const;
///< Calculate how many hours the given actor needs to rest in order to be fully healed ///< Calculate how many hours the given actor needs to rest in order to be fully healed
void fastForwardAi();
///< Simulate the passing of time
int countDeaths (const std::string& id) const; int countDeaths (const std::string& id) const;
///< Return the number of deaths for actors with the given ID. ///< Return the number of deaths for actors with the given ID.

@ -63,6 +63,9 @@ namespace MWMechanics
virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {} virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {}
/// Simulates the passing of time
virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {}
protected: protected:
/// Causes the actor to attempt to walk to the specified location /// Causes the actor to attempt to walk to the specified location
/** \return If the actor has arrived at his destination **/ /** \return If the actor has arrived at his destination **/

@ -390,4 +390,13 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
} }
} }
void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state)
{
if (!mPackages.empty())
{
MWMechanics::AiPackage* package = mPackages.front();
package->fastForward(actor, state);
}
}
} // namespace MWMechanics } // namespace MWMechanics

@ -97,6 +97,9 @@ namespace MWMechanics
/// Execute current package, switching if needed. /// Execute current package, switching if needed.
void execute (const MWWorld::Ptr& actor, MWMechanics::AiState& state, float duration); void execute (const MWWorld::Ptr& actor, MWMechanics::AiState& state, float duration);
/// Simulate the passing of time using the currently active AI package
void fastForward(const MWWorld::Ptr &actor, AiState &state);
/// Remove all packages. /// Remove all packages.
void clear(); void clear();

@ -76,24 +76,6 @@ namespace MWMechanics
mStorage = p; mStorage = p;
} }
/// \brief gives away ownership of object. Throws exception if storage does not contain Derived or is empty.
template< class Derived >
Derived* moveOut()
{
assert_derived<Derived>();
if(!mStorage)
throw std::runtime_error("Cant move out: empty storage.");
Derived* result = dynamic_cast<Derived*>(mStorage);
if(!mStorage)
throw std::runtime_error("Cant move out: wrong type requested.");
return result;
}
bool empty() const bool empty() const
{ {
return mStorage == NULL; return mStorage == NULL;
@ -120,7 +102,7 @@ namespace MWMechanics
/// \brief base class for the temporary storage of AiPackages. /// \brief base class for the temporary storage of AiPackages.
/** /**
* Each AI package with temporary values needs a AiPackageStorage class * Each AI package with temporary values needs a AiPackageStorage class
* which is derived from AiTemporaryBase. The CharacterController holds a container * which is derived from AiTemporaryBase. The Actor holds a container
* AiState where one of these storages can be stored at a time. * AiState where one of these storages can be stored at a time.
* The execute(...) member function takes this container as an argument. * The execute(...) member function takes this container as an argument.
* */ * */

@ -113,6 +113,11 @@ namespace MWMechanics
return TypeIdTravel; return TypeIdTravel;
} }
void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state)
{
}
void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const
{ {
std::auto_ptr<ESM::AiSequence::AiTravel> travel(new ESM::AiSequence::AiTravel()); std::auto_ptr<ESM::AiSequence::AiTravel> travel(new ESM::AiSequence::AiTravel());

@ -23,6 +23,9 @@ namespace MWMechanics
AiTravel(float x, float y, float z); AiTravel(float x, float y, float z);
AiTravel(const ESM::AiSequence::AiTravel* travel); AiTravel(const ESM::AiSequence::AiTravel* travel);
/// Simulates the passing of time
virtual void fastForward(const MWWorld::Ptr& actor, AiState& state);
void writeState(ESM::AiSequence::AiSequence &sequence) const; void writeState(ESM::AiSequence::AiSequence &sequence) const;
virtual AiTravel *clone() const; virtual AiTravel *clone() const;

@ -40,16 +40,9 @@ namespace MWMechanics
AiWander::GreetingState mSaidGreeting; AiWander::GreetingState mSaidGreeting;
int mGreetingTimer; int mGreetingTimer;
// Cached current cell location
int mCellX;
int mCellY;
// Cell location multiplied by ESM::Land::REAL_SIZE
float mXCell;
float mYCell;
const MWWorld::CellStore* mCell; // for detecting cell change const MWWorld::CellStore* mCell; // for detecting cell change
// AiWander states // AiWander states
bool mChooseAction; bool mChooseAction;
bool mIdleNow; bool mIdleNow;
@ -66,10 +59,6 @@ namespace MWMechanics
mReaction(0), mReaction(0),
mSaidGreeting(AiWander::Greet_None), mSaidGreeting(AiWander::Greet_None),
mGreetingTimer(0), mGreetingTimer(0),
mCellX(std::numeric_limits<int>::max()),
mCellY(std::numeric_limits<int>::max()),
mXCell(0),
mYCell(0),
mCell(NULL), mCell(NULL),
mChooseAction(true), mChooseAction(true),
mIdleNow(false), mIdleNow(false),
@ -183,7 +172,6 @@ namespace MWMechanics
currentCell = actor.getCell(); currentCell = actor.getCell();
mStoredAvailableNodes = false; // prob. not needed since mDistance = 0 mStoredAvailableNodes = false; // prob. not needed since mDistance = 0
} }
const ESM::Cell *cell = currentCell->getCell();
cStats.setDrawState(DrawState_Nothing); cStats.setDrawState(DrawState_Nothing);
cStats.setMovementFlag(CreatureStats::Flag_Run, false); cStats.setMovementFlag(CreatureStats::Flag_Run, false);
@ -371,81 +359,10 @@ namespace MWMechanics
} }
} }
int& cachedCellX = storage.mCellX;
int& cachedCellY = storage.mCellY;
float& cachedCellXposition = storage.mXCell;
float& cachedCellYposition = storage.mYCell;
// Initialization to discover & store allowed node points for this actor. // Initialization to discover & store allowed node points for this actor.
if(!mStoredAvailableNodes) if(!mStoredAvailableNodes)
{ {
// infrequently used, therefore no benefit in caching it as a member getAllowedNodes(actor, currentCell->getCell());
const ESM::Pathgrid *
pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell);
// cache the current cell location
cachedCellX = cell->mData.mX;
cachedCellY = cell->mData.mY;
// If there is no path this actor doesn't go anywhere. See:
// https://forum.openmw.org/viewtopic.php?t=1556
// http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833
if(!pathgrid || pathgrid->mPoints.empty())
mDistance = 0;
// A distance value passed into the constructor indicates how far the
// actor can wander from the spawn position. AiWander assumes that
// pathgrid points are available, and uses them to randomly select wander
// destinations within the allowed set of pathgrid points (nodes).
if(mDistance)
{
cachedCellXposition = 0;
cachedCellYposition = 0;
if(cell->isExterior())
{
cachedCellXposition = cachedCellX * ESM::Land::REAL_SIZE;
cachedCellYposition = cachedCellY * ESM::Land::REAL_SIZE;
}
// FIXME: There might be a bug here. The allowed node points are
// based on the actor's current position rather than the actor's
// spawn point. As a result the allowed nodes for wander can change
// between saves, for example.
//
// convert npcPos to local (i.e. cell) co-ordinates
Ogre::Vector3 npcPos(pos.pos);
npcPos[0] = npcPos[0] - cachedCellXposition;
npcPos[1] = npcPos[1] - cachedCellYposition;
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
// NOTE: mPoints and mAllowedNodes are in local co-ordinates
for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
{
Ogre::Vector3 nodePos(pathgrid->mPoints[counter].mX, pathgrid->mPoints[counter].mY,
pathgrid->mPoints[counter].mZ);
if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance)
mAllowedNodes.push_back(pathgrid->mPoints[counter]);
}
if(!mAllowedNodes.empty())
{
Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ);
float closestNode = npcPos.squaredDistance(firstNodePos);
unsigned int index = 0;
for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++)
{
Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY,
mAllowedNodes[counterThree].mZ);
float tempDist = npcPos.squaredDistance(nodePos);
if(tempDist < closestNode)
index = counterThree;
}
mCurrentNode = mAllowedNodes[index];
mAllowedNodes.erase(mAllowedNodes.begin() + index);
mStoredAvailableNodes = true; // set only if successful in finding allowed nodes
}
}
} }
// Actor becomes stationary - see above URL's for previous research // Actor becomes stationary - see above URL's for previous research
@ -581,8 +498,8 @@ namespace MWMechanics
// convert dest to use world co-ordinates // convert dest to use world co-ordinates
ESM::Pathgrid::Point dest; ESM::Pathgrid::Point dest;
dest.mX = destNodePos[0] + cachedCellXposition; dest.mX = destNodePos[0] + currentCell->getCell()->mData.mX * ESM::Land::REAL_SIZE;
dest.mY = destNodePos[1] + cachedCellYposition; dest.mY = destNodePos[1] + currentCell->getCell()->mData.mY * ESM::Land::REAL_SIZE;
dest.mZ = destNodePos[2]; dest.mZ = destNodePos[2];
// actor position is already in world co-ordinates // actor position is already in world co-ordinates
@ -732,6 +649,96 @@ namespace MWMechanics
} }
} }
void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state)
{
if (mDistance == 0)
return;
if (!mStoredAvailableNodes)
getAllowedNodes(actor, actor.getCell()->getCell());
if (mAllowedNodes.empty())
return;
state.moveIn(new AiWanderStorage());
int index = std::rand() / (static_cast<double> (RAND_MAX) + 1) * mAllowedNodes.size();
ESM::Pathgrid::Point dest = mAllowedNodes[index];
// apply a slight offset to prevent overcrowding
dest.mX += Ogre::Math::RangeRandom(-64, 64);
dest.mY += Ogre::Math::RangeRandom(-64, 64);
MWBase::Environment::get().getWorld()->moveObject(actor, dest.mX, dest.mY, dest.mZ);
actor.getClass().adjustPosition(actor, false);
}
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell)
{
// infrequently used, therefore no benefit in caching it as a member
const ESM::Pathgrid *
pathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*cell);
// If there is no path this actor doesn't go anywhere. See:
// https://forum.openmw.org/viewtopic.php?t=1556
// http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833
if(!pathgrid || pathgrid->mPoints.empty())
mDistance = 0;
// A distance value passed into the constructor indicates how far the
// actor can wander from the spawn position. AiWander assumes that
// pathgrid points are available, and uses them to randomly select wander
// destinations within the allowed set of pathgrid points (nodes).
if(mDistance)
{
float cellXOffset = 0;
float cellYOffset = 0;
if(cell->isExterior())
{
cellXOffset = cell->mData.mX * ESM::Land::REAL_SIZE;
cellYOffset = cell->mData.mY * ESM::Land::REAL_SIZE;
}
// FIXME: There might be a bug here. The allowed node points are
// based on the actor's current position rather than the actor's
// spawn point. As a result the allowed nodes for wander can change
// between saves, for example.
//
// convert npcPos to local (i.e. cell) co-ordinates
Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos);
npcPos[0] = npcPos[0] - cellXOffset;
npcPos[1] = npcPos[1] - cellYOffset;
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
// NOTE: mPoints and mAllowedNodes are in local co-ordinates
for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
{
Ogre::Vector3 nodePos(pathgrid->mPoints[counter].mX, pathgrid->mPoints[counter].mY,
pathgrid->mPoints[counter].mZ);
if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance)
mAllowedNodes.push_back(pathgrid->mPoints[counter]);
}
if(!mAllowedNodes.empty())
{
Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ);
float closestNode = npcPos.squaredDistance(firstNodePos);
unsigned int index = 0;
for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++)
{
Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY,
mAllowedNodes[counterThree].mZ);
float tempDist = npcPos.squaredDistance(nodePos);
if(tempDist < closestNode)
index = counterThree;
}
mCurrentNode = mAllowedNodes[index];
mAllowedNodes.erase(mAllowedNodes.begin() + index);
mStoredAvailableNodes = true; // set only if successful in finding allowed nodes
}
}
}
void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const
{ {
std::auto_ptr<ESM::AiSequence::AiWander> wander(new ESM::AiSequence::AiWander()); std::auto_ptr<ESM::AiSequence::AiWander> wander(new ESM::AiSequence::AiWander());

@ -57,6 +57,7 @@ namespace MWMechanics
virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual void writeState(ESM::AiSequence::AiSequence &sequence) const;
virtual void fastForward(const MWWorld::Ptr& actor, AiState& state);
enum GreetingState { enum GreetingState {
Greet_None, Greet_None,
@ -77,7 +78,6 @@ namespace MWMechanics
int mTimeOfDay; int mTimeOfDay;
std::vector<unsigned char> mIdle; std::vector<unsigned char> mIdle;
bool mRepeat; bool mRepeat;
bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position,
@ -98,6 +98,9 @@ namespace MWMechanics
// allowed pathgrid nodes based on mDistance from the spawn point // allowed pathgrid nodes based on mDistance from the spawn point
std::vector<ESM::Pathgrid::Point> mAllowedNodes; std::vector<ESM::Pathgrid::Point> mAllowedNodes;
void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell);
ESM::Pathgrid::Point mCurrentNode; ESM::Pathgrid::Point mCurrentNode;
bool mTrimCurrentNode; bool mTrimCurrentNode;
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes,

@ -472,6 +472,7 @@ namespace MWMechanics
void MechanicsManager::rest(bool sleep) void MechanicsManager::rest(bool sleep)
{ {
mActors.restoreDynamicStats (sleep); mActors.restoreDynamicStats (sleep);
mActors.fastForwardAi();
} }
int MechanicsManager::getHoursToRest() const int MechanicsManager::getHoursToRest() const

Loading…
Cancel
Save