2012-11-14 17:42:04 +00:00
# include "aiwander.hpp"
2018-08-14 19:05:43 +00:00
# include <components/debug/debuglog.hpp>
2015-04-22 15:58:55 +00:00
# include <components/misc/rng.hpp>
2014-06-12 21:27:04 +00:00
# include <components/esm/aisequence.hpp>
2013-05-26 15:49:44 +00:00
# include "../mwbase/world.hpp"
# include "../mwbase/environment.hpp"
# include "../mwbase/mechanicsmanager.hpp"
2014-01-16 12:49:26 +00:00
# include "../mwbase/dialoguemanager.hpp"
2014-01-19 16:03:25 +00:00
2014-02-23 19:11:05 +00:00
# include "../mwworld/class.hpp"
# include "../mwworld/esmstore.hpp"
# include "../mwworld/cellstore.hpp"
2013-05-26 15:49:44 +00:00
2017-11-27 17:30:31 +00:00
# include "pathgrid.hpp"
2014-02-23 19:11:05 +00:00
# include "creaturestats.hpp"
2014-01-29 19:29:07 +00:00
# include "steering.hpp"
2014-02-23 19:11:05 +00:00
# include "movement.hpp"
2015-08-16 06:55:02 +00:00
# include "coordinateconverter.hpp"
2015-08-21 09:12:39 +00:00
# include "actorutil.hpp"
2014-01-29 19:29:07 +00:00
2013-06-01 00:49:52 +00:00
namespace MWMechanics
2012-11-14 17:42:04 +00:00
{
2015-08-30 04:43:35 +00:00
static const int COUNT_BEFORE_RESET = 10 ;
2014-04-17 23:03:36 +00:00
static const float DOOR_CHECK_INTERVAL = 1.5f ;
2014-07-28 22:41:37 +00:00
static const int GREETING_SHOULD_START = 4 ; //how many reaction intervals should pass before NPC can greet player
static const int GREETING_SHOULD_END = 10 ;
2014-03-13 12:44:52 +00:00
2015-07-08 07:34:33 +00:00
// to prevent overcrowding
static const int DESTINATION_TOLERANCE = 64 ;
// distance must be long enough that NPC will need to move to get there.
static const int MINIMUM_WANDER_DISTANCE = DESTINATION_TOLERANCE * 2 ;
2015-03-23 07:57:36 +00:00
const std : : string AiWander : : sIdleSelectToGroupName [ GroupIndex_MaxIdle - GroupIndex_MinIdle + 1 ] =
{
std : : string ( " idle2 " ) ,
std : : string ( " idle3 " ) ,
std : : string ( " idle4 " ) ,
std : : string ( " idle5 " ) ,
std : : string ( " idle6 " ) ,
std : : string ( " idle7 " ) ,
std : : string ( " idle8 " ) ,
std : : string ( " idle9 " ) ,
} ;
2014-06-12 21:27:04 +00:00
AiWander : : AiWander ( int distance , int duration , int timeOfDay , const std : : vector < unsigned char > & idle , bool repeat ) :
2016-06-09 15:56:01 +00:00
mDistance ( distance ) , mDuration ( duration ) , mRemainingDuration ( duration ) , mTimeOfDay ( timeOfDay ) , mIdle ( idle ) ,
2018-08-22 21:20:25 +00:00
mRepeat ( repeat ) , mStoredInitialActorPosition ( false ) , mInitialActorPosition ( osg : : Vec3f ( 0 , 0 , 0 ) ) ,
mHasDestination ( false ) , mDestination ( osg : : Vec3f ( 0 , 0 , 0 ) ) , mUsePathgrid ( false )
2013-05-26 15:49:44 +00:00
{
2014-06-28 12:22:27 +00:00
mIdle . resize ( 8 , 0 ) ;
2014-06-12 21:27:04 +00:00
init ( ) ;
}
void AiWander : : init ( )
{
2014-08-14 16:55:54 +00:00
// NOTE: mDistance and mDuration must be set already
2013-06-01 00:49:52 +00:00
if ( mDistance < 0 )
mDistance = 0 ;
if ( mDuration < 0 )
mDuration = 0 ;
}
2012-11-14 17:42:04 +00:00
2013-06-01 00:49:52 +00:00
AiPackage * MWMechanics : : AiWander : : clone ( ) const
2013-05-26 15:49:44 +00:00
{
2013-06-01 00:49:52 +00:00
return new AiWander ( * this ) ;
}
2014-04-17 23:03:36 +00:00
/*
* AiWander high level states ( 0.29 .0 ) . Not entirely accurate in some cases
* e . g . non - NPC actors do not greet and some creatures may be moving even in
* the IdleNow state .
*
* [ select node ,
* build path ]
* + - - - - - - - - - - > MoveNow - - - - - - - - - - - > Walking
* | |
* [ allowed | |
* nodes ] | [ hello if near ] |
* start - - - > ChooseAction - - - - - > IdleNow |
* ^ ^ | |
* | | | |
* | + - - - - - - - - - - - + |
* | |
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
*
*
* New high level states . Not exactly as per vanilla ( e . g . door stuff )
* but the differences are required because our physics does not work like
2014-04-18 22:16:56 +00:00
* vanilla and therefore have to compensate / work around .
2014-04-17 23:03:36 +00:00
*
* [ select node , [ if stuck evade
* build path ] or remove nodes if near door ]
* + - - - - - - - - - - > MoveNow < - - - - - - - - - - > Walking
* | ^ | |
* | | ( near door ) | |
* [ allowed | | | |
* nodes ] | [ hello if near ] | |
* start - - - > ChooseAction - - - - - > IdleNow | |
* ^ ^ | ^ | |
* | | | | ( stuck near | |
* | + - - - - - - - - - - - + + - - - - - - - - - - - - - - - + |
* | player ) |
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
*
2014-04-20 00:06:03 +00:00
* NOTE : non - time critical operations are run once every 250 ms or so .
2014-04-17 23:03:36 +00:00
*
* TODO : It would be great if door opening / closing can be detected and pathgrid
2014-04-18 05:19:22 +00:00
* links dynamically updated . Currently ( 0.29 .0 ) AiWander allows choosing a
* destination beyond closed doors which sometimes makes the actors stuck at the
* door and impossible for the player to open the door .
2014-04-17 23:03:36 +00:00
*
* For now detect being stuck at the door and simply delete the nodes from the
* allowed set . The issue is when the door opens the allowed set is not
2014-04-18 05:19:22 +00:00
* re - calculated . However this would not be an issue in most cases since hostile
* actors will enter combat ( i . e . no longer wandering ) and different pathfinding
* will kick in .
2014-04-17 23:03:36 +00:00
*/
2015-06-26 15:47:04 +00:00
bool AiWander : : execute ( const MWWorld : : Ptr & actor , CharacterController & characterController , AiState & state , float duration )
2013-06-01 00:49:52 +00:00
{
2014-04-18 04:25:08 +00:00
MWMechanics : : CreatureStats & cStats = actor . getClass ( ) . getCreatureStats ( actor ) ;
2018-11-02 11:24:43 +00:00
if ( cStats . isDead ( ) | | cStats . getHealth ( ) . getCurrent ( ) < = 0 )
2014-04-18 04:25:08 +00:00
return true ; // Don't bother with dead actors
2018-11-02 11:24:43 +00:00
// get or create temporary storage
AiWanderStorage & storage = state . get < AiWanderStorage > ( ) ;
const MWWorld : : CellStore * & currentCell = storage . mCell ;
2014-10-10 21:32:15 +00:00
bool cellChange = currentCell & & ( actor . getCell ( ) ! = currentCell ) ;
2014-10-08 20:11:45 +00:00
if ( ! currentCell | | cellChange )
2014-04-17 23:03:36 +00:00
{
2016-09-05 12:18:34 +00:00
stopWalking ( actor , storage ) ;
2014-10-08 20:11:45 +00:00
currentCell = actor . getCell ( ) ;
2016-06-15 18:43:09 +00:00
storage . mPopulateAvailableNodes = true ;
2017-12-01 16:38:10 +00:00
mStoredInitialActorPosition = false ;
2014-04-17 23:03:36 +00:00
}
2016-06-09 15:56:01 +00:00
mRemainingDuration - = ( ( duration * MWBase : : Environment : : get ( ) . getWorld ( ) - > getTimeScaleFactor ( ) ) / 3600 ) ;
2016-05-28 16:02:22 +00:00
2014-04-17 23:03:36 +00:00
cStats . setDrawState ( DrawState_Nothing ) ;
cStats . setMovementFlag ( CreatureStats : : Flag_Run , false ) ;
2014-04-20 00:06:03 +00:00
ESM : : Position pos = actor . getRefData ( ) . getPosition ( ) ;
2017-04-30 13:29:59 +00:00
// If there is already a destination due to the package having been interrupted by a combat or pursue package,
// rebuild a path to it
if ( ! mPathFinder . isPathConstructed ( ) & & mHasDestination )
{
2018-08-22 21:20:25 +00:00
if ( mUsePathgrid )
{
mPathFinder . buildPathByPathgrid ( pos . asVec3 ( ) , mDestination , actor . getCell ( ) ,
getPathGridGraph ( actor . getCell ( ) ) ) ;
}
else
{
2018-11-02 11:24:43 +00:00
const osg : : Vec3f playerHalfExtents = MWBase : : Environment : : get ( ) . getWorld ( ) - > getHalfExtents ( getPlayer ( ) ) ; // Using player half extents for better performance
2018-10-15 20:07:52 +00:00
mPathFinder . buildPath ( actor , pos . asVec3 ( ) , mDestination , actor . getCell ( ) ,
getPathGridGraph ( actor . getCell ( ) ) , playerHalfExtents , getNavigatorFlags ( actor ) ) ;
2018-08-22 21:20:25 +00:00
}
2017-04-30 13:29:59 +00:00
if ( mPathFinder . isPathConstructed ( ) )
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_Walking ) ;
2017-04-30 13:29:59 +00:00
}
2017-11-28 08:05:05 +00:00
2018-08-18 15:48:34 +00:00
doPerFrameActionsForState ( actor , duration , storage ) ;
2014-12-01 14:08:55 +00:00
2014-10-10 21:32:15 +00:00
float & lastReaction = storage . mReaction ;
2014-10-08 20:11:45 +00:00
lastReaction + = duration ;
2016-08-19 19:15:26 +00:00
if ( AI_REACTION_TIME < = lastReaction )
2014-04-20 00:06:03 +00:00
{
2015-07-26 05:28:32 +00:00
lastReaction = 0 ;
2018-08-18 15:26:00 +00:00
return reactionTimeActions ( actor , storage , currentCell , cellChange , pos ) ;
2014-04-20 00:06:03 +00:00
}
2014-06-12 21:27:04 +00:00
else
2015-07-26 05:28:32 +00:00
return false ;
}
2014-04-20 00:06:03 +00:00
2015-07-26 05:28:32 +00:00
bool AiWander : : reactionTimeActions ( const MWWorld : : Ptr & actor , AiWanderStorage & storage ,
2018-08-18 15:26:00 +00:00
const MWWorld : : CellStore * & currentCell , bool cellChange , ESM : : Position & pos )
2015-07-26 05:28:32 +00:00
{
2016-04-16 02:56:41 +00:00
if ( mDistance < = 0 )
storage . mCanWanderAlongPathGrid = false ;
2015-07-26 05:29:01 +00:00
if ( isPackageCompleted ( actor , storage ) )
2014-06-14 18:02:49 +00:00
{
2016-06-09 15:56:01 +00:00
// Reset package so it can be used again
mRemainingDuration = mDuration ;
init ( ) ;
2015-07-26 05:29:01 +00:00
return true ;
2013-05-26 15:49:44 +00:00
}
2016-04-14 01:48:08 +00:00
if ( ! mStoredInitialActorPosition )
{
mInitialActorPosition = actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ;
mStoredInitialActorPosition = true ;
}
2014-04-17 23:03:36 +00:00
// Initialization to discover & store allowed node points for this actor.
2016-06-15 18:43:09 +00:00
if ( storage . mPopulateAvailableNodes )
2013-06-01 00:49:52 +00:00
{
2016-04-16 02:56:41 +00:00
getAllowedNodes ( actor , currentCell - > getCell ( ) , storage ) ;
2013-05-26 15:49:44 +00:00
}
2017-11-28 14:03:13 +00:00
bool actorCanMoveByZ = ( actor . getClass ( ) . canSwim ( actor ) & & MWBase : : Environment : : get ( ) . getWorld ( ) - > isSwimming ( actor ) )
2018-12-05 20:28:26 +00:00
| | MWBase : : Environment : : get ( ) . getWorld ( ) - > isFlying ( actor )
| | ! MWBase : : Environment : : get ( ) . getWorld ( ) - > isActorCollisionEnabled ( actor ) ;
2017-11-28 14:03:13 +00:00
if ( actorCanMoveByZ & & mDistance > 0 ) {
// Typically want to idle for a short time before the next wander
2018-06-27 08:48:34 +00:00
if ( Misc : : Rng : : rollDice ( 100 ) > = 92 & & storage . mState ! = AiWanderStorage : : Wander_Walking ) {
2017-11-28 14:03:13 +00:00
wanderNearStart ( actor , storage , mDistance ) ;
}
storage . mCanWanderAlongPathGrid = false ;
}
2017-04-30 13:29:59 +00:00
// If the package has a wander distance but no pathgrid is available,
// randomly idle or wander near spawn point
2017-11-28 14:03:13 +00:00
else if ( storage . mAllowedNodes . empty ( ) & & mDistance > 0 & & ! storage . mIsWanderingManually ) {
2016-04-13 02:16:55 +00:00
// Typically want to idle for a short time before the next wander
if ( Misc : : Rng : : rollDice ( 100 ) > = 96 ) {
2016-04-16 02:56:41 +00:00
wanderNearStart ( actor , storage , mDistance ) ;
2016-04-17 01:38:58 +00:00
} else {
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_IdleNow ) ;
2016-04-13 02:16:55 +00:00
}
2016-06-15 18:43:09 +00:00
} else if ( storage . mAllowedNodes . empty ( ) & & ! storage . mIsWanderingManually ) {
2016-04-16 02:56:41 +00:00
storage . mCanWanderAlongPathGrid = false ;
2016-04-13 02:16:55 +00:00
}
2016-04-16 17:14:00 +00:00
// If Wandering manually and hit an obstacle, stop
2018-08-18 15:26:00 +00:00
if ( storage . mIsWanderingManually & & mObstacleCheck . isEvading ( ) ) {
2016-04-17 01:38:58 +00:00
completeManualWalking ( actor , storage ) ;
2016-04-13 02:16:55 +00:00
}
2013-11-25 14:38:18 +00:00
2013-06-01 00:49:52 +00:00
// Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles.
2014-04-17 23:03:36 +00:00
if ( mDistance & & cellChange )
2013-06-01 00:49:52 +00:00
mDistance = 0 ;
2013-05-26 15:49:44 +00:00
2014-03-24 12:20:25 +00:00
// Allow interrupting a walking actor to trigger a greeting
2018-06-27 08:48:34 +00:00
AiWanderStorage : : WanderState & wanderState = storage . mState ;
if ( ( wanderState = = AiWanderStorage : : Wander_IdleNow ) | | ( wanderState = = AiWanderStorage : : Wander_Walking ) )
2013-05-26 15:49:44 +00:00
{
2015-07-12 04:37:31 +00:00
playGreetingIfPlayerGetsTooClose ( actor , storage ) ;
2013-05-26 15:49:44 +00:00
}
2014-10-10 21:32:15 +00:00
2018-06-27 08:48:34 +00:00
if ( ( wanderState = = AiWanderStorage : : Wander_MoveNow ) & & storage . mCanWanderAlongPathGrid )
2014-04-20 00:06:03 +00:00
{
2014-04-17 23:03:36 +00:00
// Construct a new path if there isn't one
2016-08-19 19:15:26 +00:00
if ( ! mPathFinder . isPathConstructed ( ) )
2013-06-01 00:49:52 +00:00
{
2016-06-15 18:43:09 +00:00
if ( ! storage . mAllowedNodes . empty ( ) )
2013-06-01 00:49:52 +00:00
{
2015-07-12 04:35:15 +00:00
setPathToAnAllowedNode ( actor , storage , pos ) ;
2013-06-01 00:49:52 +00:00
}
2017-11-28 08:05:05 +00:00
}
2018-08-18 11:01:02 +00:00
}
2018-08-19 22:26:58 +00:00
else if ( storage . mIsWanderingManually & & mPathFinder . checkPathCompleted ( ) )
2018-08-18 11:01:02 +00:00
{
2016-04-17 01:38:58 +00:00
completeManualWalking ( actor , storage ) ;
2013-06-01 00:49:52 +00:00
}
2013-06-01 00:01:42 +00:00
2014-04-17 23:03:36 +00:00
return false ; // AiWander package not yet completed
}
2014-04-20 00:06:03 +00:00
2016-05-28 16:02:22 +00:00
bool AiWander : : getRepeat ( ) const
2017-11-28 08:05:05 +00:00
{
2017-12-01 14:53:31 +00:00
return mRepeat ;
2016-05-28 16:02:22 +00:00
}
2017-12-01 14:53:31 +00:00
osg : : Vec3f AiWander : : getDestination ( const MWWorld : : Ptr & actor ) const
{
if ( mHasDestination )
return mDestination ;
2018-11-02 11:24:43 +00:00
return actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ;
2017-12-01 14:53:31 +00:00
}
2016-06-15 18:43:09 +00:00
2015-07-26 05:29:01 +00:00
bool AiWander : : isPackageCompleted ( const MWWorld : : Ptr & actor , AiWanderStorage & storage )
{
if ( mDuration )
{
2016-06-09 15:56:01 +00:00
// End package if duration is complete
if ( mRemainingDuration < = 0 )
2014-04-20 00:06:03 +00:00
{
2017-12-01 14:53:31 +00:00
stopWalking ( actor , storage ) ;
return true ;
2014-04-20 00:06:03 +00:00
}
2015-07-26 05:29:01 +00:00
}
// if get here, not yet completed
return false ;
}
2016-04-13 02:16:55 +00:00
/*
* Commands actor to walk to a random location near original spawn location .
*/
2016-04-16 02:56:41 +00:00
void AiWander : : wanderNearStart ( const MWWorld : : Ptr & actor , AiWanderStorage & storage , int wanderDistance ) {
2018-07-21 09:30:14 +00:00
const auto currentPosition = actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ;
2016-04-16 21:39:13 +00:00
std : : size_t attempts = 10 ; // If a unit can't wander out of water, don't want to hang here
bool isWaterCreature = actor . getClass ( ) . isPureWaterCreature ( actor ) ;
do {
// Determine a random location within radius of original position
2017-11-28 14:03:13 +00:00
const float wanderRadius = ( 0.2f + Misc : : Rng : : rollClosedProbability ( ) * 0.8f ) * wanderDistance ;
2018-09-17 10:52:43 +00:00
const float randomDirection = Misc : : Rng : : rollClosedProbability ( ) * 2.0f * osg : : PI ;
2016-04-16 21:39:13 +00:00
const float destinationX = mInitialActorPosition . x ( ) + wanderRadius * std : : cos ( randomDirection ) ;
const float destinationY = mInitialActorPosition . y ( ) + wanderRadius * std : : sin ( randomDirection ) ;
const float destinationZ = mInitialActorPosition . z ( ) ;
2017-04-30 13:29:59 +00:00
mDestination = osg : : Vec3f ( destinationX , destinationY , destinationZ ) ;
2016-04-16 21:39:13 +00:00
// Check if land creature will walk onto water or if water creature will swim onto land
2017-04-30 13:29:59 +00:00
if ( ( ! isWaterCreature & & ! destinationIsAtWater ( actor , mDestination ) ) | |
2018-07-21 09:30:14 +00:00
( isWaterCreature & & ! destinationThroughGround ( currentPosition , mDestination ) ) )
{
2018-11-02 11:24:43 +00:00
// Using player half extents for better performance
const osg : : Vec3f playerHalfExtents = MWBase : : Environment : : get ( ) . getWorld ( ) - > getHalfExtents ( getPlayer ( ) ) ;
mPathFinder . buildPath ( actor , currentPosition , mDestination , actor . getCell ( ) ,
2018-08-22 21:20:25 +00:00
getPathGridGraph ( actor . getCell ( ) ) , playerHalfExtents , getNavigatorFlags ( actor ) ) ;
2018-11-02 11:24:43 +00:00
mPathFinder . addPointToPath ( mDestination ) ;
2016-08-19 19:15:26 +00:00
if ( mPathFinder . isPathConstructed ( ) )
{
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_Walking , true ) ;
2017-04-30 13:29:59 +00:00
mHasDestination = true ;
2018-08-22 21:20:25 +00:00
mUsePathgrid = false ;
2016-08-19 19:15:26 +00:00
}
2016-04-16 21:39:13 +00:00
return ;
}
2016-04-16 22:51:13 +00:00
} while ( - - attempts ) ;
2016-04-16 21:39:13 +00:00
}
/*
* Returns true if the position provided is above water .
*/
bool AiWander : : destinationIsAtWater ( const MWWorld : : Ptr & actor , const osg : : Vec3f & destination ) {
2016-04-17 15:46:09 +00:00
float heightToGroundOrWater = MWBase : : Environment : : get ( ) . getWorld ( ) - > getDistToNearestRayHit ( destination , osg : : Vec3f ( 0 , 0 , - 1 ) , 1000.0 , true ) ;
2016-04-16 21:39:13 +00:00
osg : : Vec3f positionBelowSurface = destination ;
2016-04-17 15:46:09 +00:00
positionBelowSurface [ 2 ] = positionBelowSurface [ 2 ] - heightToGroundOrWater - 1.0f ;
2016-04-16 21:39:13 +00:00
return MWBase : : Environment : : get ( ) . getWorld ( ) - > isUnderwater ( actor . getCell ( ) , positionBelowSurface ) ;
}
2016-04-13 02:16:55 +00:00
2016-04-16 21:39:13 +00:00
/*
* Returns true if the start to end point travels through a collision point ( land ) .
*/
bool AiWander : : destinationThroughGround ( const osg : : Vec3f & startPoint , const osg : : Vec3f & destination ) {
return MWBase : : Environment : : get ( ) . getWorld ( ) - > castRay ( startPoint . x ( ) , startPoint . y ( ) , startPoint . z ( ) ,
destination . x ( ) , destination . y ( ) , destination . z ( ) ) ;
2016-04-13 02:16:55 +00:00
}
2016-04-17 01:38:58 +00:00
void AiWander : : completeManualWalking ( const MWWorld : : Ptr & actor , AiWanderStorage & storage ) {
stopWalking ( actor , storage ) ;
2016-08-19 19:15:26 +00:00
mObstacleCheck . clear ( ) ;
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_IdleNow ) ;
2016-04-17 01:38:58 +00:00
}
2018-08-18 15:48:34 +00:00
void AiWander : : doPerFrameActionsForState ( const MWWorld : : Ptr & actor , float duration , AiWanderStorage & storage )
2015-07-26 05:25:44 +00:00
{
switch ( storage . mState )
{
2018-06-27 08:48:34 +00:00
case AiWanderStorage : : Wander_IdleNow :
2015-07-26 05:25:44 +00:00
onIdleStatePerFrameActions ( actor , duration , storage ) ;
break ;
2018-06-27 08:48:34 +00:00
case AiWanderStorage : : Wander_Walking :
2018-08-18 15:48:34 +00:00
onWalkingStatePerFrameActions ( actor , duration , storage ) ;
2015-07-26 05:25:44 +00:00
break ;
2018-06-27 08:48:34 +00:00
case AiWanderStorage : : Wander_ChooseAction :
2015-07-26 05:25:44 +00:00
onChooseActionStatePerFrameActions ( actor , storage ) ;
break ;
2018-06-27 08:48:34 +00:00
case AiWanderStorage : : Wander_MoveNow :
2015-07-26 05:25:44 +00:00
break ; // nothing to do
default :
// should never get here
assert ( false ) ;
break ;
}
}
2015-07-26 05:24:33 +00:00
void AiWander : : onIdleStatePerFrameActions ( const MWWorld : : Ptr & actor , float duration , AiWanderStorage & storage )
{
// Check if an idle actor is too close to a door - if so start walking
2016-06-15 18:43:09 +00:00
storage . mDoorCheckDuration + = duration ;
2017-06-14 08:44:18 +00:00
2016-06-15 18:43:09 +00:00
if ( storage . mDoorCheckDuration > = DOOR_CHECK_INTERVAL )
2015-07-26 05:24:33 +00:00
{
2016-06-15 18:43:09 +00:00
storage . mDoorCheckDuration = 0 ; // restart timer
2017-10-15 15:03:11 +00:00
static float distance = MWBase : : Environment : : get ( ) . getWorld ( ) - > getMaxActivationDistance ( ) ;
2015-07-26 05:24:33 +00:00
if ( mDistance & & // actor is not intended to be stationary
2017-06-14 08:44:18 +00:00
proximityToDoor ( actor , distance * 1.6f ) )
2015-07-26 05:24:33 +00:00
{
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_MoveNow ) ;
2016-06-15 18:43:09 +00:00
storage . mTrimCurrentNode = false ; // just in case
2015-07-26 05:24:33 +00:00
return ;
2014-04-20 00:06:03 +00:00
}
}
2015-07-26 05:24:33 +00:00
bool & rotate = storage . mTurnActorGivingGreetingToFacePlayer ;
2014-10-08 20:11:45 +00:00
if ( rotate )
2014-04-20 01:59:47 +00:00
{
2014-04-20 04:27:18 +00:00
// Reduce the turning animation glitch by using a *HUGE* value of
// epsilon... TODO: a proper fix might be in either the physics or the
// animation subsystem
2015-07-26 05:24:33 +00:00
if ( zTurn ( actor , storage . mTargetAngleRadians , osg : : DegreesToRadians ( 5.f ) ) )
2014-10-08 20:11:45 +00:00
rotate = false ;
2014-04-20 01:59:47 +00:00
}
2014-12-01 14:08:55 +00:00
// Check if idle animation finished
2018-06-27 08:48:34 +00:00
AiWanderStorage : : GreetingState & greetingState = storage . mSaidGreeting ;
if ( ! checkIdle ( actor , storage . mIdleAnimation ) & & ( greetingState = = AiWanderStorage : : Greet_Done | | greetingState = = AiWanderStorage : : Greet_None ) )
2014-12-01 14:08:55 +00:00
{
2017-04-30 13:29:59 +00:00
if ( mPathFinder . isPathConstructed ( ) )
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_Walking ) ;
2017-04-30 13:29:59 +00:00
else
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_ChooseAction ) ;
2014-12-01 14:08:55 +00:00
}
2015-07-26 05:24:33 +00:00
}
2014-12-01 14:08:55 +00:00
2018-08-18 15:48:34 +00:00
void AiWander : : onWalkingStatePerFrameActions ( const MWWorld : : Ptr & actor , float duration , AiWanderStorage & storage )
2015-07-26 05:25:00 +00:00
{
2016-12-16 22:18:28 +00:00
// Is there no destination or are we there yet?
2018-07-21 09:30:14 +00:00
if ( ( ! mPathFinder . isPathConstructed ( ) ) | | pathTo ( actor , osg : : Vec3f ( mPathFinder . getPath ( ) . back ( ) ) , duration , DESTINATION_TOLERANCE ) )
2015-07-26 05:25:00 +00:00
{
stopWalking ( actor , storage ) ;
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_ChooseAction ) ;
2015-07-26 05:25:00 +00:00
}
else
{
// have not yet reached the destination
2018-08-18 15:38:08 +00:00
evadeObstacles ( actor , storage ) ;
2015-07-26 05:25:00 +00:00
}
}
2015-07-26 05:25:44 +00:00
void AiWander : : onChooseActionStatePerFrameActions ( const MWWorld : : Ptr & actor , AiWanderStorage & storage )
{
2017-04-30 13:29:59 +00:00
unsigned short idleAnimation = getRandomIdle ( ) ;
storage . mIdleAnimation = idleAnimation ;
2014-12-01 14:08:55 +00:00
2015-07-26 05:25:44 +00:00
if ( ! idleAnimation & & mDistance )
2014-12-01 14:08:55 +00:00
{
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_MoveNow ) ;
2015-07-30 12:20:16 +00:00
return ;
2015-07-26 05:25:44 +00:00
}
2015-07-30 12:15:45 +00:00
if ( idleAnimation )
{
2015-07-30 12:20:16 +00:00
if ( std : : find ( storage . mBadIdles . begin ( ) , storage . mBadIdles . end ( ) , idleAnimation ) = = storage . mBadIdles . end ( ) )
{
if ( ! playIdle ( actor , idleAnimation ) )
{
storage . mBadIdles . push_back ( idleAnimation ) ;
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_ChooseAction ) ;
2015-07-30 12:20:16 +00:00
return ;
}
}
2015-07-30 12:15:45 +00:00
}
2016-05-28 16:02:22 +00:00
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_IdleNow ) ;
2015-07-26 05:25:44 +00:00
}
2018-08-18 15:38:08 +00:00
void AiWander : : evadeObstacles ( const MWWorld : : Ptr & actor , AiWanderStorage & storage )
2015-07-12 04:38:36 +00:00
{
2016-08-19 19:15:26 +00:00
if ( mObstacleCheck . isEvading ( ) )
2015-07-12 04:38:36 +00:00
{
// first check if we're walking into a door
2017-10-15 15:03:11 +00:00
static float distance = MWBase : : Environment : : get ( ) . getWorld ( ) - > getMaxActivationDistance ( ) ;
2017-06-14 08:44:18 +00:00
if ( proximityToDoor ( actor , distance ) )
2014-12-01 14:08:55 +00:00
{
2015-07-12 04:38:36 +00:00
// remove allowed points then select another random destination
2016-06-15 18:43:09 +00:00
storage . mTrimCurrentNode = true ;
2016-08-19 19:15:26 +00:00
trimAllowedNodes ( storage . mAllowedNodes , mPathFinder ) ;
mObstacleCheck . clear ( ) ;
2017-04-30 13:29:59 +00:00
stopWalking ( actor , storage ) ;
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_MoveNow ) ;
2014-12-01 14:08:55 +00:00
}
2016-08-19 19:15:26 +00:00
storage . mStuckCount + + ; // TODO: maybe no longer needed
2015-07-12 04:38:36 +00:00
}
2015-08-30 04:43:35 +00:00
// if stuck for sufficiently long, act like current location was the destination
2016-06-15 18:43:09 +00:00
if ( storage . mStuckCount > = COUNT_BEFORE_RESET ) // something has gone wrong, reset
2015-07-12 04:38:36 +00:00
{
2016-08-19 19:15:26 +00:00
mObstacleCheck . clear ( ) ;
2015-07-12 04:38:36 +00:00
stopWalking ( actor , storage ) ;
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_ChooseAction ) ;
2016-06-15 18:43:09 +00:00
storage . mStuckCount = 0 ;
2014-12-01 14:08:55 +00:00
}
2015-07-12 04:38:36 +00:00
}
2014-12-01 14:08:55 +00:00
2015-07-12 04:37:31 +00:00
void AiWander : : playGreetingIfPlayerGetsTooClose ( const MWWorld : : Ptr & actor , AiWanderStorage & storage )
{
// Play a random voice greeting if the player gets too close
int hello = actor . getClass ( ) . getCreatureStats ( actor ) . getAiSetting ( CreatureStats : : AI_Hello ) . getModified ( ) ;
float helloDistance = static_cast < float > ( hello ) ;
static int iGreetDistanceMultiplier = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( )
2018-08-29 15:38:12 +00:00
. get < ESM : : GameSetting > ( ) . find ( " iGreetDistanceMultiplier " ) - > mValue . getInteger ( ) ;
2015-07-12 04:37:31 +00:00
helloDistance * = iGreetDistanceMultiplier ;
2014-04-20 00:06:03 +00:00
2015-08-21 09:12:39 +00:00
MWWorld : : Ptr player = getPlayer ( ) ;
2015-07-12 04:37:31 +00:00
osg : : Vec3f playerPos ( player . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
osg : : Vec3f actorPos ( actor . getRefData ( ) . getPosition ( ) . asVec3 ( ) ) ;
2014-04-20 00:06:03 +00:00
2015-07-12 04:37:31 +00:00
int & greetingTimer = storage . mGreetingTimer ;
2018-06-27 08:48:34 +00:00
AiWanderStorage : : GreetingState & greetingState = storage . mSaidGreeting ;
if ( greetingState = = AiWanderStorage : : Greet_None )
2013-05-26 15:49:44 +00:00
{
2018-11-02 11:24:43 +00:00
if ( ( playerPos - actorPos ) . length2 ( ) < = helloDistance * helloDistance & &
2015-07-12 04:37:31 +00:00
! player . getClass ( ) . getCreatureStats ( player ) . isDead ( ) & & MWBase : : Environment : : get ( ) . getWorld ( ) - > getLOS ( player , actor )
& & MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > awarenessCheck ( player , actor ) )
greetingTimer + + ;
if ( greetingTimer > = GREETING_SHOULD_START )
2013-05-26 15:49:44 +00:00
{
2018-06-27 08:48:34 +00:00
greetingState = AiWanderStorage : : Greet_InProgress ;
2015-07-12 04:37:31 +00:00
MWBase : : Environment : : get ( ) . getDialogueManager ( ) - > say ( actor , " hello " ) ;
greetingTimer = 0 ;
2013-05-26 15:49:44 +00:00
}
}
2018-06-27 08:48:34 +00:00
if ( greetingState = = AiWanderStorage : : Greet_InProgress )
2013-06-01 00:49:52 +00:00
{
2015-07-12 04:37:31 +00:00
greetingTimer + + ;
2013-05-26 15:49:44 +00:00
2018-06-27 08:48:34 +00:00
if ( storage . mState = = AiWanderStorage : : Wander_Walking )
2014-04-29 09:17:07 +00:00
{
2017-04-30 13:29:59 +00:00
stopWalking ( actor , storage , false ) ;
2016-08-19 19:15:26 +00:00
mObstacleCheck . clear ( ) ;
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_IdleNow ) ;
2015-07-12 04:37:31 +00:00
}
2014-04-29 07:09:51 +00:00
2015-07-19 06:00:49 +00:00
turnActorToFacePlayer ( actorPos , playerPos , storage ) ;
2014-04-29 07:09:51 +00:00
2015-07-12 04:37:31 +00:00
if ( greetingTimer > = GREETING_SHOULD_END )
{
2018-06-27 08:48:34 +00:00
greetingState = AiWanderStorage : : Greet_Done ;
2015-07-12 04:37:31 +00:00
greetingTimer = 0 ;
2014-04-29 07:09:51 +00:00
}
}
2018-06-27 08:48:34 +00:00
if ( greetingState = = AiWanderStorage : : Greet_Done )
2013-05-26 15:49:44 +00:00
{
2015-07-12 04:37:31 +00:00
float resetDist = 2 * helloDistance ;
2018-11-02 11:24:43 +00:00
if ( ( playerPos - actorPos ) . length2 ( ) > = resetDist * resetDist )
2018-06-27 08:48:34 +00:00
greetingState = AiWanderStorage : : Greet_None ;
2015-07-12 04:37:31 +00:00
}
}
2014-01-19 16:03:25 +00:00
2015-07-19 06:00:49 +00:00
void AiWander : : turnActorToFacePlayer ( const osg : : Vec3f & actorPosition , const osg : : Vec3f & playerPosition , AiWanderStorage & storage )
{
osg : : Vec3f dir = playerPosition - actorPosition ;
2014-12-01 14:08:55 +00:00
2015-07-19 06:00:49 +00:00
float faceAngleRadians = std : : atan2 ( dir . x ( ) , dir . y ( ) ) ;
storage . mTargetAngleRadians = faceAngleRadians ;
storage . mTurnActorGivingGreetingToFacePlayer = true ;
}
2014-04-20 00:36:01 +00:00
2015-07-12 04:35:15 +00:00
void AiWander : : setPathToAnAllowedNode ( const MWWorld : : Ptr & actor , AiWanderStorage & storage , const ESM : : Position & actorPos )
{
2016-06-15 18:43:09 +00:00
unsigned int randNode = Misc : : Rng : : rollDice ( storage . mAllowedNodes . size ( ) ) ;
ESM : : Pathgrid : : Point dest ( storage . mAllowedNodes [ randNode ] ) ;
2017-11-28 14:03:13 +00:00
2015-07-12 04:35:15 +00:00
ToWorldCoordinates ( dest , storage . mCell - > getCell ( ) ) ;
2013-05-26 15:49:44 +00:00
2016-12-14 21:11:22 +00:00
// actor position is already in world coordinates
2018-11-02 11:24:43 +00:00
const osg : : Vec3f start = actorPos . asVec3 ( ) ;
2013-05-26 15:49:44 +00:00
2015-07-12 04:35:15 +00:00
// don't take shortcuts for wandering
2018-11-02 11:24:43 +00:00
const osg : : Vec3f destVec3f = PathFinder : : makeOsgVec3 ( dest ) ;
2018-08-22 21:20:25 +00:00
mPathFinder . buildPathByPathgrid ( start , destVec3f , actor . getCell ( ) , getPathGridGraph ( actor . getCell ( ) ) ) ;
2013-05-26 15:49:44 +00:00
2016-08-19 19:15:26 +00:00
if ( mPathFinder . isPathConstructed ( ) )
2015-07-12 04:35:15 +00:00
{
2018-07-21 09:30:14 +00:00
mDestination = destVec3f ;
2017-04-30 13:29:59 +00:00
mHasDestination = true ;
2018-08-22 21:20:25 +00:00
mUsePathgrid = true ;
2015-07-12 04:35:15 +00:00
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
2016-06-15 18:43:09 +00:00
ESM : : Pathgrid : : Point temp = storage . mAllowedNodes [ randNode ] ;
storage . mAllowedNodes . erase ( storage . mAllowedNodes . begin ( ) + randNode ) ;
2015-07-12 04:35:15 +00:00
// check if mCurrentNode was taken out of mAllowedNodes
2016-06-15 18:43:09 +00:00
if ( storage . mTrimCurrentNode & & storage . mAllowedNodes . size ( ) > 1 )
storage . mTrimCurrentNode = false ;
2015-07-12 04:35:15 +00:00
else
2016-06-15 18:43:09 +00:00
storage . mAllowedNodes . push_back ( storage . mCurrentNode ) ;
storage . mCurrentNode = temp ;
2013-06-01 00:01:42 +00:00
2018-06-27 08:48:34 +00:00
storage . setState ( AiWanderStorage : : Wander_Walking ) ;
2013-06-01 00:49:52 +00:00
}
2015-07-12 04:35:15 +00:00
// Choose a different node and delete this one from possible nodes because it is uncreachable:
else
2016-06-15 18:43:09 +00:00
storage . mAllowedNodes . erase ( storage . mAllowedNodes . begin ( ) + randNode ) ;
2015-07-12 04:35:15 +00:00
}
2013-06-01 00:01:42 +00:00
2015-07-05 06:07:14 +00:00
void AiWander : : ToWorldCoordinates ( ESM : : Pathgrid : : Point & point , const ESM : : Cell * cell )
{
2015-09-12 02:17:46 +00:00
CoordinateConverter ( cell ) . toWorld ( point ) ;
2014-04-17 23:03:36 +00:00
}
2014-01-29 19:29:07 +00:00
2014-04-17 23:03:36 +00:00
void AiWander : : trimAllowedNodes ( std : : vector < ESM : : Pathgrid : : Point > & nodes ,
2014-05-01 07:41:25 +00:00
const PathFinder & pathfinder )
2014-04-17 23:03:36 +00:00
{
// TODO: how to add these back in once the door opens?
2014-04-18 22:16:56 +00:00
// Idea: keep a list of detected closed doors (see aicombat.cpp)
// Every now and then check whether one of the doors is opened. (maybe
// at the end of playing idle?) If the door is opened then re-calculate
// allowed nodes starting from the spawn point.
2018-07-21 09:30:14 +00:00
auto paths = pathfinder . getPath ( ) ;
2014-04-17 23:03:36 +00:00
while ( paths . size ( ) > = 2 )
{
2018-07-21 09:30:14 +00:00
const auto pt = paths . back ( ) ;
2014-04-18 05:45:39 +00:00
for ( unsigned int j = 0 ; j < nodes . size ( ) ; j + + )
2014-04-17 23:03:36 +00:00
{
2016-12-14 21:11:22 +00:00
// FIXME: doesn't handle a door with the same X/Y
// coordinates but with a different Z
2018-07-21 09:30:14 +00:00
if ( std : : abs ( nodes [ j ] . mX - pt . x ( ) ) < = 0.5 & & std : : abs ( nodes [ j ] . mY - pt . y ( ) ) < = 0.5 )
2014-04-17 23:03:36 +00:00
{
nodes . erase ( nodes . begin ( ) + j ) ;
break ;
}
2014-01-29 19:29:07 +00:00
}
2014-04-17 23:03:36 +00:00
paths . pop_back ( ) ;
2013-05-26 15:49:44 +00:00
}
}
2013-06-01 00:49:52 +00:00
int AiWander : : getTypeId ( ) const
2013-05-26 15:49:44 +00:00
{
2014-01-07 00:12:37 +00:00
return TypeIdWander ;
2013-05-26 15:49:44 +00:00
}
2017-04-30 13:29:59 +00:00
void AiWander : : stopWalking ( const MWWorld : : Ptr & actor , AiWanderStorage & storage , bool clearPath )
2013-06-01 00:49:52 +00:00
{
2017-04-30 13:29:59 +00:00
if ( clearPath )
{
mPathFinder . clearPath ( ) ;
mHasDestination = false ;
}
2014-05-22 18:37:22 +00:00
actor . getClass ( ) . getMovementSettings ( actor ) . mPosition [ 1 ] = 0 ;
2013-06-01 00:49:52 +00:00
}
2013-05-26 15:49:44 +00:00
2015-07-30 12:08:58 +00:00
bool AiWander : : playIdle ( const MWWorld : : Ptr & actor , unsigned short idleSelect )
2013-06-01 00:49:52 +00:00
{
2015-03-23 07:57:36 +00:00
if ( ( GroupIndex_MinIdle < = idleSelect ) & & ( idleSelect < = GroupIndex_MaxIdle ) )
{
const std : : string & groupName = sIdleSelectToGroupName [ idleSelect - GroupIndex_MinIdle ] ;
2015-07-30 12:08:58 +00:00
return MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > playAnimationGroup ( actor , groupName , 0 , 1 ) ;
}
else
{
2018-08-14 19:05:43 +00:00
Log ( Debug : : Verbose ) < < " Attempted to play out of range idle animation \" " < < idleSelect < < " \" for " < < actor . getCellRef ( ) . getRefId ( ) ;
2015-07-30 12:08:58 +00:00
return false ;
2015-03-23 07:57:36 +00:00
}
2013-06-01 00:49:52 +00:00
}
2013-05-26 15:49:44 +00:00
2013-06-01 00:49:52 +00:00
bool AiWander : : checkIdle ( const MWWorld : : Ptr & actor , unsigned short idleSelect )
{
2015-03-23 07:57:36 +00:00
if ( ( GroupIndex_MinIdle < = idleSelect ) & & ( idleSelect < = GroupIndex_MaxIdle ) )
{
const std : : string & groupName = sIdleSelectToGroupName [ idleSelect - GroupIndex_MinIdle ] ;
return MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > checkAnimationPlaying ( actor , groupName ) ;
}
2013-06-01 00:49:52 +00:00
else
2015-03-23 07:57:36 +00:00
{
2013-06-01 00:49:52 +00:00
return false ;
2015-03-23 07:57:36 +00:00
}
2013-06-01 00:49:52 +00:00
}
2014-04-29 07:09:51 +00:00
2015-07-19 06:04:42 +00:00
short unsigned AiWander : : getRandomIdle ( )
2014-04-26 09:47:47 +00:00
{
unsigned short idleRoll = 0 ;
2015-07-19 06:04:42 +00:00
short unsigned selectedAnimation = 0 ;
2014-04-26 09:47:47 +00:00
for ( unsigned int counter = 0 ; counter < mIdle . size ( ) ; counter + + )
{
2014-06-12 21:27:04 +00:00
static float fIdleChanceMultiplier = MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( )
2018-08-29 15:38:12 +00:00
. get < ESM : : GameSetting > ( ) . find ( " fIdleChanceMultiplier " ) - > mValue . getFloat ( ) ;
2014-06-12 21:27:04 +00:00
2015-03-08 04:42:07 +00:00
unsigned short idleChance = static_cast < unsigned short > ( fIdleChanceMultiplier * mIdle [ counter ] ) ;
2015-04-22 15:58:55 +00:00
unsigned short randSelect = ( int ) ( Misc : : Rng : : rollProbability ( ) * int ( 100 / fIdleChanceMultiplier ) ) ;
2014-04-26 09:47:47 +00:00
if ( randSelect < idleChance & & randSelect > idleRoll )
{
2015-07-19 06:04:42 +00:00
selectedAnimation = counter + GroupIndex_MinIdle ;
2014-04-26 09:47:47 +00:00
idleRoll = randSelect ;
}
}
2015-07-19 06:04:42 +00:00
return selectedAnimation ;
2014-04-26 09:47:47 +00:00
}
2014-06-12 21:27:04 +00:00
2014-12-31 17:41:57 +00:00
void AiWander : : fastForward ( const MWWorld : : Ptr & actor , AiState & state )
{
2016-06-09 15:56:01 +00:00
// Update duration counter
mRemainingDuration - - ;
2014-12-31 17:41:57 +00:00
if ( mDistance = = 0 )
return ;
2016-06-15 18:43:09 +00:00
AiWanderStorage & storage = state . get < AiWanderStorage > ( ) ;
if ( storage . mPopulateAvailableNodes )
getAllowedNodes ( actor , actor . getCell ( ) - > getCell ( ) , storage ) ;
2014-12-31 17:41:57 +00:00
2016-06-15 18:43:09 +00:00
if ( storage . mAllowedNodes . empty ( ) )
2014-12-31 17:41:57 +00:00
return ;
2016-06-15 18:43:09 +00:00
int index = Misc : : Rng : : rollDice ( storage . mAllowedNodes . size ( ) ) ;
ESM : : Pathgrid : : Point dest = storage . mAllowedNodes [ index ] ;
2017-11-11 08:31:18 +00:00
ESM : : Pathgrid : : Point worldDest = dest ;
ToWorldCoordinates ( worldDest , actor . getCell ( ) - > getCell ( ) ) ;
2018-08-18 10:42:11 +00:00
bool isPathGridOccupied = MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isAnyActorInRange ( PathFinder : : makeOsgVec3 ( worldDest ) , 60 ) ;
2017-11-11 08:31:18 +00:00
// add offset only if the selected pathgrid is occupied by another actor
if ( isPathGridOccupied )
{
ESM : : Pathgrid : : PointList points ;
getNeighbouringNodes ( dest , actor . getCell ( ) , points ) ;
// there are no neighbouring nodes, nowhere to move
if ( points . empty ( ) )
return ;
int initialSize = points . size ( ) ;
bool isOccupied = false ;
// AI will try to move the NPC towards every neighboring node until suitable place will be found
for ( int i = 0 ; i < initialSize ; i + + )
{
int randomIndex = Misc : : Rng : : rollDice ( points . size ( ) ) ;
ESM : : Pathgrid : : Point connDest = points [ randomIndex ] ;
// add an offset towards random neighboring node
2018-08-18 10:42:11 +00:00
osg : : Vec3f dir = PathFinder : : makeOsgVec3 ( connDest ) - PathFinder : : makeOsgVec3 ( dest ) ;
2017-11-11 08:31:18 +00:00
float length = dir . length ( ) ;
dir . normalize ( ) ;
for ( int j = 1 ; j < = 3 ; j + + )
{
// move for 5-15% towards random neighboring node
2018-08-18 10:42:11 +00:00
dest = PathFinder : : makePathgridPoint ( PathFinder : : makeOsgVec3 ( dest ) + dir * ( j * 5 * length / 100.f ) ) ;
2017-11-11 08:31:18 +00:00
worldDest = dest ;
ToWorldCoordinates ( worldDest , actor . getCell ( ) - > getCell ( ) ) ;
2018-08-18 10:42:11 +00:00
isOccupied = MWBase : : Environment : : get ( ) . getMechanicsManager ( ) - > isAnyActorInRange ( PathFinder : : makeOsgVec3 ( worldDest ) , 60 ) ;
2017-11-11 08:31:18 +00:00
if ( ! isOccupied )
break ;
}
if ( ! isOccupied )
break ;
// Will try an another neighboring node
points . erase ( points . begin ( ) + randomIndex ) ;
}
// there is no free space, nowhere to move
if ( isOccupied )
return ;
}
// place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be underground.
2018-05-26 11:35:48 +00:00
// Adding 20 in adjustPosition() is not enough.
dest . mZ + = 60 ;
2014-12-31 17:41:57 +00:00
2015-07-05 06:07:14 +00:00
ToWorldCoordinates ( dest , actor . getCell ( ) - > getCell ( ) ) ;
2015-01-01 17:11:37 +00:00
2017-11-11 08:31:18 +00:00
state . moveIn ( new AiWanderStorage ( ) ) ;
2017-11-28 08:05:05 +00:00
MWBase : : Environment : : get ( ) . getWorld ( ) - > moveObject ( actor , static_cast < float > ( dest . mX ) ,
2015-03-08 04:42:07 +00:00
static_cast < float > ( dest . mY ) , static_cast < float > ( dest . mZ ) ) ;
2014-12-31 17:41:57 +00:00
actor . getClass ( ) . adjustPosition ( actor , false ) ;
}
2017-11-11 08:31:18 +00:00
void AiWander : : getNeighbouringNodes ( ESM : : Pathgrid : : Point dest , const MWWorld : : CellStore * currentCell , ESM : : Pathgrid : : PointList & points )
2015-07-08 06:41:03 +00:00
{
2017-11-11 08:31:18 +00:00
const ESM : : Pathgrid * pathgrid =
MWBase : : Environment : : get ( ) . getWorld ( ) - > getStore ( ) . get < ESM : : Pathgrid > ( ) . search ( * currentCell - > getCell ( ) ) ;
2018-08-18 10:42:11 +00:00
int index = PathFinder : : getClosestPoint ( pathgrid , PathFinder : : makeOsgVec3 ( dest ) ) ;
2017-11-11 08:31:18 +00:00
2017-11-27 17:30:31 +00:00
getPathGridGraph ( currentCell ) . getNeighbouringPoints ( index , points ) ;
2014-12-31 17:41:57 +00:00
}
2016-04-16 02:56:41 +00:00
void AiWander : : getAllowedNodes ( const MWWorld : : Ptr & actor , const ESM : : Cell * cell , AiWanderStorage & storage )
2014-12-31 17:41:57 +00:00
{
// 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 ) ;
2016-04-11 01:21:18 +00:00
const MWWorld : : CellStore * cellStore = actor . getCell ( ) ;
2014-12-31 17:41:57 +00:00
2016-06-15 18:43:09 +00:00
storage . mAllowedNodes . clear ( ) ;
2014-12-31 17:41:57 +00:00
// 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
2015-03-28 07:05:54 +00:00
// Note: In order to wander, need at least two points.
if ( ! pathgrid | | ( pathgrid - > mPoints . size ( ) < 2 ) )
2016-04-16 02:56:41 +00:00
storage . mCanWanderAlongPathGrid = false ;
2014-12-31 17:41:57 +00:00
// 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).
2015-06-21 04:23:40 +00:00
// ... pathgrids don't usually include water, so swimmers ignore them
2016-04-16 02:56:41 +00:00
if ( mDistance & & storage . mCanWanderAlongPathGrid & & ! actor . getClass ( ) . isPureWaterCreature ( actor ) )
2014-12-31 17:41:57 +00:00
{
2016-12-14 21:11:22 +00:00
// get NPC's position in local (i.e. cell) coordinates
2015-07-05 06:08:09 +00:00
osg : : Vec3f npcPos ( mInitialActorPosition ) ;
2015-09-12 02:17:46 +00:00
CoordinateConverter ( cell ) . toLocal ( npcPos ) ;
2017-11-28 08:05:05 +00:00
2016-04-11 01:21:18 +00:00
// Find closest pathgrid point
2018-08-18 10:42:11 +00:00
int closestPointIndex = PathFinder : : getClosestPoint ( pathgrid , npcPos ) ;
2014-12-31 17:41:57 +00:00
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
2016-04-11 01:21:18 +00:00
// and if the point is connected to the closest current point
2016-12-14 21:11:22 +00:00
// NOTE: mPoints and mAllowedNodes are in local coordinates
2015-07-05 06:21:35 +00:00
int pointIndex = 0 ;
2014-12-31 17:41:57 +00:00
for ( unsigned int counter = 0 ; counter < pathgrid - > mPoints . size ( ) ; counter + + )
{
2018-08-18 10:42:11 +00:00
osg : : Vec3f nodePos ( PathFinder : : makeOsgVec3 ( pathgrid - > mPoints [ counter ] ) ) ;
2016-04-11 01:21:18 +00:00
if ( ( npcPos - nodePos ) . length2 ( ) < = mDistance * mDistance & &
2017-11-27 17:30:31 +00:00
getPathGridGraph ( cellStore ) . isPointConnected ( closestPointIndex , counter ) )
2015-07-05 06:21:35 +00:00
{
2016-06-15 18:43:09 +00:00
storage . mAllowedNodes . push_back ( pathgrid - > mPoints [ counter ] ) ;
2015-07-05 06:21:35 +00:00
pointIndex = counter ;
}
2014-12-31 17:41:57 +00:00
}
2016-06-15 18:43:09 +00:00
if ( storage . mAllowedNodes . size ( ) = = 1 )
2014-12-31 17:41:57 +00:00
{
2016-06-15 18:43:09 +00:00
AddNonPathGridAllowedPoints ( npcPos , pathgrid , pointIndex , storage ) ;
2015-03-28 07:05:54 +00:00
}
2016-06-15 18:43:09 +00:00
if ( ! storage . mAllowedNodes . empty ( ) )
2015-03-28 07:05:54 +00:00
{
2016-06-15 18:43:09 +00:00
SetCurrentNodeToClosestAllowedNode ( npcPos , storage ) ;
2015-03-28 07:05:54 +00:00
}
2014-12-31 17:41:57 +00:00
}
2016-06-15 18:43:09 +00:00
storage . mPopulateAvailableNodes = false ;
2014-12-31 17:41:57 +00:00
}
2015-03-28 07:05:54 +00:00
2017-11-28 08:05:05 +00:00
// When only one path grid point in wander distance,
2015-07-05 06:21:35 +00:00
// additional points for NPC to wander to are:
// 1. NPC's initial location
// 2. Partway along the path between the point and its connected points.
2016-06-15 18:43:09 +00:00
void AiWander : : AddNonPathGridAllowedPoints ( osg : : Vec3f npcPos , const ESM : : Pathgrid * pathGrid , int pointIndex , AiWanderStorage & storage )
2015-07-05 06:21:35 +00:00
{
2018-08-18 10:42:11 +00:00
storage . mAllowedNodes . push_back ( PathFinder : : makePathgridPoint ( npcPos ) ) ;
2015-07-05 06:21:35 +00:00
for ( std : : vector < ESM : : Pathgrid : : Edge > : : const_iterator it = pathGrid - > mEdges . begin ( ) ; it ! = pathGrid - > mEdges . end ( ) ; + + it )
{
if ( it - > mV0 = = pointIndex )
{
2016-06-15 18:43:09 +00:00
AddPointBetweenPathGridPoints ( pathGrid - > mPoints [ it - > mV0 ] , pathGrid - > mPoints [ it - > mV1 ] , storage ) ;
2014-12-31 17:41:57 +00:00
}
}
}
2016-06-15 18:43:09 +00:00
void AiWander : : AddPointBetweenPathGridPoints ( const ESM : : Pathgrid : : Point & start , const ESM : : Pathgrid : : Point & end , AiWanderStorage & storage )
2015-07-05 06:21:35 +00:00
{
2018-08-18 10:42:11 +00:00
osg : : Vec3f vectorStart = PathFinder : : makeOsgVec3 ( start ) ;
osg : : Vec3f delta = PathFinder : : makeOsgVec3 ( end ) - vectorStart ;
2015-07-05 06:21:35 +00:00
float length = delta . length ( ) ;
delta . normalize ( ) ;
2015-07-08 07:34:33 +00:00
int distance = std : : max ( mDistance / 2 , MINIMUM_WANDER_DISTANCE ) ;
2017-11-28 08:05:05 +00:00
2015-07-08 06:41:03 +00:00
// must not travel longer than distance between waypoints or NPC goes past waypoint
distance = std : : min ( distance , static_cast < int > ( length ) ) ;
2015-07-05 06:21:35 +00:00
delta * = distance ;
2018-08-18 10:42:11 +00:00
storage . mAllowedNodes . push_back ( PathFinder : : makePathgridPoint ( vectorStart + delta ) ) ;
2015-07-05 06:21:35 +00:00
}
2017-04-20 11:36:14 +00:00
void AiWander : : SetCurrentNodeToClosestAllowedNode ( const osg : : Vec3f & npcPos , AiWanderStorage & storage )
2015-03-28 07:05:54 +00:00
{
2015-08-21 07:34:28 +00:00
float distanceToClosestNode = std : : numeric_limits < float > : : max ( ) ;
2015-07-05 06:17:18 +00:00
unsigned int index = 0 ;
2016-06-15 18:43:09 +00:00
for ( unsigned int counterThree = 0 ; counterThree < storage . mAllowedNodes . size ( ) ; counterThree + + )
2015-07-05 06:17:18 +00:00
{
2018-08-18 10:42:11 +00:00
osg : : Vec3f nodePos ( PathFinder : : makeOsgVec3 ( storage . mAllowedNodes [ counterThree ] ) ) ;
2015-07-05 06:17:18 +00:00
float tempDist = ( npcPos - nodePos ) . length2 ( ) ;
if ( tempDist < distanceToClosestNode )
{
index = counterThree ;
distanceToClosestNode = tempDist ;
}
}
2016-06-15 18:43:09 +00:00
storage . mCurrentNode = storage . mAllowedNodes [ index ] ;
storage . mAllowedNodes . erase ( storage . mAllowedNodes . begin ( ) + index ) ;
2015-03-28 07:05:54 +00:00
}
2014-06-12 21:27:04 +00:00
void AiWander : : writeState ( ESM : : AiSequence : : AiSequence & sequence ) const
{
2016-06-09 15:56:01 +00:00
float remainingDuration ;
if ( mRemainingDuration > 0 & & mRemainingDuration < 24 )
remainingDuration = mRemainingDuration ;
else
remainingDuration = mDuration ;
2017-04-28 15:30:26 +00:00
std : : unique_ptr < ESM : : AiSequence : : AiWander > wander ( new ESM : : AiSequence : : AiWander ( ) ) ;
2014-06-12 21:27:04 +00:00
wander - > mData . mDistance = mDistance ;
wander - > mData . mDuration = mDuration ;
wander - > mData . mTimeOfDay = mTimeOfDay ;
2016-06-09 15:56:01 +00:00
wander - > mDurationData . mRemainingDuration = remainingDuration ;
2014-06-28 12:22:27 +00:00
assert ( mIdle . size ( ) = = 8 ) ;
2014-06-12 21:27:04 +00:00
for ( int i = 0 ; i < 8 ; + + i )
wander - > mData . mIdle [ i ] = mIdle [ i ] ;
wander - > mData . mShouldRepeat = mRepeat ;
2014-12-31 20:27:19 +00:00
wander - > mStoredInitialActorPosition = mStoredInitialActorPosition ;
if ( mStoredInitialActorPosition )
wander - > mInitialActorPosition = mInitialActorPosition ;
2014-06-12 21:27:04 +00:00
ESM : : AiSequence : : AiPackageContainer package ;
package . mType = ESM : : AiSequence : : Ai_Wander ;
package . mPackage = wander . release ( ) ;
sequence . mPackages . push_back ( package ) ;
}
AiWander : : AiWander ( const ESM : : AiSequence : : AiWander * wander )
2014-09-26 15:12:48 +00:00
: mDistance ( wander - > mData . mDistance )
, mDuration ( wander - > mData . mDuration )
2016-06-09 15:56:01 +00:00
, mRemainingDuration ( wander - > mDurationData . mRemainingDuration )
2014-09-26 15:12:48 +00:00
, mTimeOfDay ( wander - > mData . mTimeOfDay )
2015-03-06 22:04:54 +00:00
, mRepeat ( wander - > mData . mShouldRepeat ! = 0 )
2014-12-31 20:27:19 +00:00
, mStoredInitialActorPosition ( wander - > mStoredInitialActorPosition )
2017-04-30 13:29:59 +00:00
, mHasDestination ( false )
, mDestination ( osg : : Vec3f ( 0 , 0 , 0 ) )
2018-11-13 19:07:01 +00:00
, mUsePathgrid ( false )
2014-06-12 21:27:04 +00:00
{
2015-01-01 17:58:17 +00:00
if ( mStoredInitialActorPosition )
mInitialActorPosition = wander - > mInitialActorPosition ;
2014-06-12 21:27:04 +00:00
for ( int i = 0 ; i < 8 ; + + i )
mIdle . push_back ( wander - > mData . mIdle [ i ] ) ;
2016-06-09 15:56:01 +00:00
if ( mRemainingDuration < = 0 | | mRemainingDuration > = 24 )
mRemainingDuration = mDuration ;
2014-08-14 16:55:54 +00:00
init ( ) ;
2014-06-12 21:27:04 +00:00
}
2013-05-26 15:49:44 +00:00
}