forked from mirror/openmw-tes3mp
Implement AiWander fast-forward (Feature #1125)
This commit is contained in:
parent
d26d5f6c26
commit
a8ae0dec52
11 changed files with 141 additions and 108 deletions
|
@ -1641,4 +1641,18 @@ namespace MWMechanics
|
|||
|
||||
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;
|
||||
///< 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;
|
||||
///< 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 {}
|
||||
|
||||
/// Simulates the passing of time
|
||||
virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {}
|
||||
|
||||
protected:
|
||||
/// Causes the actor to attempt to walk to the specified location
|
||||
/** \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
|
||||
|
|
|
@ -97,6 +97,9 @@ namespace MWMechanics
|
|||
/// Execute current package, switching if needed.
|
||||
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.
|
||||
void clear();
|
||||
|
||||
|
|
|
@ -76,24 +76,6 @@ namespace MWMechanics
|
|||
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
|
||||
{
|
||||
return mStorage == NULL;
|
||||
|
@ -120,7 +102,7 @@ namespace MWMechanics
|
|||
/// \brief base class for the temporary storage of AiPackages.
|
||||
/**
|
||||
* 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.
|
||||
* The execute(...) member function takes this container as an argument.
|
||||
* */
|
||||
|
|
|
@ -113,6 +113,11 @@ namespace MWMechanics
|
|||
return TypeIdTravel;
|
||||
}
|
||||
|
||||
void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const
|
||||
{
|
||||
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(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;
|
||||
|
||||
virtual AiTravel *clone() const;
|
||||
|
|
|
@ -40,16 +40,9 @@ namespace MWMechanics
|
|||
|
||||
AiWander::GreetingState mSaidGreeting;
|
||||
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
|
||||
|
||||
|
||||
// AiWander states
|
||||
bool mChooseAction;
|
||||
bool mIdleNow;
|
||||
|
@ -66,10 +59,6 @@ namespace MWMechanics
|
|||
mReaction(0),
|
||||
mSaidGreeting(AiWander::Greet_None),
|
||||
mGreetingTimer(0),
|
||||
mCellX(std::numeric_limits<int>::max()),
|
||||
mCellY(std::numeric_limits<int>::max()),
|
||||
mXCell(0),
|
||||
mYCell(0),
|
||||
mCell(NULL),
|
||||
mChooseAction(true),
|
||||
mIdleNow(false),
|
||||
|
@ -183,7 +172,6 @@ namespace MWMechanics
|
|||
currentCell = actor.getCell();
|
||||
mStoredAvailableNodes = false; // prob. not needed since mDistance = 0
|
||||
}
|
||||
const ESM::Cell *cell = currentCell->getCell();
|
||||
|
||||
cStats.setDrawState(DrawState_Nothing);
|
||||
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.
|
||||
if(!mStoredAvailableNodes)
|
||||
{
|
||||
// infrequently used, therefore no benefit in caching it as a member
|
||||
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
|
||||
}
|
||||
}
|
||||
getAllowedNodes(actor, currentCell->getCell());
|
||||
}
|
||||
|
||||
// Actor becomes stationary - see above URL's for previous research
|
||||
|
@ -581,8 +498,8 @@ namespace MWMechanics
|
|||
|
||||
// convert dest to use world co-ordinates
|
||||
ESM::Pathgrid::Point dest;
|
||||
dest.mX = destNodePos[0] + cachedCellXposition;
|
||||
dest.mY = destNodePos[1] + cachedCellYposition;
|
||||
dest.mX = destNodePos[0] + currentCell->getCell()->mData.mX * ESM::Land::REAL_SIZE;
|
||||
dest.mY = destNodePos[1] + currentCell->getCell()->mData.mY * ESM::Land::REAL_SIZE;
|
||||
dest.mZ = destNodePos[2];
|
||||
|
||||
// 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
|
||||
{
|
||||
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 fastForward(const MWWorld::Ptr& actor, AiState& state);
|
||||
|
||||
enum GreetingState {
|
||||
Greet_None,
|
||||
|
@ -77,7 +78,6 @@ namespace MWMechanics
|
|||
int mTimeOfDay;
|
||||
std::vector<unsigned char> mIdle;
|
||||
bool mRepeat;
|
||||
|
||||
|
||||
|
||||
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
|
||||
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
|
||||
|
||||
void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell);
|
||||
|
||||
ESM::Pathgrid::Point mCurrentNode;
|
||||
bool mTrimCurrentNode;
|
||||
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes,
|
||||
|
|
|
@ -472,6 +472,7 @@ namespace MWMechanics
|
|||
void MechanicsManager::rest(bool sleep)
|
||||
{
|
||||
mActors.restoreDynamicStats (sleep);
|
||||
mActors.fastForwardAi();
|
||||
}
|
||||
|
||||
int MechanicsManager::getHoursToRest() const
|
||||
|
|
Loading…
Reference in a new issue