1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-03-03 16:49:54 +00:00

Merge remote-tracking branch 'dteviot/refactoringAiWander'

This commit is contained in:
Marc Zinnschlag 2015-07-20 09:57:46 +02:00
commit f3d3cbc58b
6 changed files with 67 additions and 81 deletions

View file

@ -36,13 +36,13 @@ namespace
float getZAngleToDir(const osg::Vec3f& dir) float getZAngleToDir(const osg::Vec3f& dir)
{ {
return osg::RadiansToDegrees(std::atan2(dir.x(), dir.y())); return std::atan2(dir.x(), dir.y());
} }
float getXAngleToDir(const osg::Vec3f& dir, float dirLen = 0.0f) float getXAngleToDir(const osg::Vec3f& dir, float dirLen = 0.0f)
{ {
float len = (dirLen > 0.0f)? dirLen : dir.length(); float len = (dirLen > 0.0f)? dirLen : dir.length();
return osg::RadiansToDegrees(-std::asin(dir.z() / len)); return -std::asin(dir.z() / len);
} }
@ -221,12 +221,12 @@ namespace MWMechanics
if(movement.mRotation[2] != 0) if(movement.mRotation[2] != 0)
{ {
if(zTurn(actor, osg::DegreesToRadians(movement.mRotation[2]))) movement.mRotation[2] = 0; if(zTurn(actor, movement.mRotation[2])) movement.mRotation[2] = 0;
} }
if(movement.mRotation[0] != 0) if(movement.mRotation[0] != 0)
{ {
if(smoothTurn(actor, osg::DegreesToRadians(movement.mRotation[0]), 0)) movement.mRotation[0] = 0; if(smoothTurn(actor, movement.mRotation[0], 0)) movement.mRotation[0] = 0;
} }
bool& attack = storage.mAttack; bool& attack = storage.mAttack;

View file

@ -104,7 +104,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po
actor.getClass().getMovementSettings(actor).mPosition[0] = 1; actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
// change the angle a bit, too // change the angle a bit, too
zTurn(actor, osg::DegreesToRadians(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]));
} }
} }
else { //Not stuck, so reset things else { //Not stuck, so reset things
@ -117,7 +117,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time actor.getClass().getMovementSettings(actor).mPosition[1] = 1; //Just run forward the rest of the time
} }
zTurn(actor, osg::DegreesToRadians(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
return false; return false;
} }

View file

@ -51,10 +51,10 @@ namespace MWMechanics
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive. /// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
struct AiWanderStorage : AiTemporaryBase struct AiWanderStorage : AiTemporaryBase
{ {
// the z rotation angle (degrees) we want to reach // the z rotation angle to reach
// used every frame when mRotate is true // when mTurnActorGivingGreetingToFacePlayer is true
float mTargetAngleRadians; float mTargetAngleRadians;
bool mRotate; bool mTurnActorGivingGreetingToFacePlayer;
float mReaction; // update some actions infrequently float mReaction; // update some actions infrequently
@ -64,27 +64,21 @@ namespace MWMechanics
const MWWorld::CellStore* mCell; // for detecting cell change const MWWorld::CellStore* mCell; // for detecting cell change
// AiWander states // AiWander states
bool mChooseAction; AiWander::WanderState mState;
bool mIdleNow;
bool mMoveNow;
bool mWalking;
unsigned short mPlayedIdle; unsigned short mIdleAnimation;
PathFinder mPathFinder; PathFinder mPathFinder;
AiWanderStorage(): AiWanderStorage():
mTargetAngleRadians(0), mTargetAngleRadians(0),
mRotate(false), mTurnActorGivingGreetingToFacePlayer(false),
mReaction(0), mReaction(0),
mSaidGreeting(AiWander::Greet_None), mSaidGreeting(AiWander::Greet_None),
mGreetingTimer(0), mGreetingTimer(0),
mCell(NULL), mCell(NULL),
mChooseAction(true), mState(AiWander::Wander_ChooseAction),
mIdleNow(false), mIdleAnimation(0)
mMoveNow(false),
mWalking(false),
mPlayedIdle(0)
{}; {};
}; };
@ -200,88 +194,77 @@ namespace MWMechanics
ESM::Position pos = actor.getRefData().getPosition(); ESM::Position pos = actor.getRefData().getPosition();
bool& idleNow = storage.mIdleNow; WanderState& wanderState = storage.mState;
bool& moveNow = storage.mMoveNow;
bool& walking = storage.mWalking;
// Check if an idle actor is too close to a door - if so start walking // Check if an idle actor is too close to a door - if so start walking
mDoorCheckDuration += duration; mDoorCheckDuration += duration;
if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL) if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL)
{ {
mDoorCheckDuration = 0; // restart timer mDoorCheckDuration = 0; // restart timer
if(mDistance && // actor is not intended to be stationary if(mDistance && // actor is not intended to be stationary
idleNow && // but is in idle (wanderState == Wander_IdleNow) && // but is in idle
!walking && // FIXME: some actors are idle while walking
proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only
{ {
idleNow = false; wanderState = Wander_MoveNow;
moveNow = true;
mTrimCurrentNode = false; // just in case mTrimCurrentNode = false; // just in case
} }
} }
// Are we there yet? // Are we there yet?
bool& chooseAction = storage.mChooseAction; if ((wanderState == Wander_Walking) &&
if(walking &&
storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE))
{ {
stopWalking(actor, storage); stopWalking(actor, storage);
chooseAction = true; wanderState = Wander_ChooseAction;
mHasReturnPosition = false; mHasReturnPosition = false;
} }
if(walking) // have not yet reached the destination if (wanderState == Wander_Walking) // have not yet reached the destination
{ {
// turn towards the next point in mPath // turn towards the next point in mPath
zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
evadeObstacles(actor, storage, duration); evadeObstacles(actor, storage, duration);
} }
bool& rotate = storage.mTurnActorGivingGreetingToFacePlayer;
float& targetAngleRadians = storage.mTargetAngleRadians;
bool& rotate = storage.mRotate;
if (rotate) if (rotate)
{ {
// Reduce the turning animation glitch by using a *HUGE* value of // Reduce the turning animation glitch by using a *HUGE* value of
// epsilon... TODO: a proper fix might be in either the physics or the // epsilon... TODO: a proper fix might be in either the physics or the
// animation subsystem // animation subsystem
if (zTurn(actor, targetAngleRadians, osg::DegreesToRadians(5.f))) if (zTurn(actor, storage.mTargetAngleRadians, osg::DegreesToRadians(5.f)))
rotate = false; rotate = false;
} }
// Check if idle animation finished // Check if idle animation finished
short unsigned& playedIdle = storage.mPlayedIdle; short unsigned& idleAnimation = storage.mIdleAnimation;
GreetingState& greetingState = storage.mSaidGreeting; GreetingState& greetingState = storage.mSaidGreeting;
if(idleNow && !checkIdle(actor, playedIdle) && (greetingState == Greet_Done || greetingState == Greet_None)) if ((wanderState == Wander_IdleNow) &&
!checkIdle(actor, idleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
{ {
playedIdle = 0; wanderState = Wander_ChooseAction;
idleNow = false;
chooseAction = true;
} }
MWBase::World *world = MWBase::Environment::get().getWorld(); MWBase::World *world = MWBase::Environment::get().getWorld();
if(chooseAction) if (wanderState == Wander_ChooseAction)
{ {
playedIdle = 0; idleAnimation = getRandomIdle();
getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection
if(!playedIdle && mDistance) if(!idleAnimation && mDistance)
{ {
chooseAction = false; wanderState = Wander_MoveNow;
moveNow = true;
} }
else else
{ {
// Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander: // Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander:
MWWorld::TimeStamp currentTime = world->getTimeStamp(); MWWorld::TimeStamp currentTime = world->getTimeStamp();
mStartTime = currentTime; mStartTime = currentTime;
playIdle(actor, playedIdle); playIdle(actor, idleAnimation);
chooseAction = false; wanderState = Wander_IdleNow;
idleNow = true;
} }
} }
@ -334,9 +317,6 @@ namespace MWMechanics
mHasReturnPosition = false; mHasReturnPosition = false;
if (mDistance == 0 && mHasReturnPosition && (pos.asVec3() - mReturnPosition).length2() > 20*20) if (mDistance == 0 && mHasReturnPosition && (pos.asVec3() - mReturnPosition).length2() > 20*20)
{ {
chooseAction = false;
idleNow = false;
if (!storage.mPathFinder.isPathConstructed()) if (!storage.mPathFinder.isPathConstructed())
{ {
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition)); ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition));
@ -349,19 +329,18 @@ namespace MWMechanics
if(storage.mPathFinder.isPathConstructed()) if(storage.mPathFinder.isPathConstructed())
{ {
moveNow = false; wanderState = Wander_Walking;
walking = true;
} }
} }
} }
// Allow interrupting a walking actor to trigger a greeting // Allow interrupting a walking actor to trigger a greeting
if(idleNow || walking) if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking))
{ {
playGreetingIfPlayerGetsTooClose(actor, storage); playGreetingIfPlayerGetsTooClose(actor, storage);
} }
if(moveNow && mDistance) if ((wanderState == Wander_MoveNow) && mDistance)
{ {
// Construct a new path if there isn't one // Construct a new path if there isn't one
if(!storage.mPathFinder.isPathConstructed()) if(!storage.mPathFinder.isPathConstructed())
@ -388,8 +367,7 @@ namespace MWMechanics
trimAllowedNodes(mAllowedNodes, storage.mPathFinder); trimAllowedNodes(mAllowedNodes, storage.mPathFinder);
mObstacleCheck.clear(); mObstacleCheck.clear();
storage.mPathFinder.clearPath(); storage.mPathFinder.clearPath();
storage.mWalking = false; storage.mState = Wander_MoveNow;
storage.mMoveNow = true;
} }
else // probably walking into another NPC else // probably walking into another NPC
{ {
@ -399,7 +377,7 @@ namespace MWMechanics
actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f; actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f;
// change the angle a bit, too // change the angle a bit, too
const ESM::Position& pos = actor.getRefData().getPosition(); const ESM::Position& pos = actor.getRefData().getPosition();
zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]))); zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1]));
} }
mStuckCount++; // TODO: maybe no longer needed mStuckCount++; // TODO: maybe no longer needed
} }
@ -411,7 +389,7 @@ namespace MWMechanics
mObstacleCheck.clear(); mObstacleCheck.clear();
stopWalking(actor, storage); stopWalking(actor, storage);
storage.mChooseAction = true; storage.mState = Wander_ChooseAction;
mStuckCount = 0; mStuckCount = 0;
} }
//#endif //#endif
@ -483,22 +461,14 @@ namespace MWMechanics
{ {
greetingTimer++; greetingTimer++;
if (storage.mWalking) if (storage.mState == Wander_Walking)
{ {
stopWalking(actor, storage); stopWalking(actor, storage);
mObstacleCheck.clear(); mObstacleCheck.clear();
storage.mIdleNow = true; storage.mState = Wander_IdleNow;
getRandomIdle(storage.mPlayedIdle);
} }
if (!storage.mRotate) turnActorToFacePlayer(actorPos, playerPos, storage);
{
osg::Vec3f dir = playerPos - actorPos;
float faceAngleRadians = std::atan2(dir.x(), dir.y());
storage.mTargetAngleRadians = faceAngleRadians;
storage.mRotate = true;
}
if (greetingTimer >= GREETING_SHOULD_END) if (greetingTimer >= GREETING_SHOULD_END)
{ {
@ -515,6 +485,15 @@ namespace MWMechanics
} }
} }
void AiWander::turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage)
{
osg::Vec3f dir = playerPosition - actorPosition;
float faceAngleRadians = std::atan2(dir.x(), dir.y());
storage.mTargetAngleRadians = faceAngleRadians;
storage.mTurnActorGivingGreetingToFacePlayer = true;
}
void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos) void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
{ {
unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size());
@ -539,8 +518,7 @@ namespace MWMechanics
mAllowedNodes.push_back(mCurrentNode); mAllowedNodes.push_back(mCurrentNode);
mCurrentNode = temp; mCurrentNode = temp;
storage.mMoveNow = false; storage.mState = Wander_Walking;
storage.mWalking = true;
} }
// Choose a different node and delete this one from possible nodes because it is uncreachable: // Choose a different node and delete this one from possible nodes because it is uncreachable:
else else
@ -591,8 +569,6 @@ namespace MWMechanics
{ {
storage.mPathFinder.clearPath(); storage.mPathFinder.clearPath();
actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
storage.mMoveNow = false;
storage.mWalking = false;
} }
void AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) void AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect)
@ -626,9 +602,10 @@ namespace MWMechanics
} }
} }
void AiWander::getRandomIdle(short unsigned& playedIdle) short unsigned AiWander::getRandomIdle()
{ {
unsigned short idleRoll = 0; unsigned short idleRoll = 0;
short unsigned selectedAnimation = 0;
for(unsigned int counter = 0; counter < mIdle.size(); counter++) for(unsigned int counter = 0; counter < mIdle.size(); counter++)
{ {
@ -639,10 +616,11 @@ namespace MWMechanics
unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier)); unsigned short randSelect = (int)(Misc::Rng::rollProbability() * int(100 / fIdleChanceMultiplier));
if(randSelect < idleChance && randSelect > idleRoll) if(randSelect < idleChance && randSelect > idleRoll)
{ {
playedIdle = counter+2; selectedAnimation = counter + GroupIndex_MinIdle;
idleRoll = randSelect; idleRoll = randSelect;
} }
} }
return selectedAnimation;
} }
void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state) void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state)

View file

@ -63,6 +63,13 @@ namespace MWMechanics
Greet_InProgress, Greet_InProgress,
Greet_Done Greet_Done
}; };
enum WanderState {
Wander_ChooseAction,
Wander_IdleNow,
Wander_MoveNow,
Wander_Walking
};
private: private:
// NOTE: mDistance and mDuration must be set already // NOTE: mDistance and mDuration must be set already
void init(); void init();
@ -70,11 +77,12 @@ namespace MWMechanics
void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage); void stopWalking(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
void getRandomIdle(unsigned short& playedIdle); short unsigned getRandomIdle();
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos); void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage); void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration); void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration);
void playIdleDialogueRandomly(const MWWorld::Ptr& actor); void playIdleDialogueRandomly(const MWWorld::Ptr& actor);
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
int mDistance; // how far the actor can wander from the spawn point int mDistance; // how far the actor can wander from the spawn point
int mDuration; int mDuration;

View file

@ -264,7 +264,7 @@ namespace MWMechanics
float directionX = nextPoint.mX - x; float directionX = nextPoint.mX - x;
float directionY = nextPoint.mY - y; float directionY = nextPoint.mY - y;
return osg::RadiansToDegrees(std::atan2(directionX, directionY)); return std::atan2(directionX, directionY);
} }
bool PathFinder::checkPathCompleted(float x, float y, float tolerance) bool PathFinder::checkPathCompleted(float x, float y, float tolerance)

View file

@ -40,7 +40,7 @@ namespace MWMechanics
bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); bool checkPathCompleted(float x, float y, float tolerance = PathTolerance);
///< \Returns true if we are within \a tolerance units of the last path point. ///< \Returns true if we are within \a tolerance units of the last path point.
/// In degrees /// In radians
float getZAngleToNext(float x, float y) const; float getZAngleToNext(float x, float y) const;
bool isPathConstructed() const bool isPathConstructed() const