mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-03 20:45:33 +00:00
Merge remote-tracking branch 'dteviot/refactoringAiWander'
This commit is contained in:
commit
26ea3aa1ad
4 changed files with 229 additions and 147 deletions
|
@ -40,6 +40,7 @@ namespace
|
||||||
return -std::asin(dir.z() / len);
|
return -std::asin(dir.z() / len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const float REACTION_INTERVAL = 0.25f;
|
||||||
|
|
||||||
const float PATHFIND_Z_REACH = 50.0f;
|
const float PATHFIND_Z_REACH = 50.0f;
|
||||||
// distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid
|
// distance at which actor pays more attention to decide whether to shortcut or stick to pathgrid
|
||||||
|
@ -176,10 +177,8 @@ namespace MWMechanics
|
||||||
// get or create temporary storage
|
// get or create temporary storage
|
||||||
AiCombatStorage& storage = state.get<AiCombatStorage>();
|
AiCombatStorage& storage = state.get<AiCombatStorage>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//General description
|
//General description
|
||||||
if(actor.getClass().getCreatureStats(actor).isDead())
|
if (actor.getClass().getCreatureStats(actor).isDead())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId);
|
||||||
|
@ -191,9 +190,6 @@ namespace MWMechanics
|
||||||
|| target.getClass().getCreatureStats(target).isDead())
|
|| target.getClass().getCreatureStats(target).isDead())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
const MWWorld::Class& actorClass = actor.getClass();
|
|
||||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
|
||||||
|
|
||||||
|
|
||||||
//Update every frame
|
//Update every frame
|
||||||
bool& combatMove = storage.mCombatMove;
|
bool& combatMove = storage.mCombatMove;
|
||||||
|
@ -210,23 +206,9 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actorClass.getMovementSettings(actor) = movement;
|
UpdateActorsMovement(actor, movement);
|
||||||
actorClass.getMovementSettings(actor).mRotation[0] = 0;
|
|
||||||
actorClass.getMovementSettings(actor).mRotation[2] = 0;
|
|
||||||
|
|
||||||
if(movement.mRotation[2] != 0)
|
|
||||||
{
|
|
||||||
if(zTurn(actor, movement.mRotation[2])) movement.mRotation[2] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(movement.mRotation[0] != 0)
|
|
||||||
{
|
|
||||||
if(smoothTurn(actor, movement.mRotation[0], 0)) movement.mRotation[0] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool& attack = storage.mAttack;
|
bool& attack = storage.mAttack;
|
||||||
bool& readyToAttack = storage.mReadyToAttack;
|
|
||||||
|
|
||||||
if (attack && (characterController.getAttackStrength() >= storage.mStrength || characterController.readyToPrepareAttack()))
|
if (attack && (characterController.getAttackStrength() >= storage.mStrength || characterController.readyToPrepareAttack()))
|
||||||
attack = false;
|
attack = false;
|
||||||
|
|
||||||
|
@ -236,14 +218,22 @@ namespace MWMechanics
|
||||||
actionCooldown -= duration;
|
actionCooldown -= duration;
|
||||||
|
|
||||||
float& timerReact = storage.mTimerReact;
|
float& timerReact = storage.mTimerReact;
|
||||||
float tReaction = 0.25f;
|
if(timerReact < REACTION_INTERVAL)
|
||||||
if(timerReact < tReaction)
|
|
||||||
{
|
{
|
||||||
timerReact += duration;
|
timerReact += duration;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timerReact = 0;
|
||||||
|
return reactionTimeActions(actor, characterController, storage, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Update with period = tReaction
|
bool AiCombat::reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController,
|
||||||
|
AiCombatStorage& storage, MWWorld::Ptr target)
|
||||||
|
{
|
||||||
|
MWMechanics::Movement& movement = storage.mMovement;
|
||||||
|
|
||||||
// Stop attacking if target is not seen
|
// Stop attacking if target is not seen
|
||||||
if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0
|
if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0
|
||||||
|
@ -253,7 +243,6 @@ namespace MWMechanics
|
||||||
return false; // TODO: run away instead of doing nothing
|
return false; // TODO: run away instead of doing nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
timerReact = 0;
|
|
||||||
const MWWorld::CellStore*& currentCell = storage.mCell;
|
const MWWorld::CellStore*& currentCell = storage.mCell;
|
||||||
bool cellChange = currentCell && (actor.getCell() != currentCell);
|
bool cellChange = currentCell && (actor.getCell() != currentCell);
|
||||||
if(!currentCell || cellChange)
|
if(!currentCell || cellChange)
|
||||||
|
@ -261,8 +250,10 @@ namespace MWMechanics
|
||||||
currentCell = actor.getCell();
|
currentCell = actor.getCell();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MWWorld::Class& actorClass = actor.getClass();
|
||||||
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||||
|
|
||||||
|
float& actionCooldown = storage.mActionCooldown;
|
||||||
if (actionCooldown > 0)
|
if (actionCooldown > 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -284,6 +275,7 @@ namespace MWMechanics
|
||||||
float weapRange = 1.0f;
|
float weapRange = 1.0f;
|
||||||
|
|
||||||
// Get weapon characteristics
|
// Get weapon characteristics
|
||||||
|
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
if (actorClass.hasInventoryStore(actor))
|
if (actorClass.hasInventoryStore(actor))
|
||||||
{
|
{
|
||||||
//Get weapon range
|
//Get weapon range
|
||||||
|
@ -334,13 +326,14 @@ namespace MWMechanics
|
||||||
|
|
||||||
|
|
||||||
float& strength = storage.mStrength;
|
float& strength = storage.mStrength;
|
||||||
|
bool& readyToAttack = storage.mReadyToAttack;
|
||||||
// start new attack
|
// start new attack
|
||||||
if(readyToAttack && characterController.readyToStartAttack())
|
if(readyToAttack && characterController.readyToStartAttack())
|
||||||
{
|
{
|
||||||
if (storage.mAttackCooldown <= 0)
|
if (storage.mAttackCooldown <= 0)
|
||||||
{
|
{
|
||||||
attack = true; // attack starts just now
|
storage.mAttack = true; // attack starts just now
|
||||||
characterController.setAttackingOrSpell(attack);
|
characterController.setAttackingOrSpell(true);
|
||||||
|
|
||||||
if (!distantCombat)
|
if (!distantCombat)
|
||||||
chooseBestAttack(weapon, movement);
|
chooseBestAttack(weapon, movement);
|
||||||
|
@ -364,7 +357,7 @@ namespace MWMechanics
|
||||||
storage.mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9);
|
storage.mAttackCooldown = std::min(baseDelay + 0.01 * Misc::Rng::roll0to99(), baseDelay + 0.9);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
storage.mAttackCooldown -= tReaction;
|
storage.mAttackCooldown -= REACTION_INTERVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -408,7 +401,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool isStuck = false;
|
bool isStuck = false;
|
||||||
float speed = 0.0f;
|
float speed = 0.0f;
|
||||||
if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2)
|
if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * REACTION_INTERVAL / 2)
|
||||||
isStuck = true;
|
isStuck = true;
|
||||||
|
|
||||||
lastActorPos = vActorPos;
|
lastActorPos = vActorPos;
|
||||||
|
@ -445,7 +438,7 @@ namespace MWMechanics
|
||||||
if (distantCombat)
|
if (distantCombat)
|
||||||
{
|
{
|
||||||
osg::Vec3f& lastTargetPos = storage.mLastTargetPos;
|
osg::Vec3f& lastTargetPos = storage.mLastTargetPos;
|
||||||
osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, tReaction, weaptype, strength);
|
osg::Vec3f vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, REACTION_INTERVAL, weaptype, strength);
|
||||||
lastTargetPos = vTargetPos;
|
lastTargetPos = vTargetPos;
|
||||||
movement.mRotation[0] = getXAngleToDir(vAimDir);
|
movement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||||
movement.mRotation[2] = getZAngleToDir(vAimDir);
|
movement.mRotation[2] = getZAngleToDir(vAimDir);
|
||||||
|
@ -466,8 +459,8 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
if(movement.mPosition[0] || movement.mPosition[1])
|
if(movement.mPosition[0] || movement.mPosition[1])
|
||||||
{
|
{
|
||||||
timerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
storage.mTimerCombatMove = 0.1f + 0.1f * Misc::Rng::rollClosedProbability();
|
||||||
combatMove = true;
|
storage.mCombatMove = true;
|
||||||
}
|
}
|
||||||
// only NPCs are smart enough to use dodge movements
|
// only NPCs are smart enough to use dodge movements
|
||||||
else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2)))
|
else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2)))
|
||||||
|
@ -476,8 +469,8 @@ namespace MWMechanics
|
||||||
if (Misc::Rng::rollClosedProbability() < 0.25)
|
if (Misc::Rng::rollClosedProbability() < 0.25)
|
||||||
{
|
{
|
||||||
movement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f;
|
movement.mPosition[0] = Misc::Rng::rollProbability() < 0.5 ? 1.0f : -1.0f;
|
||||||
timerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability();
|
storage.mTimerCombatMove = 0.05f + 0.15f * Misc::Rng::rollClosedProbability();
|
||||||
combatMove = true;
|
storage.mCombatMove = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,7 +498,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
if(speed == 0.0f) speed = actorClass.getSpeed(actor);
|
if(speed == 0.0f) speed = actorClass.getSpeed(actor);
|
||||||
// maximum dist before pit/obstacle for actor to avoid them depending on his speed
|
// maximum dist before pit/obstacle for actor to avoid them depending on his speed
|
||||||
float maxAvoidDist = tReaction * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
|
float maxAvoidDist = REACTION_INTERVAL * speed + speed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
|
||||||
preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vDirToTarget.x(), vDirToTarget.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2);
|
preferShortcut = checkWayIsClear(vActorPos, vTargetPos, osg::Vec3f(vDirToTarget.x(), vDirToTarget.y(), 0).length() > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -566,8 +559,8 @@ namespace MWMechanics
|
||||||
if (readyToAttack)
|
if (readyToAttack)
|
||||||
{
|
{
|
||||||
// to stop possible sideway moving after target moved out of attack range
|
// to stop possible sideway moving after target moved out of attack range
|
||||||
combatMove = true;
|
storage.mCombatMove = true;
|
||||||
timerCombatMove = 0;
|
storage.mTimerCombatMove = 0;
|
||||||
}
|
}
|
||||||
readyToAttack = false;
|
readyToAttack = false;
|
||||||
}
|
}
|
||||||
|
@ -575,6 +568,29 @@ namespace MWMechanics
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AiCombat::UpdateActorsMovement(const MWWorld::Ptr& actor, MWMechanics::Movement& desiredMovement)
|
||||||
|
{
|
||||||
|
MWMechanics::Movement& actorMovementSettings = actor.getClass().getMovementSettings(actor);
|
||||||
|
actorMovementSettings = desiredMovement;
|
||||||
|
RotateActorOnAxis(actor, 2, actorMovementSettings, desiredMovement);
|
||||||
|
RotateActorOnAxis(actor, 0, actorMovementSettings, desiredMovement);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AiCombat::RotateActorOnAxis(const MWWorld::Ptr& actor, int axis,
|
||||||
|
MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement)
|
||||||
|
{
|
||||||
|
actorMovementSettings.mRotation[axis] = 0;
|
||||||
|
float& targetAngleRadians = desiredMovement.mRotation[axis];
|
||||||
|
if (targetAngleRadians != 0)
|
||||||
|
{
|
||||||
|
if (smoothTurn(actor, targetAngleRadians, axis))
|
||||||
|
{
|
||||||
|
// actor now facing desired direction, no need to turn any more
|
||||||
|
targetAngleRadians = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AiCombat::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell)
|
bool AiCombat::doesPathNeedRecalc(ESM::Pathgrid::Point dest, const ESM::Cell *cell)
|
||||||
{
|
{
|
||||||
if (!mPathFinder.getPath().empty())
|
if (!mPathFinder.getPath().empty())
|
||||||
|
|
|
@ -26,6 +26,8 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
class Action;
|
class Action;
|
||||||
|
|
||||||
|
struct AiCombatStorage;
|
||||||
|
|
||||||
/// \brief Causes the actor to fight another actor
|
/// \brief Causes the actor to fight another actor
|
||||||
class AiCombat : public AiPackage
|
class AiCombat : public AiPackage
|
||||||
{
|
{
|
||||||
|
@ -58,8 +60,14 @@ namespace MWMechanics
|
||||||
|
|
||||||
int mTargetActorId;
|
int mTargetActorId;
|
||||||
|
|
||||||
|
|
||||||
void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
|
void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
|
||||||
|
bool reactionTimeActions(const MWWorld::Ptr& actor, CharacterController& characterController,
|
||||||
|
AiCombatStorage& storage, MWWorld::Ptr target);
|
||||||
|
|
||||||
|
/// Transfer desired movement (from AiCombatStorage) to Actor
|
||||||
|
void UpdateActorsMovement(const MWWorld::Ptr& actor, MWMechanics::Movement& movement);
|
||||||
|
void RotateActorOnAxis(const MWWorld::Ptr& actor, int axis,
|
||||||
|
MWMechanics::Movement& actorMovementSettings, MWMechanics::Movement& desiredMovement);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -193,109 +193,27 @@ namespace MWMechanics
|
||||||
|
|
||||||
ESM::Position pos = actor.getRefData().getPosition();
|
ESM::Position pos = actor.getRefData().getPosition();
|
||||||
|
|
||||||
|
doPerFrameActionsForState(actor, duration, storage, pos);
|
||||||
WanderState& wanderState = storage.mState;
|
|
||||||
// Check if an idle actor is too close to a door - if so start walking
|
|
||||||
mDoorCheckDuration += duration;
|
|
||||||
if(mDoorCheckDuration >= DOOR_CHECK_INTERVAL)
|
|
||||||
{
|
|
||||||
mDoorCheckDuration = 0; // restart timer
|
|
||||||
if(mDistance && // actor is not intended to be stationary
|
|
||||||
(wanderState == Wander_IdleNow) && // but is in idle
|
|
||||||
proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only
|
|
||||||
{
|
|
||||||
wanderState = Wander_MoveNow;
|
|
||||||
mTrimCurrentNode = false; // just in case
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Are we there yet?
|
|
||||||
if ((wanderState == Wander_Walking) &&
|
|
||||||
storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE))
|
|
||||||
{
|
|
||||||
stopWalking(actor, storage);
|
|
||||||
wanderState = Wander_ChooseAction;
|
|
||||||
mHasReturnPosition = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (wanderState == Wander_Walking) // have not yet reached the destination
|
|
||||||
{
|
|
||||||
// turn towards the next point in mPath
|
|
||||||
zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
|
|
||||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
|
|
||||||
|
|
||||||
evadeObstacles(actor, storage, duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool& rotate = storage.mTurnActorGivingGreetingToFacePlayer;
|
|
||||||
if (rotate)
|
|
||||||
{
|
|
||||||
// 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
|
|
||||||
if (zTurn(actor, storage.mTargetAngleRadians, osg::DegreesToRadians(5.f)))
|
|
||||||
rotate = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if idle animation finished
|
|
||||||
short unsigned& idleAnimation = storage.mIdleAnimation;
|
|
||||||
GreetingState& greetingState = storage.mSaidGreeting;
|
|
||||||
if ((wanderState == Wander_IdleNow) &&
|
|
||||||
!checkIdle(actor, idleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
|
|
||||||
{
|
|
||||||
wanderState = Wander_ChooseAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
||||||
|
|
||||||
if (wanderState == Wander_ChooseAction)
|
|
||||||
{
|
|
||||||
idleAnimation = getRandomIdle();
|
|
||||||
|
|
||||||
if(!idleAnimation && mDistance)
|
|
||||||
{
|
|
||||||
wanderState = Wander_MoveNow;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander:
|
|
||||||
MWWorld::TimeStamp currentTime = world->getTimeStamp();
|
|
||||||
mStartTime = currentTime;
|
|
||||||
playIdle(actor, idleAnimation);
|
|
||||||
wanderState = Wander_IdleNow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
playIdleDialogueRandomly(actor);
|
playIdleDialogueRandomly(actor);
|
||||||
|
|
||||||
float& lastReaction = storage.mReaction;
|
float& lastReaction = storage.mReaction;
|
||||||
lastReaction += duration;
|
lastReaction += duration;
|
||||||
if(lastReaction < REACTION_INTERVAL)
|
if (REACTION_INTERVAL <= lastReaction)
|
||||||
{
|
{
|
||||||
return false;
|
lastReaction = 0;
|
||||||
|
return reactionTimeActions(actor, storage, currentCell, cellChange, pos);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
lastReaction = 0;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: everything below get updated every REACTION_INTERVAL seconds
|
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
|
||||||
|
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos)
|
||||||
if(mDuration)
|
{
|
||||||
|
if (isPackageCompleted(actor, storage))
|
||||||
{
|
{
|
||||||
// End package if duration is complete or mid-night hits:
|
return true;
|
||||||
MWWorld::TimeStamp currentTime = world->getTimeStamp();
|
|
||||||
if((currentTime.getHour() >= mStartTime.getHour() + mDuration) ||
|
|
||||||
(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()))
|
|
||||||
{
|
|
||||||
if(!mRepeat)
|
|
||||||
{
|
|
||||||
stopWalking(actor, storage);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mStartTime = currentTime;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialization to discover & store allowed node points for this actor.
|
// Initialization to discover & store allowed node points for this actor.
|
||||||
|
@ -315,26 +233,14 @@ namespace MWMechanics
|
||||||
// For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere
|
// For stationary NPCs, move back to the starting location if another AiPackage moved us elsewhere
|
||||||
if (cellChange)
|
if (cellChange)
|
||||||
mHasReturnPosition = false;
|
mHasReturnPosition = false;
|
||||||
if (mDistance == 0 && mHasReturnPosition && (pos.asVec3() - mReturnPosition).length2() > 20*20)
|
if (mDistance == 0 && mHasReturnPosition
|
||||||
|
&& (pos.asVec3() - mReturnPosition).length2() > (DESTINATION_TOLERANCE * DESTINATION_TOLERANCE))
|
||||||
{
|
{
|
||||||
if (!storage.mPathFinder.isPathConstructed())
|
returnToStartLocation(actor, storage, pos);
|
||||||
{
|
|
||||||
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition));
|
|
||||||
|
|
||||||
// actor position is already in world co-ordinates
|
|
||||||
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
|
|
||||||
|
|
||||||
// don't take shortcuts for wandering
|
|
||||||
storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false);
|
|
||||||
|
|
||||||
if(storage.mPathFinder.isPathConstructed())
|
|
||||||
{
|
|
||||||
wanderState = Wander_Walking;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow interrupting a walking actor to trigger a greeting
|
// Allow interrupting a walking actor to trigger a greeting
|
||||||
|
WanderState& wanderState = storage.mState;
|
||||||
if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking))
|
if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking))
|
||||||
{
|
{
|
||||||
playGreetingIfPlayerGetsTooClose(actor, storage);
|
playGreetingIfPlayerGetsTooClose(actor, storage);
|
||||||
|
@ -355,6 +261,150 @@ namespace MWMechanics
|
||||||
return false; // AiWander package not yet completed
|
return false; // AiWander package not yet completed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AiWander::isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||||
|
{
|
||||||
|
if (mDuration)
|
||||||
|
{
|
||||||
|
// End package if duration is complete or mid-night hits:
|
||||||
|
MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||||
|
if ((currentTime.getHour() >= mStartTime.getHour() + mDuration) ||
|
||||||
|
(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()))
|
||||||
|
{
|
||||||
|
if (!mRepeat)
|
||||||
|
{
|
||||||
|
stopWalking(actor, storage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mStartTime = currentTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if get here, not yet completed
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AiWander::returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)
|
||||||
|
{
|
||||||
|
if (!storage.mPathFinder.isPathConstructed())
|
||||||
|
{
|
||||||
|
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mReturnPosition));
|
||||||
|
|
||||||
|
// actor position is already in world co-ordinates
|
||||||
|
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
|
||||||
|
|
||||||
|
// don't take shortcuts for wandering
|
||||||
|
storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false);
|
||||||
|
|
||||||
|
if (storage.mPathFinder.isPathConstructed())
|
||||||
|
{
|
||||||
|
storage.mState = Wander_Walking;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos)
|
||||||
|
{
|
||||||
|
switch (storage.mState)
|
||||||
|
{
|
||||||
|
case Wander_IdleNow:
|
||||||
|
onIdleStatePerFrameActions(actor, duration, storage);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Wander_Walking:
|
||||||
|
onWalkingStatePerFrameActions(actor, duration, storage, pos);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Wander_ChooseAction:
|
||||||
|
onChooseActionStatePerFrameActions(actor, storage);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Wander_MoveNow:
|
||||||
|
break; // nothing to do
|
||||||
|
|
||||||
|
default:
|
||||||
|
// should never get here
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
mDoorCheckDuration += duration;
|
||||||
|
if (mDoorCheckDuration >= DOOR_CHECK_INTERVAL)
|
||||||
|
{
|
||||||
|
mDoorCheckDuration = 0; // restart timer
|
||||||
|
if (mDistance && // actor is not intended to be stationary
|
||||||
|
proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only
|
||||||
|
{
|
||||||
|
storage.mState = Wander_MoveNow;
|
||||||
|
mTrimCurrentNode = false; // just in case
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool& rotate = storage.mTurnActorGivingGreetingToFacePlayer;
|
||||||
|
if (rotate)
|
||||||
|
{
|
||||||
|
// 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
|
||||||
|
if (zTurn(actor, storage.mTargetAngleRadians, osg::DegreesToRadians(5.f)))
|
||||||
|
rotate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if idle animation finished
|
||||||
|
GreetingState& greetingState = storage.mSaidGreeting;
|
||||||
|
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
|
||||||
|
{
|
||||||
|
storage.mState = Wander_ChooseAction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor,
|
||||||
|
float duration, AiWanderStorage& storage, ESM::Position& pos)
|
||||||
|
{
|
||||||
|
// Are we there yet?
|
||||||
|
if (storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE))
|
||||||
|
{
|
||||||
|
stopWalking(actor, storage);
|
||||||
|
storage.mState = Wander_ChooseAction;
|
||||||
|
mHasReturnPosition = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// have not yet reached the destination
|
||||||
|
//... turn towards the next point in mPath
|
||||||
|
zTurn(actor, storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
|
||||||
|
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
|
||||||
|
|
||||||
|
evadeObstacles(actor, storage, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AiWander::onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||||
|
{
|
||||||
|
|
||||||
|
short unsigned& idleAnimation = storage.mIdleAnimation;
|
||||||
|
idleAnimation = getRandomIdle();
|
||||||
|
|
||||||
|
if (!idleAnimation && mDistance)
|
||||||
|
{
|
||||||
|
storage.mState = Wander_MoveNow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander:
|
||||||
|
MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||||
|
mStartTime = currentTime;
|
||||||
|
playIdle(actor, idleAnimation);
|
||||||
|
storage.mState = Wander_IdleNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration)
|
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration)
|
||||||
{
|
{
|
||||||
if (mObstacleCheck.check(actor, duration))
|
if (mObstacleCheck.check(actor, duration))
|
||||||
|
|
|
@ -83,6 +83,14 @@ namespace MWMechanics
|
||||||
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);
|
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
|
||||||
|
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos);
|
||||||
|
void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||||
|
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos);
|
||||||
|
void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
|
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
|
||||||
|
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos);
|
||||||
|
bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||||
|
void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos);
|
||||||
|
|
||||||
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;
|
||||||
|
|
Loading…
Reference in a new issue