forked from mirror/openmw-tes3mp
Merged pull request #1781
This commit is contained in:
commit
d9de8ccb5b
12 changed files with 210 additions and 216 deletions
|
@ -50,6 +50,7 @@
|
||||||
Bug #4458: AiWander console command handles idle chances incorrectly
|
Bug #4458: AiWander console command handles idle chances incorrectly
|
||||||
Bug #4459: NotCell dialogue condition doesn't support partial matches
|
Bug #4459: NotCell dialogue condition doesn't support partial matches
|
||||||
Bug #4461: "Open" spell from non-player caster isn't a crime
|
Bug #4461: "Open" spell from non-player caster isn't a crime
|
||||||
|
Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages
|
||||||
Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal
|
Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal
|
||||||
Bug #4474: No fallback when getVampireHead fails
|
Bug #4474: No fallback when getVampireHead fails
|
||||||
Bug #4475: Scripted animations should not cause movement
|
Bug #4475: Scripted animations should not cause movement
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
|
||||||
Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation)
|
Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation)
|
||||||
{
|
{
|
||||||
mCharacterController.reset(new CharacterController(ptr, animation));
|
mCharacterController.reset(new CharacterController(ptr, animation));
|
||||||
|
@ -19,10 +18,4 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
return mCharacterController.get();
|
return mCharacterController.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
AiState& Actor::getAiState()
|
|
||||||
{
|
|
||||||
return mAiState;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "aistate.hpp"
|
|
||||||
|
|
||||||
namespace MWRender
|
namespace MWRender
|
||||||
{
|
{
|
||||||
class Animation;
|
class Animation;
|
||||||
|
@ -29,12 +27,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
CharacterController* getCharacterController();
|
CharacterController* getCharacterController();
|
||||||
|
|
||||||
AiState& getAiState();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<CharacterController> mCharacterController;
|
std::unique_ptr<CharacterController> mCharacterController;
|
||||||
|
|
||||||
AiState mAiState;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1365,7 +1365,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
|
CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||||
if (isConscious(iter->first))
|
if (isConscious(iter->first))
|
||||||
stats.getAiSequence().execute(iter->first, *iter->second->getCharacterController(), iter->second->getAiState(), duration);
|
stats.getAiSequence().execute(iter->first, *iter->second->getCharacterController(), duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1993,7 +1993,7 @@ namespace MWMechanics
|
||||||
|| ptr.getClass().getCreatureStats(ptr).isParalyzed())
|
|| ptr.getClass().getCreatureStats(ptr).isParalyzed())
|
||||||
continue;
|
continue;
|
||||||
MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence();
|
||||||
seq.fastForward(ptr, it->second->getAiState());
|
seq.fastForward(ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,74 +35,6 @@ namespace
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
|
||||||
/// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive.
|
|
||||||
struct AiCombatStorage : AiTemporaryBase
|
|
||||||
{
|
|
||||||
float mAttackCooldown;
|
|
||||||
float mTimerReact;
|
|
||||||
float mTimerCombatMove;
|
|
||||||
bool mReadyToAttack;
|
|
||||||
bool mAttack;
|
|
||||||
float mAttackRange;
|
|
||||||
bool mCombatMove;
|
|
||||||
osg::Vec3f mLastTargetPos;
|
|
||||||
const MWWorld::CellStore* mCell;
|
|
||||||
std::shared_ptr<Action> mCurrentAction;
|
|
||||||
float mActionCooldown;
|
|
||||||
float mStrength;
|
|
||||||
bool mForceNoShortcut;
|
|
||||||
ESM::Position mShortcutFailPos;
|
|
||||||
MWMechanics::Movement mMovement;
|
|
||||||
|
|
||||||
enum FleeState
|
|
||||||
{
|
|
||||||
FleeState_None,
|
|
||||||
FleeState_Idle,
|
|
||||||
FleeState_RunBlindly,
|
|
||||||
FleeState_RunToDestination
|
|
||||||
};
|
|
||||||
FleeState mFleeState;
|
|
||||||
bool mLOS;
|
|
||||||
float mUpdateLOSTimer;
|
|
||||||
float mFleeBlindRunTimer;
|
|
||||||
ESM::Pathgrid::Point mFleeDest;
|
|
||||||
|
|
||||||
AiCombatStorage():
|
|
||||||
mAttackCooldown(0.0f),
|
|
||||||
mTimerReact(AI_REACTION_TIME),
|
|
||||||
mTimerCombatMove(0.0f),
|
|
||||||
mReadyToAttack(false),
|
|
||||||
mAttack(false),
|
|
||||||
mAttackRange(0.0f),
|
|
||||||
mCombatMove(false),
|
|
||||||
mLastTargetPos(0,0,0),
|
|
||||||
mCell(NULL),
|
|
||||||
mCurrentAction(),
|
|
||||||
mActionCooldown(0.0f),
|
|
||||||
mStrength(),
|
|
||||||
mForceNoShortcut(false),
|
|
||||||
mShortcutFailPos(),
|
|
||||||
mMovement(),
|
|
||||||
mFleeState(FleeState_None),
|
|
||||||
mLOS(false),
|
|
||||||
mUpdateLOSTimer(0.0f),
|
|
||||||
mFleeBlindRunTimer(0.0f)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
|
|
||||||
void updateCombatMove(float duration);
|
|
||||||
void stopCombatMove();
|
|
||||||
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
|
|
||||||
const ESM::Weapon* weapon, bool distantCombat);
|
|
||||||
void updateAttack(CharacterController& characterController);
|
|
||||||
void stopAttack();
|
|
||||||
|
|
||||||
void startFleeing();
|
|
||||||
void stopFleeing();
|
|
||||||
bool isFleeing();
|
|
||||||
};
|
|
||||||
|
|
||||||
AiCombat::AiCombat(const MWWorld::Ptr& actor)
|
AiCombat::AiCombat(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
mTargetActorId = actor.getClass().getCreatureStats(actor).getActorId();
|
||||||
|
|
|
@ -23,7 +23,72 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
class Action;
|
class Action;
|
||||||
|
|
||||||
struct AiCombatStorage;
|
/// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive.
|
||||||
|
struct AiCombatStorage : AiTemporaryBase
|
||||||
|
{
|
||||||
|
float mAttackCooldown;
|
||||||
|
float mTimerReact;
|
||||||
|
float mTimerCombatMove;
|
||||||
|
bool mReadyToAttack;
|
||||||
|
bool mAttack;
|
||||||
|
float mAttackRange;
|
||||||
|
bool mCombatMove;
|
||||||
|
osg::Vec3f mLastTargetPos;
|
||||||
|
const MWWorld::CellStore* mCell;
|
||||||
|
std::shared_ptr<Action> mCurrentAction;
|
||||||
|
float mActionCooldown;
|
||||||
|
float mStrength;
|
||||||
|
bool mForceNoShortcut;
|
||||||
|
ESM::Position mShortcutFailPos;
|
||||||
|
MWMechanics::Movement mMovement;
|
||||||
|
|
||||||
|
enum FleeState
|
||||||
|
{
|
||||||
|
FleeState_None,
|
||||||
|
FleeState_Idle,
|
||||||
|
FleeState_RunBlindly,
|
||||||
|
FleeState_RunToDestination
|
||||||
|
};
|
||||||
|
FleeState mFleeState;
|
||||||
|
bool mLOS;
|
||||||
|
float mUpdateLOSTimer;
|
||||||
|
float mFleeBlindRunTimer;
|
||||||
|
ESM::Pathgrid::Point mFleeDest;
|
||||||
|
|
||||||
|
AiCombatStorage():
|
||||||
|
mAttackCooldown(0.0f),
|
||||||
|
mTimerReact(AI_REACTION_TIME),
|
||||||
|
mTimerCombatMove(0.0f),
|
||||||
|
mReadyToAttack(false),
|
||||||
|
mAttack(false),
|
||||||
|
mAttackRange(0.0f),
|
||||||
|
mCombatMove(false),
|
||||||
|
mLastTargetPos(0,0,0),
|
||||||
|
mCell(NULL),
|
||||||
|
mCurrentAction(),
|
||||||
|
mActionCooldown(0.0f),
|
||||||
|
mStrength(),
|
||||||
|
mForceNoShortcut(false),
|
||||||
|
mShortcutFailPos(),
|
||||||
|
mMovement(),
|
||||||
|
mFleeState(FleeState_None),
|
||||||
|
mLOS(false),
|
||||||
|
mUpdateLOSTimer(0.0f),
|
||||||
|
mFleeBlindRunTimer(0.0f)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
|
||||||
|
void updateCombatMove(float duration);
|
||||||
|
void stopCombatMove();
|
||||||
|
void startAttackIfReady(const MWWorld::Ptr& actor, CharacterController& characterController,
|
||||||
|
const ESM::Weapon* weapon, bool distantCombat);
|
||||||
|
void updateAttack(CharacterController& characterController);
|
||||||
|
void stopAttack();
|
||||||
|
|
||||||
|
void startFleeing();
|
||||||
|
void stopFleeing();
|
||||||
|
bool isFleeing();
|
||||||
|
};
|
||||||
|
|
||||||
/// \brief Causes the actor to fight another actor
|
/// \brief Causes the actor to fight another actor
|
||||||
class AiCombat : public AiPackage
|
class AiCombat : public AiPackage
|
||||||
|
|
|
@ -17,22 +17,6 @@
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
struct AiFollowStorage : AiTemporaryBase
|
|
||||||
{
|
|
||||||
float mTimer;
|
|
||||||
bool mMoving;
|
|
||||||
float mTargetAngleRadians;
|
|
||||||
bool mTurnActorToTarget;
|
|
||||||
|
|
||||||
AiFollowStorage() :
|
|
||||||
mTimer(0.f),
|
|
||||||
mMoving(false),
|
|
||||||
mTargetAngleRadians(0.f),
|
|
||||||
mTurnActorToTarget(false)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
int AiFollow::mFollowIndexCounter = 0;
|
int AiFollow::mFollowIndexCounter = 0;
|
||||||
|
|
||||||
AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z)
|
AiFollow::AiFollow(const std::string &actorId, float duration, float x, float y, float z)
|
||||||
|
|
|
@ -19,6 +19,21 @@ namespace AiSequence
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
struct AiFollowStorage : AiTemporaryBase
|
||||||
|
{
|
||||||
|
float mTimer;
|
||||||
|
bool mMoving;
|
||||||
|
float mTargetAngleRadians;
|
||||||
|
bool mTurnActorToTarget;
|
||||||
|
|
||||||
|
AiFollowStorage() :
|
||||||
|
mTimer(0.f),
|
||||||
|
mMoving(false),
|
||||||
|
mTargetAngleRadians(0.f),
|
||||||
|
mTurnActorToTarget(false)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
/// \brief AiPackage for an actor to follow another actor/the PC
|
/// \brief AiPackage for an actor to follow another actor/the PC
|
||||||
/** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely
|
/** The AI will follow the target until a condition (time, or position) are set. Both can be disabled to cause the actor to follow the other indefinitely
|
||||||
**/
|
**/
|
||||||
|
|
|
@ -188,7 +188,7 @@ bool isActualAiPackage(int packageTypeId)
|
||||||
&& packageTypeId != AiPackage::TypeIdInternalTravel);
|
&& packageTypeId != AiPackage::TypeIdInternalTravel);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state, float duration)
|
void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration)
|
||||||
{
|
{
|
||||||
if(actor != getPlayer())
|
if(actor != getPlayer())
|
||||||
{
|
{
|
||||||
|
@ -262,7 +262,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor, CharacterController& charac
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (package->execute (actor,characterController,state,duration))
|
if (package->execute (actor, characterController, mAiState, duration))
|
||||||
{
|
{
|
||||||
// Put repeating noncombat AI packages on the end of the stack so they can be used again
|
// Put repeating noncombat AI packages on the end of the stack so they can be used again
|
||||||
if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat()))
|
if (isActualAiPackage(packageTypeId) && (mRepeat || package->getRepeat()))
|
||||||
|
@ -360,6 +360,14 @@ void AiSequence::stack (const AiPackage& package, const MWWorld::Ptr& actor, boo
|
||||||
}
|
}
|
||||||
|
|
||||||
mPackages.push_back (package.clone());
|
mPackages.push_back (package.clone());
|
||||||
|
|
||||||
|
// Make sure that temporary storage is empty
|
||||||
|
if (cancelOther)
|
||||||
|
{
|
||||||
|
mAiState.moveIn(new AiCombatStorage());
|
||||||
|
mAiState.moveIn(new AiFollowStorage());
|
||||||
|
mAiState.moveIn(new AiWanderStorage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AiPackage* MWMechanics::AiSequence::getActivePackage()
|
AiPackage* MWMechanics::AiSequence::getActivePackage()
|
||||||
|
@ -494,12 +502,12 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence)
|
||||||
mLastAiPackage = sequence.mLastAiPackage;
|
mLastAiPackage = sequence.mLastAiPackage;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state)
|
void AiSequence::fastForward(const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
if (!mPackages.empty())
|
if (!mPackages.empty())
|
||||||
{
|
{
|
||||||
MWMechanics::AiPackage* package = mPackages.front();
|
MWMechanics::AiPackage* package = mPackages.front();
|
||||||
package->fastForward(actor, state);
|
package->fastForward(actor, mAiState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
||||||
|
#include "aistate.hpp"
|
||||||
|
|
||||||
#include <components/esm/loadnpc.hpp>
|
#include <components/esm/loadnpc.hpp>
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
|
@ -47,6 +49,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
/// The type of AI package that ran last
|
/// The type of AI package that ran last
|
||||||
int mLastAiPackage;
|
int mLastAiPackage;
|
||||||
|
AiState mAiState;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
///Default constructor
|
///Default constructor
|
||||||
|
@ -104,10 +107,10 @@ namespace MWMechanics
|
||||||
void stopPursuit();
|
void stopPursuit();
|
||||||
|
|
||||||
/// Execute current package, switching if needed.
|
/// Execute current package, switching if needed.
|
||||||
void execute (const MWWorld::Ptr& actor, CharacterController& characterController, MWMechanics::AiState& state, float duration);
|
void execute (const MWWorld::Ptr& actor, CharacterController& characterController, float duration);
|
||||||
|
|
||||||
/// Simulate the passing of time using the currently active AI package
|
/// Simulate the passing of time using the currently active AI package
|
||||||
void fastForward(const MWWorld::Ptr &actor, AiState &state);
|
void fastForward(const MWWorld::Ptr &actor);
|
||||||
|
|
||||||
/// Remove all packages.
|
/// Remove all packages.
|
||||||
void clear();
|
void clear();
|
||||||
|
|
|
@ -51,67 +51,6 @@ namespace MWMechanics
|
||||||
std::string("idle9"),
|
std::string("idle9"),
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
|
|
||||||
struct AiWanderStorage : AiTemporaryBase
|
|
||||||
{
|
|
||||||
// the z rotation angle to reach
|
|
||||||
// when mTurnActorGivingGreetingToFacePlayer is true
|
|
||||||
float mTargetAngleRadians;
|
|
||||||
bool mTurnActorGivingGreetingToFacePlayer;
|
|
||||||
float mReaction; // update some actions infrequently
|
|
||||||
|
|
||||||
AiWander::GreetingState mSaidGreeting;
|
|
||||||
int mGreetingTimer;
|
|
||||||
|
|
||||||
const MWWorld::CellStore* mCell; // for detecting cell change
|
|
||||||
|
|
||||||
// AiWander states
|
|
||||||
AiWander::WanderState mState;
|
|
||||||
|
|
||||||
bool mIsWanderingManually;
|
|
||||||
bool mCanWanderAlongPathGrid;
|
|
||||||
|
|
||||||
unsigned short mIdleAnimation;
|
|
||||||
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
|
|
||||||
|
|
||||||
// do we need to calculate allowed nodes based on mDistance
|
|
||||||
bool mPopulateAvailableNodes;
|
|
||||||
|
|
||||||
// allowed pathgrid nodes based on mDistance from the spawn point
|
|
||||||
// in local coordinates of mCell
|
|
||||||
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
|
|
||||||
|
|
||||||
ESM::Pathgrid::Point mCurrentNode;
|
|
||||||
bool mTrimCurrentNode;
|
|
||||||
|
|
||||||
float mDoorCheckDuration;
|
|
||||||
int mStuckCount;
|
|
||||||
|
|
||||||
AiWanderStorage():
|
|
||||||
mTargetAngleRadians(0),
|
|
||||||
mTurnActorGivingGreetingToFacePlayer(false),
|
|
||||||
mReaction(0),
|
|
||||||
mSaidGreeting(AiWander::Greet_None),
|
|
||||||
mGreetingTimer(0),
|
|
||||||
mCell(NULL),
|
|
||||||
mState(AiWander::Wander_ChooseAction),
|
|
||||||
mIsWanderingManually(false),
|
|
||||||
mCanWanderAlongPathGrid(true),
|
|
||||||
mIdleAnimation(0),
|
|
||||||
mBadIdles(),
|
|
||||||
mPopulateAvailableNodes(true),
|
|
||||||
mAllowedNodes(),
|
|
||||||
mTrimCurrentNode(false),
|
|
||||||
mDoorCheckDuration(0), // TODO: maybe no longer needed
|
|
||||||
mStuckCount(0)
|
|
||||||
{};
|
|
||||||
|
|
||||||
void setState(const AiWander::WanderState wanderState, const bool isManualWander = false) {
|
|
||||||
mState = wanderState;
|
|
||||||
mIsWanderingManually = isManualWander;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
|
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
|
||||||
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
|
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
|
||||||
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0))
|
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0))
|
||||||
|
@ -221,7 +160,7 @@ namespace MWMechanics
|
||||||
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
|
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
|
||||||
|
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
storage.setState(Wander_Walking);
|
storage.setState(AiWanderStorage::Wander_Walking);
|
||||||
}
|
}
|
||||||
|
|
||||||
doPerFrameActionsForState(actor, duration, storage, pos);
|
doPerFrameActionsForState(actor, duration, storage, pos);
|
||||||
|
@ -270,7 +209,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
if(actorCanMoveByZ && mDistance > 0) {
|
if(actorCanMoveByZ && mDistance > 0) {
|
||||||
// Typically want to idle for a short time before the next wander
|
// Typically want to idle for a short time before the next wander
|
||||||
if (Misc::Rng::rollDice(100) >= 92 && storage.mState != Wander_Walking) {
|
if (Misc::Rng::rollDice(100) >= 92 && storage.mState != AiWanderStorage::Wander_Walking) {
|
||||||
wanderNearStart(actor, storage, mDistance);
|
wanderNearStart(actor, storage, mDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,7 +222,7 @@ namespace MWMechanics
|
||||||
if (Misc::Rng::rollDice(100) >= 96) {
|
if (Misc::Rng::rollDice(100) >= 96) {
|
||||||
wanderNearStart(actor, storage, mDistance);
|
wanderNearStart(actor, storage, mDistance);
|
||||||
} else {
|
} else {
|
||||||
storage.setState(Wander_IdleNow);
|
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||||
}
|
}
|
||||||
} else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually) {
|
} else if (storage.mAllowedNodes.empty() && !storage.mIsWanderingManually) {
|
||||||
storage.mCanWanderAlongPathGrid = false;
|
storage.mCanWanderAlongPathGrid = false;
|
||||||
|
@ -299,13 +238,13 @@ namespace MWMechanics
|
||||||
mDistance = 0;
|
mDistance = 0;
|
||||||
|
|
||||||
// Allow interrupting a walking actor to trigger a greeting
|
// Allow interrupting a walking actor to trigger a greeting
|
||||||
WanderState& wanderState = storage.mState;
|
AiWanderStorage::WanderState& wanderState = storage.mState;
|
||||||
if ((wanderState == Wander_IdleNow) || (wanderState == Wander_Walking))
|
if ((wanderState == AiWanderStorage::Wander_IdleNow) || (wanderState == AiWanderStorage::Wander_Walking))
|
||||||
{
|
{
|
||||||
playGreetingIfPlayerGetsTooClose(actor, storage);
|
playGreetingIfPlayerGetsTooClose(actor, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((wanderState == Wander_MoveNow) && storage.mCanWanderAlongPathGrid)
|
if ((wanderState == AiWanderStorage::Wander_MoveNow) && storage.mCanWanderAlongPathGrid)
|
||||||
{
|
{
|
||||||
// Construct a new path if there isn't one
|
// Construct a new path if there isn't one
|
||||||
if(!mPathFinder.isPathConstructed())
|
if(!mPathFinder.isPathConstructed())
|
||||||
|
@ -381,7 +320,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
{
|
{
|
||||||
storage.setState(Wander_Walking, true);
|
storage.setState(AiWanderStorage::Wander_Walking, true);
|
||||||
mHasDestination = true;
|
mHasDestination = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
@ -410,26 +349,26 @@ namespace MWMechanics
|
||||||
void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) {
|
void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) {
|
||||||
stopWalking(actor, storage);
|
stopWalking(actor, storage);
|
||||||
mObstacleCheck.clear();
|
mObstacleCheck.clear();
|
||||||
storage.setState(Wander_IdleNow);
|
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos)
|
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos)
|
||||||
{
|
{
|
||||||
switch (storage.mState)
|
switch (storage.mState)
|
||||||
{
|
{
|
||||||
case Wander_IdleNow:
|
case AiWanderStorage::Wander_IdleNow:
|
||||||
onIdleStatePerFrameActions(actor, duration, storage);
|
onIdleStatePerFrameActions(actor, duration, storage);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Wander_Walking:
|
case AiWanderStorage::Wander_Walking:
|
||||||
onWalkingStatePerFrameActions(actor, duration, storage, pos);
|
onWalkingStatePerFrameActions(actor, duration, storage, pos);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Wander_ChooseAction:
|
case AiWanderStorage::Wander_ChooseAction:
|
||||||
onChooseActionStatePerFrameActions(actor, storage);
|
onChooseActionStatePerFrameActions(actor, storage);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Wander_MoveNow:
|
case AiWanderStorage::Wander_MoveNow:
|
||||||
break; // nothing to do
|
break; // nothing to do
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -451,7 +390,7 @@ namespace MWMechanics
|
||||||
if (mDistance && // actor is not intended to be stationary
|
if (mDistance && // actor is not intended to be stationary
|
||||||
proximityToDoor(actor, distance*1.6f))
|
proximityToDoor(actor, distance*1.6f))
|
||||||
{
|
{
|
||||||
storage.setState(Wander_MoveNow);
|
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||||
storage.mTrimCurrentNode = false; // just in case
|
storage.mTrimCurrentNode = false; // just in case
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -468,13 +407,13 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if idle animation finished
|
// Check if idle animation finished
|
||||||
GreetingState& greetingState = storage.mSaidGreeting;
|
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
|
||||||
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None))
|
if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == AiWanderStorage::Greet_Done || greetingState == AiWanderStorage::Greet_None))
|
||||||
{
|
{
|
||||||
if (mPathFinder.isPathConstructed())
|
if (mPathFinder.isPathConstructed())
|
||||||
storage.setState(Wander_Walking);
|
storage.setState(AiWanderStorage::Wander_Walking);
|
||||||
else
|
else
|
||||||
storage.setState(Wander_ChooseAction);
|
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,7 +424,7 @@ namespace MWMechanics
|
||||||
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, ESM::Pathgrid::Point(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
|
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, ESM::Pathgrid::Point(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
|
||||||
{
|
{
|
||||||
stopWalking(actor, storage);
|
stopWalking(actor, storage);
|
||||||
storage.setState(Wander_ChooseAction);
|
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -502,7 +441,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (!idleAnimation && mDistance)
|
if (!idleAnimation && mDistance)
|
||||||
{
|
{
|
||||||
storage.setState(Wander_MoveNow);
|
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(idleAnimation)
|
if(idleAnimation)
|
||||||
|
@ -512,13 +451,13 @@ namespace MWMechanics
|
||||||
if(!playIdle(actor, idleAnimation))
|
if(!playIdle(actor, idleAnimation))
|
||||||
{
|
{
|
||||||
storage.mBadIdles.push_back(idleAnimation);
|
storage.mBadIdles.push_back(idleAnimation);
|
||||||
storage.setState(Wander_ChooseAction);
|
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.setState(Wander_IdleNow);
|
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos)
|
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos)
|
||||||
|
@ -534,7 +473,7 @@ namespace MWMechanics
|
||||||
trimAllowedNodes(storage.mAllowedNodes, mPathFinder);
|
trimAllowedNodes(storage.mAllowedNodes, mPathFinder);
|
||||||
mObstacleCheck.clear();
|
mObstacleCheck.clear();
|
||||||
stopWalking(actor, storage);
|
stopWalking(actor, storage);
|
||||||
storage.setState(Wander_MoveNow);
|
storage.setState(AiWanderStorage::Wander_MoveNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.mStuckCount++; // TODO: maybe no longer needed
|
storage.mStuckCount++; // TODO: maybe no longer needed
|
||||||
|
@ -545,7 +484,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
mObstacleCheck.clear();
|
mObstacleCheck.clear();
|
||||||
stopWalking(actor, storage);
|
stopWalking(actor, storage);
|
||||||
storage.setState(Wander_ChooseAction);
|
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||||
storage.mStuckCount = 0;
|
storage.mStuckCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -596,8 +535,8 @@ namespace MWMechanics
|
||||||
float playerDistSqr = (playerPos - actorPos).length2();
|
float playerDistSqr = (playerPos - actorPos).length2();
|
||||||
|
|
||||||
int& greetingTimer = storage.mGreetingTimer;
|
int& greetingTimer = storage.mGreetingTimer;
|
||||||
GreetingState& greetingState = storage.mSaidGreeting;
|
AiWanderStorage::GreetingState& greetingState = storage.mSaidGreeting;
|
||||||
if (greetingState == Greet_None)
|
if (greetingState == AiWanderStorage::Greet_None)
|
||||||
{
|
{
|
||||||
if ((playerDistSqr <= helloDistance*helloDistance) &&
|
if ((playerDistSqr <= helloDistance*helloDistance) &&
|
||||||
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
|
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
|
||||||
|
@ -606,37 +545,37 @@ namespace MWMechanics
|
||||||
|
|
||||||
if (greetingTimer >= GREETING_SHOULD_START)
|
if (greetingTimer >= GREETING_SHOULD_START)
|
||||||
{
|
{
|
||||||
greetingState = Greet_InProgress;
|
greetingState = AiWanderStorage::Greet_InProgress;
|
||||||
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
|
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
|
||||||
greetingTimer = 0;
|
greetingTimer = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (greetingState == Greet_InProgress)
|
if (greetingState == AiWanderStorage::Greet_InProgress)
|
||||||
{
|
{
|
||||||
greetingTimer++;
|
greetingTimer++;
|
||||||
|
|
||||||
if (storage.mState == Wander_Walking)
|
if (storage.mState == AiWanderStorage::Wander_Walking)
|
||||||
{
|
{
|
||||||
stopWalking(actor, storage, false);
|
stopWalking(actor, storage, false);
|
||||||
mObstacleCheck.clear();
|
mObstacleCheck.clear();
|
||||||
storage.setState(Wander_IdleNow);
|
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||||
}
|
}
|
||||||
|
|
||||||
turnActorToFacePlayer(actorPos, playerPos, storage);
|
turnActorToFacePlayer(actorPos, playerPos, storage);
|
||||||
|
|
||||||
if (greetingTimer >= GREETING_SHOULD_END)
|
if (greetingTimer >= GREETING_SHOULD_END)
|
||||||
{
|
{
|
||||||
greetingState = Greet_Done;
|
greetingState = AiWanderStorage::Greet_Done;
|
||||||
greetingTimer = 0;
|
greetingTimer = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (greetingState == MWMechanics::AiWander::Greet_Done)
|
if (greetingState == AiWanderStorage::Greet_Done)
|
||||||
{
|
{
|
||||||
float resetDist = 2 * helloDistance;
|
float resetDist = 2 * helloDistance;
|
||||||
if (playerDistSqr >= resetDist*resetDist)
|
if (playerDistSqr >= resetDist*resetDist)
|
||||||
greetingState = Greet_None;
|
greetingState = AiWanderStorage::Greet_None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -676,7 +615,7 @@ namespace MWMechanics
|
||||||
storage.mAllowedNodes.push_back(storage.mCurrentNode);
|
storage.mAllowedNodes.push_back(storage.mCurrentNode);
|
||||||
storage.mCurrentNode = temp;
|
storage.mCurrentNode = temp;
|
||||||
|
|
||||||
storage.setState(Wander_Walking);
|
storage.setState(AiWanderStorage::Wander_Walking);
|
||||||
}
|
}
|
||||||
// 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
|
||||||
|
|
|
@ -22,7 +22,80 @@ namespace ESM
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
struct AiWanderStorage;
|
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
|
||||||
|
struct AiWanderStorage : AiTemporaryBase
|
||||||
|
{
|
||||||
|
// the z rotation angle to reach
|
||||||
|
// when mTurnActorGivingGreetingToFacePlayer is true
|
||||||
|
float mTargetAngleRadians;
|
||||||
|
bool mTurnActorGivingGreetingToFacePlayer;
|
||||||
|
float mReaction; // update some actions infrequently
|
||||||
|
|
||||||
|
enum GreetingState
|
||||||
|
{
|
||||||
|
Greet_None,
|
||||||
|
Greet_InProgress,
|
||||||
|
Greet_Done
|
||||||
|
};
|
||||||
|
GreetingState mSaidGreeting;
|
||||||
|
int mGreetingTimer;
|
||||||
|
|
||||||
|
const MWWorld::CellStore* mCell; // for detecting cell change
|
||||||
|
|
||||||
|
// AiWander states
|
||||||
|
enum WanderState
|
||||||
|
{
|
||||||
|
Wander_ChooseAction,
|
||||||
|
Wander_IdleNow,
|
||||||
|
Wander_MoveNow,
|
||||||
|
Wander_Walking
|
||||||
|
};
|
||||||
|
WanderState mState;
|
||||||
|
|
||||||
|
bool mIsWanderingManually;
|
||||||
|
bool mCanWanderAlongPathGrid;
|
||||||
|
|
||||||
|
unsigned short mIdleAnimation;
|
||||||
|
std::vector<unsigned short> mBadIdles; // Idle animations that when called cause errors
|
||||||
|
|
||||||
|
// do we need to calculate allowed nodes based on mDistance
|
||||||
|
bool mPopulateAvailableNodes;
|
||||||
|
|
||||||
|
// allowed pathgrid nodes based on mDistance from the spawn point
|
||||||
|
// in local coordinates of mCell
|
||||||
|
std::vector<ESM::Pathgrid::Point> mAllowedNodes;
|
||||||
|
|
||||||
|
ESM::Pathgrid::Point mCurrentNode;
|
||||||
|
bool mTrimCurrentNode;
|
||||||
|
|
||||||
|
float mDoorCheckDuration;
|
||||||
|
int mStuckCount;
|
||||||
|
|
||||||
|
AiWanderStorage():
|
||||||
|
mTargetAngleRadians(0),
|
||||||
|
mTurnActorGivingGreetingToFacePlayer(false),
|
||||||
|
mReaction(0),
|
||||||
|
mSaidGreeting(Greet_None),
|
||||||
|
mGreetingTimer(0),
|
||||||
|
mCell(NULL),
|
||||||
|
mState(Wander_ChooseAction),
|
||||||
|
mIsWanderingManually(false),
|
||||||
|
mCanWanderAlongPathGrid(true),
|
||||||
|
mIdleAnimation(0),
|
||||||
|
mBadIdles(),
|
||||||
|
mPopulateAvailableNodes(true),
|
||||||
|
mAllowedNodes(),
|
||||||
|
mTrimCurrentNode(false),
|
||||||
|
mDoorCheckDuration(0), // TODO: maybe no longer needed
|
||||||
|
mStuckCount(0)
|
||||||
|
{};
|
||||||
|
|
||||||
|
void setState(const WanderState wanderState, const bool isManualWander = false)
|
||||||
|
{
|
||||||
|
mState = wanderState;
|
||||||
|
mIsWanderingManually = isManualWander;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// \brief Causes the Actor to wander within a specified range
|
/// \brief Causes the Actor to wander within a specified range
|
||||||
class AiWander : public AiPackage
|
class AiWander : public AiPackage
|
||||||
|
@ -52,19 +125,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
osg::Vec3f getDestination(const MWWorld::Ptr& actor) const;
|
osg::Vec3f getDestination(const MWWorld::Ptr& actor) const;
|
||||||
|
|
||||||
enum GreetingState {
|
|
||||||
Greet_None,
|
|
||||||
Greet_InProgress,
|
|
||||||
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();
|
||||||
|
|
Loading…
Reference in a new issue