You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw-tes3mp/apps/openmw/mwmechanics/aiwander.cpp

446 lines
18 KiB
C++

#include "aiwander.hpp"
#include <OgreVector3.h>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/dialoguemanager.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp"
#include "creaturestats.hpp"
#include "steering.hpp"
#include "movement.hpp"
namespace MWMechanics
{
// NOTE: determined empirically but probably need further tweaking
static const int COUNT_BEFORE_STUCK = 20;
static const int COUNT_BEFORE_RESET = 200;
static const int COUNT_EVADE = 7;
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<int>& idle, bool repeat):
mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat)
, mCellX(std::numeric_limits<int>::max())
, mCellY(std::numeric_limits<int>::max())
, mXCell(0)
, mYCell(0)
, mX(0)
, mY(0)
, mZ(0)
, mPrevX(0)
, mPrevY(0)
, mWalkState(State_Norm)
, mStuckCount(0)
, mEvadeCount(0)
, mSaidGreeting(false)
{
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
{
if(mIdle[counter] >= 127 || mIdle[counter] < 0)
mIdle[counter] = 0;
}
if(mDistance < 0)
mDistance = 0;
if(mDuration < 0)
mDuration = 0;
if(mDuration == 0)
mTimeOfDay = 0;
mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp();
mPlayedIdle = 0;
mPathgrid = NULL;
mIdleChanceMultiplier =
MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fIdleChanceMultiplier")->getFloat();
mStoredAvailableNodes = false;
mChooseAction = true;
mIdleNow = false;
mMoveNow = false;
mWalking = false;
}
AiPackage * MWMechanics::AiWander::clone() const
{
return new AiWander(*this);
}
11 years ago
bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
{
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, false);
MWBase::World *world = MWBase::Environment::get().getWorld();
if(mDuration)
{
// End package if duration is complete or mid-night hits:
MWWorld::TimeStamp currentTime = world->getTimeStamp();
if(currentTime.getHour() >= mStartTime.getHour() + mDuration)
{
if(!mRepeat)
{
stopWalking(actor);
return true;
}
else
mStartTime = currentTime;
}
else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())
{
if(!mRepeat)
{
stopWalking(actor);
return true;
}
else
mStartTime = currentTime;
}
}
ESM::Position pos = actor.getRefData().getPosition();
if(!mStoredAvailableNodes)
{
mStoredAvailableNodes = true;
mPathgrid = world->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->getCell());
mCellX = actor.getCell()->getCell()->mData.mX;
mCellY = actor.getCell()->getCell()->mData.mY;
if(!mPathgrid)
mDistance = 0;
else if(mPathgrid->mPoints.empty())
mDistance = 0;
if(mDistance)
{
mXCell = 0;
mYCell = 0;
if(actor.getCell()->getCell()->isExterior())
{
mXCell = mCellX * ESM::Land::REAL_SIZE;
mYCell = mCellY * ESM::Land::REAL_SIZE;
}
Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos);
npcPos[0] = npcPos[0] - mXCell;
npcPos[1] = npcPos[1] - mYCell;
for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++)
{
Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, mPathgrid->mPoints[counter].mY,
mPathgrid->mPoints[counter].mZ);
if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance)
mAllowedNodes.push_back(mPathgrid->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);
}
}
}
if(mAllowedNodes.empty())
mDistance = 0;
// Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles.
if(mDistance && (mCellX != actor.getCell()->getCell()->mData.mX || mCellY != actor.getCell()->getCell()->mData.mY))
mDistance = 0;
if(mChooseAction)
{
mPlayedIdle = 0;
unsigned short idleRoll = 0;
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));
if(randSelect < idleChance && randSelect > idleRoll)
{
mPlayedIdle = counter+2;
idleRoll = randSelect;
}
}
if(!mPlayedIdle && mDistance)
{
mChooseAction = false;
mMoveNow = true;
}
else
{
// Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander:
MWWorld::TimeStamp currentTime = world->getTimeStamp();
mStartTime = currentTime;
playIdle(actor, mPlayedIdle);
mChooseAction = false;
mIdleNow = true;
// Play idle voiced dialogue entries randomly
int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
if (hello > 0)
{
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
float chance = store.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
// Don't bother if the player is out of hearing range
if (roll < chance && Ogre::Vector3(player.getRefData().getPosition().pos).distance(Ogre::Vector3(actor.getRefData().getPosition().pos)) < 1500)
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}
}
}
if(mIdleNow)
{
// Play a random voice greeting if the player gets too close
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
float helloDistance = hello;
int iGreetDistanceMultiplier = store.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
helloDistance *= iGreetDistanceMultiplier;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
float playerDist = Ogre::Vector3(player.getRefData().getPosition().pos).distance(
Ogre::Vector3(actor.getRefData().getPosition().pos));
if (!mSaidGreeting)
{
// TODO: check if actor is aware / has line of sight
if (playerDist <= helloDistance
// Only play a greeting if the player is not moving
&& Ogre::Vector3(player.getClass().getMovementSettings(player).mPosition).squaredLength() == 0)
{
mSaidGreeting = true;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
// TODO: turn to face player and interrupt the idle animation?
}
}
else
{
float fGreetDistanceReset = store.get<ESM::GameSetting>().find("fGreetDistanceReset")->getFloat();
if (playerDist >= fGreetDistanceReset * iGreetDistanceMultiplier)
mSaidGreeting = false;
}
// Check if idle animation finished
if(!checkIdle(actor, mPlayedIdle))
{
mPlayedIdle = 0;
mIdleNow = false;
mChooseAction = true;
}
}
if(mMoveNow && mDistance)
{
if(!mPathFinder.isPathConstructed())
{
assert(mAllowedNodes.size());
unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size());
Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ);
ESM::Pathgrid::Point dest;
dest.mX = destNodePos[0] + mXCell;
dest.mY = destNodePos[1] + mYCell;
dest.mZ = destNodePos[2];
ESM::Pathgrid::Point start;
start.mX = pos.pos[0];
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
mPathFinder.buildPath(start, dest, actor.getCell(), false);
if(mPathFinder.isPathConstructed())
{
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
ESM::Pathgrid::Point temp = mAllowedNodes[randNode];
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
mAllowedNodes.push_back(mCurrentNode);
mCurrentNode = temp;
mMoveNow = false;
mWalking = true;
}
// Choose a different node and delete this one from possible nodes because it is uncreachable:
else
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
}
}
if(mWalking)
{
if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
{
stopWalking(actor);
mMoveNow = false;
mWalking = false;
mChooseAction = true;
}
else
{
/* 1 n
* State_Norm <---> State_CheckStuck --> State_Evade
* ^ ^ | ^ | ^ | |
* | | | | | | | |
* | +---+ +---+ +---+ | m
* | any < n < m |
* +--------------------------------------------+
*/
bool samePosition = (abs(pos.pos[0] - mPrevX) < 1) && (abs(pos.pos[1] - mPrevY) < 1);
switch(mWalkState)
{
case State_Norm:
{
if(!samePosition)
break;
else
mWalkState = State_CheckStuck;
}
/* FALL THROUGH */
case State_CheckStuck:
{
if(!samePosition)
{
mWalkState = State_Norm;
// to do this properly need yet another variable, simply don't clear for now
//mStuckCount = 0;
break;
}
else
{
// consider stuck only if position unchanges consecutively
if((mStuckCount++ % COUNT_BEFORE_STUCK) == 0)
mWalkState = State_Evade;
// NOTE: mStuckCount is purposely not cleared here
else
break; // still in the same state, but counter got incremented
}
}
/* FALL THROUGH */
case State_Evade:
{
if(mEvadeCount++ < COUNT_EVADE)
break;
else
{
mWalkState = State_Norm; // tried to evade, assume all is ok and start again
// NOTE: mStuckCount is purposely not cleared here
mEvadeCount = 0;
}
}
/* NO DEFAULT CASE */
}
if(mWalkState == State_Evade)
{
//std::cout << "Stuck \""<<actor.getClass().getName(actor)<<"\"" << std::endl;
// diagonal should have same animation as walk forward
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
actor.getClass().getMovementSettings(actor).mPosition[1] = 0.01f;
// change the angle a bit, too
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
}
else
{
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
}
if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
{
//std::cout << "Reset \""<<actor.getClass().getName(actor)<<"\"" << std::endl;
mWalkState = State_Norm;
mStuckCount = 0;
stopWalking(actor);
mMoveNow = false;
mWalking = false;
mChooseAction = true;
}
// update position
ESM::Position updatedPos = actor.getRefData().getPosition();
mPrevX = updatedPos.pos[0];
mPrevY = updatedPos.pos[1];
}
}
return false;
}
int AiWander::getTypeId() const
{
return TypeIdWander;
}
void AiWander::stopWalking(const MWWorld::Ptr& actor)
{
mPathFinder.clearPath();
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
}
void AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect)
{
if(idleSelect == 2)
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle2", 0, 1);
else if(idleSelect == 3)
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
else if(idleSelect == 4)
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle4", 0, 1);
else if(idleSelect == 5)
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle5", 0, 1);
else if(idleSelect == 6)
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle6", 0, 1);
else if(idleSelect == 7)
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle7", 0, 1);
else if(idleSelect == 8)
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle8", 0, 1);
else if(idleSelect == 9)
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle9", 0, 1);
}
bool AiWander::checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect)
{
if(idleSelect == 2)
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle2");
else if(idleSelect == 3)
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle3");
else if(idleSelect == 4)
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle4");
else if(idleSelect == 5)
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle5");
else if(idleSelect == 6)
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle6");
else if(idleSelect == 7)
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle7");
else if(idleSelect == 8)
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle8");
else if(idleSelect == 9)
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle9");
else
return false;
}
}