Merge remote-tracking branch 'terrorfisch/aistate'

This commit is contained in:
Marc Zinnschlag 2014-10-13 19:14:15 +02:00
commit 6beee95151
23 changed files with 537 additions and 307 deletions

View file

@ -25,6 +25,7 @@
#include "npcstats.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
#include "character.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
@ -1170,7 +1171,7 @@ namespace MWMechanics
updateCrimePersuit(iter->first, duration);
if (iter->first != player)
iter->first.getClass().getCreatureStats(iter->first).getAiSequence().execute(iter->first, duration);
iter->first.getClass().getCreatureStats(iter->first).getAiSequence().execute(iter->first,iter->second->getAiState(), duration);
CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first);
if(!stats.isDead())

View file

@ -21,7 +21,7 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const
{
return new AiActivate(*this);
}
bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration)
bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor, AiState& state, float duration)
{
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
const MWWorld::Ptr target = MWBase::Environment::get().getWorld()->searchPtr(mObjectId, false); //The target to follow

View file

@ -28,7 +28,7 @@ namespace MWMechanics
AiActivate(const ESM::AiSequence::AiActivate* activate);
virtual AiActivate *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration);
virtual int getTypeId() const;
virtual void writeState(ESM::AiSequence::AiSequence& sequence) const;

View file

@ -18,7 +18,7 @@ MWMechanics::AiAvoidDoor::AiAvoidDoor(const MWWorld::Ptr& doorPtr)
}
bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor,float duration)
bool MWMechanics::AiAvoidDoor::execute (const MWWorld::Ptr& actor, AiState& state, float duration)
{
ESM::Position pos = actor.getRefData().getPosition();

View file

@ -20,7 +20,7 @@ namespace MWMechanics
virtual AiAvoidDoor *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration);
virtual int getTypeId() const;

View file

@ -26,12 +26,6 @@
namespace
{
static float sgn(Ogre::Radian a)
{
if(a.valueDegrees() > 0)
return 1.0;
return -1.0;
}
//chooses an attack depending on probability to avoid uniformity
ESM::Weapon::AttackType chooseBestAttack(const ESM::Weapon* weapon, MWMechanics::Movement &movement);
@ -41,16 +35,15 @@ namespace
Ogre::Vector3 AimDirToMovingTarget(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, const Ogre::Vector3& vLastTargetPos,
float duration, int weapType, float strength);
float getZAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f)
float getZAngleToDir(const Ogre::Vector3& dir)
{
float len = (dirLen > 0.0f)? dirLen : dir.length();
return Ogre::Radian( Ogre::Math::ACos(dir.y / len) * sgn(Ogre::Math::ASin(dir.x / len)) ).valueDegrees();
return Ogre::Math::ATan2(dir.x,dir.y).valueDegrees();
}
float getXAngleToDir(const Ogre::Vector3& dir, float dirLen = 0.0f)
{
float len = (dirLen > 0.0f)? dirLen : dir.length();
return Ogre::Radian(-Ogre::Math::ASin(dir.z / len)).valueDegrees();
return -Ogre::Math::ASin(dir.z / len).valueDegrees();
}
@ -88,40 +81,60 @@ namespace MWMechanics
static const float DOOR_CHECK_INTERVAL = 1.5f; // same as AiWander
// NOTE: MIN_DIST_TO_DOOR_SQUARED is defined in obstacle.hpp
/// \brief This class holds the variables AiCombat needs which are deleted if the package becomes inactive.
struct AiCombatStorage : AiTemporaryBase
{
float mTimerAttack;
float mTimerReact;
float mTimerCombatMove;
bool mReadyToAttack;
bool mAttack;
bool mFollowTarget;
bool mCombatMove;
Ogre::Vector3 mLastTargetPos;
const MWWorld::CellStore* mCell;
boost::shared_ptr<Action> mCurrentAction;
float mActionCooldown;
float mStrength;
float mMinMaxAttackDuration[3][2];
bool mMinMaxAttackDurationInitialised;
bool mForceNoShortcut;
ESM::Position mShortcutFailPos;
Ogre::Vector3 mLastActorPos;
MWMechanics::Movement mMovement;
AiCombatStorage():
mTimerAttack(0),
mTimerReact(0),
mTimerCombatMove(0),
mAttack(false),
mFollowTarget(false),
mCombatMove(false),
mReadyToAttack(false),
mForceNoShortcut(false),
mCell(NULL),
mCurrentAction(),
mActionCooldown(0),
mStrength(),
mMinMaxAttackDurationInitialised(false),
mLastTargetPos(0,0,0),
mLastActorPos(0,0,0),
mMovement(){}
};
AiCombat::AiCombat(const MWWorld::Ptr& actor) :
mTargetActorId(actor.getClass().getCreatureStats(actor).getActorId())
, mMinMaxAttackDuration()
, mMovement()
{
init();
mLastTargetPos = Ogre::Vector3(actor.getRefData().getPosition().pos);
}
{}
AiCombat::AiCombat(const ESM::AiSequence::AiCombat *combat)
: mMinMaxAttackDuration()
, mMovement()
{
mTargetActorId = combat->mTargetActorId;
init();
}
void AiCombat::init()
{
mActionCooldown = 0;
mTimerAttack = 0;
mTimerReact = 0;
mTimerCombatMove = 0;
mFollowTarget = false;
mReadyToAttack = false;
mAttack = false;
mCombatMove = false;
mForceNoShortcut = false;
mStrength = 0;
mCell = NULL;
mLastTargetPos = Ogre::Vector3(0,0,0);
mMinMaxAttackDurationInitialised = false;
}
/*
@ -170,8 +183,13 @@ namespace MWMechanics
* Use the Observer Pattern to co-ordinate attacks, provide intelligence on
* whether the target was hit, etc.
*/
bool AiCombat::execute (const MWWorld::Ptr& actor,float duration)
bool AiCombat::execute (const MWWorld::Ptr& actor, AiState& state, float duration)
{
// get or create temporary storage
AiCombatStorage& storage = state.get<AiCombatStorage>();
//General description
if(actor.getClass().getCreatureStats(actor).isDead())
return true;
@ -197,32 +215,39 @@ namespace MWMechanics
{
actorClass.getCreatureStats(actor).setAttackingOrSpell(false);
return true;
}
}
//Update every frame
if(mCombatMove)
bool& combatMove = storage.mCombatMove;
float& timerCombatMove = storage.mTimerCombatMove;
MWMechanics::Movement& movement = storage.mMovement;
if(combatMove)
{
mTimerCombatMove -= duration;
if( mTimerCombatMove <= 0)
timerCombatMove -= duration;
if( timerCombatMove <= 0)
{
mTimerCombatMove = 0;
mMovement.mPosition[1] = mMovement.mPosition[0] = 0;
mCombatMove = false;
timerCombatMove = 0;
movement.mPosition[1] = movement.mPosition[0] = 0;
combatMove = false;
}
}
actorClass.getMovementSettings(actor) = mMovement;
actorClass.getMovementSettings(actor) = movement;
actorClass.getMovementSettings(actor).mRotation[0] = 0;
actorClass.getMovementSettings(actor).mRotation[2] = 0;
if(mMovement.mRotation[2] != 0)
if(movement.mRotation[2] != 0)
{
if(zTurn(actor, Ogre::Degree(mMovement.mRotation[2]))) mMovement.mRotation[2] = 0;
if(zTurn(actor, Ogre::Degree(movement.mRotation[2]))) movement.mRotation[2] = 0;
}
if(mMovement.mRotation[0] != 0)
if(movement.mRotation[0] != 0)
{
if(smoothTurn(actor, Ogre::Degree(mMovement.mRotation[0]), 0)) mMovement.mRotation[0] = 0;
if(smoothTurn(actor, Ogre::Degree(movement.mRotation[0]), 0)) movement.mRotation[0] = 0;
}
//TODO: Some skills affect period of strikes.For berserk-like style period ~ 0.25f
@ -230,44 +255,57 @@ namespace MWMechanics
ESM::Weapon::AttackType attackType;
if(mReadyToAttack)
bool& attack = storage.mAttack;
bool& readyToAttack = storage.mReadyToAttack;
float& timerAttack = storage.mTimerAttack;
bool& minMaxAttackDurationInitialised = storage.mMinMaxAttackDurationInitialised;
float (&minMaxAttackDuration)[3][2] = storage.mMinMaxAttackDuration;
if(readyToAttack)
{
if (!mMinMaxAttackDurationInitialised)
if (!minMaxAttackDurationInitialised)
{
// TODO: this must be updated when a different weapon is equipped
getMinMaxAttackDuration(actor, mMinMaxAttackDuration);
mMinMaxAttackDurationInitialised = true;
getMinMaxAttackDuration(actor, minMaxAttackDuration);
minMaxAttackDurationInitialised = true;
}
if (mTimerAttack < 0) mAttack = false;
if (timerAttack < 0) attack = false;
mTimerAttack -= duration;
timerAttack -= duration;
}
else
{
mTimerAttack = -attacksPeriod;
mAttack = false;
timerAttack = -attacksPeriod;
attack = false;
}
actorClass.getCreatureStats(actor).setAttackingOrSpell(mAttack);
actorClass.getCreatureStats(actor).setAttackingOrSpell(attack);
mActionCooldown -= duration;
float& actionCooldown = storage.mActionCooldown;
actionCooldown -= duration;
float& timerReact = storage.mTimerReact;
float tReaction = 0.25f;
if(mTimerReact < tReaction)
if(timerReact < tReaction)
{
mTimerReact += duration;
timerReact += duration;
return false;
}
//Update with period = tReaction
mTimerReact = 0;
bool cellChange = mCell && (actor.getCell() != mCell);
if(!mCell || cellChange)
timerReact = 0;
const MWWorld::CellStore*& currentCell = storage.mCell;
bool cellChange = currentCell && (actor.getCell() != currentCell);
if(!currentCell || cellChange)
{
mCell = actor.getCell();
currentCell = actor.getCell();
}
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(actor);
@ -276,18 +314,19 @@ namespace MWMechanics
actorClass.getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
if (mActionCooldown > 0)
if (actionCooldown > 0)
return false;
float rangeAttack = 0;
float rangeFollow = 0;
boost::shared_ptr<Action>& currentAction = storage.mCurrentAction;
if (anim->upperBodyReady())
{
mCurrentAction = prepareNextAction(actor, target);
mActionCooldown = mCurrentAction->getActionCooldown();
currentAction = prepareNextAction(actor, target);
actionCooldown = currentAction->getActionCooldown();
}
if (mCurrentAction.get())
mCurrentAction->getCombatRange(rangeAttack, rangeFollow);
if (currentAction.get())
currentAction->getCombatRange(rangeAttack, rangeFollow);
// FIXME: consider moving this stuff to ActionWeapon::getCombatRange
const ESM::Weapon *weapon = NULL;
@ -346,21 +385,23 @@ namespace MWMechanics
weapRange = 150.f;
}
float& strength = storage.mStrength;
// start new attack
if(mReadyToAttack)
if(readyToAttack)
{
if(mTimerAttack <= -attacksPeriod)
if(timerAttack <= -attacksPeriod)
{
mAttack = true; // attack starts just now
attack = true; // attack starts just now
if (!distantCombat) attackType = chooseBestAttack(weapon, mMovement);
if (!distantCombat) attackType = chooseBestAttack(weapon, movement);
else attackType = ESM::Weapon::AT_Chop; // cause it's =0
mStrength = static_cast<float>(rand()) / RAND_MAX;
strength = static_cast<float>(rand()) / RAND_MAX;
// Note: may be 0 for some animations
mTimerAttack = mMinMaxAttackDuration[attackType][0] +
(mMinMaxAttackDuration[attackType][1] - mMinMaxAttackDuration[attackType][0]) * mStrength;
timerAttack = minMaxAttackDuration[attackType][0] +
(minMaxAttackDuration[attackType][1] - minMaxAttackDuration[attackType][0]) * strength;
//say a provoking combat phrase
if (actor.getClass().isNpc())
@ -411,13 +452,16 @@ namespace MWMechanics
Ogre::Vector3 vTargetPos(target.getRefData().getPosition().pos);
Ogre::Vector3 vDirToTarget = vTargetPos - vActorPos;
float distToTarget = vDirToTarget.length();
Ogre::Vector3& lastActorPos = storage.mLastActorPos;
bool& followTarget = storage.mFollowTarget;
bool isStuck = false;
float speed = 0.0f;
if(mMovement.mPosition[1] && (mLastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2)
if(movement.mPosition[1] && (lastActorPos - vActorPos).length() < (speed = actorClass.getSpeed(actor)) * tReaction / 2)
isStuck = true;
mLastActorPos = vActorPos;
lastActorPos = vActorPos;
// check if actor can move along z-axis
bool canMoveByZ = (actorClass.canSwim(actor) && world->isSwimming(actor))
@ -427,7 +471,7 @@ namespace MWMechanics
bool inLOS = distantCombat ? world->getLOS(actor, target) : true;
// (within attack dist) || (not quite attack dist while following)
if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && mFollowTarget && !isStuck)))
if(inLOS && (distToTarget < rangeAttack || (distToTarget <= rangeFollow && followTarget && !isStuck)))
{
//Melee and Close-up combat
@ -437,29 +481,30 @@ namespace MWMechanics
// note: in getZAngleToDir if we preserve dir.z then horizontal angle can be inaccurate
if (distantCombat)
{
Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, mLastTargetPos, tReaction, weaptype, mStrength);
mLastTargetPos = vTargetPos;
mMovement.mRotation[0] = getXAngleToDir(vAimDir);
mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vAimDir.x, vAimDir.y, 0));
Ogre::Vector3& lastTargetPos = storage.mLastTargetPos;
Ogre::Vector3 vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, tReaction, weaptype, strength);
lastTargetPos = vTargetPos;
movement.mRotation[0] = getXAngleToDir(vAimDir);
movement.mRotation[2] = getZAngleToDir(vAimDir);
}
else
{
mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget);
mMovement.mRotation[2] = getZAngleToDir(Ogre::Vector3(vDirToTarget.x, vDirToTarget.y, 0));
movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget);
movement.mRotation[2] = getZAngleToDir(vDirToTarget);
}
// (not quite attack dist while following)
if (mFollowTarget && distToTarget > rangeAttack)
if (followTarget && distToTarget > rangeAttack)
{
//Close-up combat: just run up on target
mMovement.mPosition[1] = 1;
movement.mPosition[1] = 1;
}
else // (within attack dist)
{
if(mMovement.mPosition[0] || mMovement.mPosition[1])
if(movement.mPosition[0] || movement.mPosition[1])
{
mTimerCombatMove = 0.1f + 0.1f * static_cast<float>(rand())/RAND_MAX;
mCombatMove = true;
timerCombatMove = 0.1f + 0.1f * static_cast<float>(rand())/RAND_MAX;
combatMove = true;
}
// only NPCs are smart enough to use dodge movements
else if(actorClass.isNpc() && (!distantCombat || (distantCombat && distToTarget < rangeAttack/2)))
@ -467,20 +512,20 @@ namespace MWMechanics
//apply sideway movement (kind of dodging) with some probability
if(static_cast<float>(rand())/RAND_MAX < 0.25)
{
mMovement.mPosition[0] = static_cast<float>(rand())/RAND_MAX < 0.5? 1: -1;
mTimerCombatMove = 0.05f + 0.15f * static_cast<float>(rand())/RAND_MAX;
mCombatMove = true;
movement.mPosition[0] = static_cast<float>(rand())/RAND_MAX < 0.5? 1: -1;
timerCombatMove = 0.05f + 0.15f * static_cast<float>(rand())/RAND_MAX;
combatMove = true;
}
}
if(distantCombat && distToTarget < rangeAttack/4)
{
mMovement.mPosition[1] = -1;
movement.mPosition[1] = -1;
}
mReadyToAttack = true;
readyToAttack = true;
//only once got in melee combat, actor is allowed to use close-up shortcutting
mFollowTarget = true;
followTarget = true;
}
}
else // remote pathfinding
@ -489,8 +534,11 @@ namespace MWMechanics
if (!distantCombat) inLOS = world->getLOS(actor, target);
// check if shortcut is available
if(inLOS && (!isStuck || mReadyToAttack)
&& (!mForceNoShortcut || (Ogre::Vector3(mShortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST))
bool& forceNoShortcut = storage.mForceNoShortcut;
ESM::Position& shortcutFailPos = storage.mShortcutFailPos;
if(inLOS && (!isStuck || readyToAttack)
&& (!forceNoShortcut || (Ogre::Vector3(shortcutFailPos.pos) - vActorPos).length() >= PATHFIND_SHORTCUT_RETRY_DIST))
{
if(speed == 0.0f) speed = actorClass.getSpeed(actor);
// maximum dist before pit/obstacle for actor to avoid them depending on his speed
@ -504,21 +552,21 @@ namespace MWMechanics
if(preferShortcut)
{
if (canMoveByZ)
mMovement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget);
mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
mForceNoShortcut = false;
mShortcutFailPos.pos[0] = mShortcutFailPos.pos[1] = mShortcutFailPos.pos[2] = 0;
movement.mRotation[0] = getXAngleToDir(vDirToTarget, distToTarget);
movement.mRotation[2] = getZAngleToDir(vDirToTarget);
forceNoShortcut = false;
shortcutFailPos.pos[0] = shortcutFailPos.pos[1] = shortcutFailPos.pos[2] = 0;
mPathFinder.clearPath();
}
else // if shortcut failed stick to path grid
{
if(!isStuck && mShortcutFailPos.pos[0] == 0.0f && mShortcutFailPos.pos[1] == 0.0f && mShortcutFailPos.pos[2] == 0.0f)
if(!isStuck && shortcutFailPos.pos[0] == 0.0f && shortcutFailPos.pos[1] == 0.0f && shortcutFailPos.pos[2] == 0.0f)
{
mForceNoShortcut = true;
mShortcutFailPos = pos;
forceNoShortcut = true;
shortcutFailPos = pos;
}
mFollowTarget = false;
followTarget = false;
buildNewPath(actor, target); //may fail to build a path, check before use
@ -536,7 +584,7 @@ namespace MWMechanics
// if current actor pos is closer to target then last point of path (excluding target itself) then go straight on target
if(distToTarget <= (vTargetPos - vBeforeTarget).length())
{
mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
movement.mRotation[2] = getZAngleToDir(vDirToTarget);
preferShortcut = true;
}
}
@ -545,20 +593,20 @@ namespace MWMechanics
if(!preferShortcut)
{
if(!mPathFinder.getPath().empty())
mMovement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
movement.mRotation[2] = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
else
mMovement.mRotation[2] = getZAngleToDir(vDirToTarget, distToTarget);
movement.mRotation[2] = getZAngleToDir(vDirToTarget);
}
}
mMovement.mPosition[1] = 1;
if (mReadyToAttack)
movement.mPosition[1] = 1;
if (readyToAttack)
{
// to stop possible sideway moving after target moved out of attack range
mCombatMove = true;
mTimerCombatMove = 0;
combatMove = true;
timerCombatMove = 0;
}
mReadyToAttack = false;
readyToAttack = false;
}
if(!isStuck && distToTarget > rangeAttack && !distantCombat)
@ -579,16 +627,16 @@ namespace MWMechanics
float t = s1/speed1;
float s2 = speed2 * t;
float t_swing =
mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0] +
(mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - mMinMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * static_cast<float>(rand()) / RAND_MAX;
minMaxAttackDuration[ESM::Weapon::AT_Thrust][0] +
(minMaxAttackDuration[ESM::Weapon::AT_Thrust][1] - minMaxAttackDuration[ESM::Weapon::AT_Thrust][0]) * static_cast<float>(rand()) / RAND_MAX;
if (t + s2/speed1 <= t_swing)
{
mReadyToAttack = true;
if(mTimerAttack <= -attacksPeriod)
readyToAttack = true;
if(timerAttack <= -attacksPeriod)
{
mTimerAttack = t_swing;
mAttack = true;
timerAttack = t_swing;
attack = true;
}
}
}
@ -598,7 +646,7 @@ namespace MWMechanics
// coded at 250ms or 1/4 second
//
// TODO: Add a parameter to vary DURATION_SAME_SPOT?
if((distToTarget > rangeAttack || mFollowTarget) &&
if((distToTarget > rangeAttack || followTarget) &&
mObstacleCheck.check(actor, tReaction)) // check if evasive action needed
{
// probably walking into another NPC TODO: untested in combat situation
@ -610,8 +658,8 @@ namespace MWMechanics
if(mPathFinder.isPathConstructed())
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
if(mFollowTarget)
mFollowTarget = false;
if(followTarget)
followTarget = false;
// FIXME: can fool actors to stay behind doors, etc.
// Related to Bug#1102 and to some degree #1155 as well
}

View file

@ -42,7 +42,7 @@ namespace MWMechanics
virtual AiCombat *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration);
virtual int getTypeId() const;
@ -54,40 +54,14 @@ namespace MWMechanics
virtual void writeState(ESM::AiSequence::AiSequence &sequence) const;
private:
PathFinder mPathFinder;
// controls duration of the actual strike
float mTimerAttack;
float mTimerReact;
// controls duration of the sideway & forward moves
// when mCombatMove is true
float mTimerCombatMove;
// AiCombat states
bool mReadyToAttack, mAttack;
bool mFollowTarget;
bool mCombatMove;
float mStrength; // this is actually make sense only in ranged combat
float mMinMaxAttackDuration[3][2]; // slash, thrust, chop has different durations
bool mMinMaxAttackDurationInitialised;
bool mForceNoShortcut;
ESM::Position mShortcutFailPos;
Ogre::Vector3 mLastActorPos;
MWMechanics::Movement mMovement;
int mTargetActorId;
Ogre::Vector3 mLastTargetPos;
const MWWorld::CellStore* mCell;
ObstacleCheck mObstacleCheck;
boost::shared_ptr<Action> mCurrentAction;
float mActionCooldown;
void buildNewPath(const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
};
}
#endif

View file

@ -64,7 +64,7 @@ namespace MWMechanics
return new AiEscort(*this);
}
bool AiEscort::execute (const MWWorld::Ptr& actor,float duration)
bool AiEscort::execute (const MWWorld::Ptr& actor, AiState& state, float duration)
{
// If AiEscort has ran for as long or longer then the duration specified
// and the duration is not infinite, the package is complete.

View file

@ -33,7 +33,7 @@ namespace MWMechanics
virtual AiEscort *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration);
virtual int getTypeId() const;
@ -48,7 +48,6 @@ namespace MWMechanics
float mMaxDist;
float mRemainingDuration; // In seconds
PathFinder mPathFinder;
int mCellX;
int mCellY;
};

View file

@ -41,7 +41,7 @@ MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow)
}
bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duration)
{
MWWorld::Ptr target = getTarget();

View file

@ -35,7 +35,7 @@ namespace MWMechanics
virtual AiFollow *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration);
virtual int getTypeId() const;

View file

@ -6,6 +6,7 @@
#include "../mwbase/world.hpp"
#include "obstacle.hpp"
#include "aistate.hpp"
namespace MWWorld
{
@ -20,8 +21,10 @@ namespace ESM
}
}
namespace MWMechanics
{
/// \brief Base class for AI packages
class AiPackage
{
@ -50,7 +53,7 @@ namespace MWMechanics
/// Updates and runs the package (Should run every frame)
/// \return Package completed?
virtual bool execute (const MWWorld::Ptr& actor,float duration) = 0;
virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration) = 0;
/// Returns the TypeID of the AiPackage
/// \see enum TypeId

View file

@ -30,7 +30,7 @@ AiPursue *MWMechanics::AiPursue::clone() const
{
return new AiPursue(*this);
}
bool AiPursue::execute (const MWWorld::Ptr& actor, float duration)
bool AiPursue::execute (const MWWorld::Ptr& actor, AiState& state, float duration)
{
if(actor.getClass().getCreatureStats(actor).isDead())
return true;

View file

@ -31,7 +31,7 @@ namespace MWMechanics
AiPursue(const ESM::AiSequence::AiPursue* pursue);
virtual AiPursue *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration);
virtual int getTypeId() const;
MWWorld::Ptr getTarget() const;

View file

@ -2,6 +2,7 @@
#include "aisequence.hpp"
#include "aipackage.hpp"
#include "aistate.hpp"
#include "aiwander.hpp"
#include "aiescort.hpp"
@ -146,7 +147,7 @@ bool AiSequence::isPackageDone() const
return mDone;
}
void AiSequence::execute (const MWWorld::Ptr& actor,float duration)
void AiSequence::execute (const MWWorld::Ptr& actor, AiState& state,float duration)
{
if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr()
&& !actor.getClass().getCreatureStats(actor).getKnockedDown())
@ -208,7 +209,7 @@ void AiSequence::execute (const MWWorld::Ptr& actor,float duration)
}
}
if (package->execute (actor,duration))
if (package->execute (actor,state,duration))
{
// To account for the rare case where AiPackage::execute() queued another AI package
// (e.g. AiPursue executing a dialogue script that uses startCombat)

View file

@ -4,6 +4,7 @@
#include <list>
#include <components/esm/loadnpc.hpp>
//#include "aistate.hpp"
namespace MWWorld
{
@ -18,9 +19,15 @@ namespace ESM
}
}
namespace MWMechanics
{
class AiPackage;
template< class Base > class DerivedClassStorage;
class AiTemporaryBase;
typedef DerivedClassStorage<AiTemporaryBase> AiState;
/// \brief Sequence of AI-packages for a single actor
/** The top-most AI package is run each frame. When completed, it is removed from the stack. **/
@ -88,7 +95,7 @@ namespace MWMechanics
void stopPursuit();
/// Execute current package, switching if needed.
void execute (const MWWorld::Ptr& actor,float duration);
void execute (const MWWorld::Ptr& actor, MWMechanics::AiState& state, float duration);
/// Remove all packages.
void clear();

View file

@ -0,0 +1,136 @@
#ifndef AISTATE_H
#define AISTATE_H
#include <typeinfo>
#include <stdexcept>
// c++11 replacement
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_base_of.hpp>
namespace MWMechanics
{
/** \brief stores one object of any class derived from Base.
* Requesting a certain dereived class via get() either returns
* the stored object if it has the correct type or otherwise replaces
* it with an object of the requested type.
*/
template< class Base >
class DerivedClassStorage
{
private:
Base* mStorage;
// assert that Derived is derived from Base.
template< class Derived >
void assert_derived()
{
// c++11:
// static_assert( std::is_base_of<Base,Derived> , "DerivedClassStorage may only store derived classes" );
// boost:
BOOST_STATIC_ASSERT((boost::is_base_of<Base,Derived>::value));//,"DerivedClassStorage may only store derived classes");
}
//if needed you have to provide a clone member function
DerivedClassStorage( const DerivedClassStorage& other );
DerivedClassStorage& operator=( const DerivedClassStorage& );
public:
/// \brief returns reference to stored object or deletes it and creates a fitting
template< class Derived >
Derived& get()
{
assert_derived<Derived>();
Derived* result = dynamic_cast<Derived*>(mStorage);
if(!result)
{
if(mStorage)
delete mStorage;
mStorage = result = new Derived();
}
//return a reference to the (new allocated) object
return *result;
}
template< class Derived >
void store( const Derived& payload )
{
assert_derived<Derived>();
if(mStorage)
delete mStorage;
mStorage = new Derived(payload);
}
/// \brief takes ownership of the passed object
template< class Derived >
void moveIn( Derived* p )
{
assert_derived<Derived>();
if(mStorage)
delete mStorage;
mStorage = p;
}
/// \brief gives away ownership of object. Throws exception if storage does not contain Derived or is empty.
template< class Derived >
Derived* moveOut()
{
assert_derived<Derived>();
if(!mStorage)
throw std::runtime_error("Cant move out: empty storage.");
Derived* result = dynamic_cast<Derived*>(mStorage);
if(!mStorage)
throw std::runtime_error("Cant move out: wrong type requested.");
return result;
}
bool empty() const
{
return mStorage == NULL;
}
const std::type_info& getType() const
{
return typeid(mStorage);
}
DerivedClassStorage():mStorage(NULL){};
~DerivedClassStorage()
{
if(mStorage)
delete mStorage;
};
};
/// \brief base class for the temporary storage of AiPackages.
/**
* Each AI package with temporary values needs a AiPackageStorage class
* which is derived from AiTemporaryBase. The CharacterController holds a container
* AiState where one of these storages can be stored at a time.
* The execute(...) member function takes this container as an argument.
* */
struct AiTemporaryBase
{
virtual ~AiTemporaryBase(){};
};
/// \brief Container for AI package status.
typedef DerivedClassStorage<AiTemporaryBase> AiState;
}
#endif // AISTATE_H

View file

@ -17,7 +17,7 @@
namespace MWMechanics
{
AiTravel::AiTravel(float x, float y, float z)
: mX(x),mY(y),mZ(z),mPathFinder()
: mX(x),mY(y),mZ(z)
, mCellX(std::numeric_limits<int>::max())
, mCellY(std::numeric_limits<int>::max())
{
@ -25,7 +25,6 @@ namespace MWMechanics
AiTravel::AiTravel(const ESM::AiSequence::AiTravel *travel)
: mX(travel->mData.mX), mY(travel->mData.mY), mZ(travel->mData.mZ)
, mPathFinder()
, mCellX(std::numeric_limits<int>::max())
, mCellY(std::numeric_limits<int>::max())
{
@ -37,7 +36,7 @@ namespace MWMechanics
return new AiTravel(*this);
}
bool AiTravel::execute (const MWWorld::Ptr& actor,float duration)
bool AiTravel::execute (const MWWorld::Ptr& actor, AiState& state, float duration)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::Position pos = actor.getRefData().getPosition();

View file

@ -27,7 +27,7 @@ namespace MWMechanics
virtual AiTravel *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration);
virtual int getTypeId() const;
@ -39,7 +39,6 @@ namespace MWMechanics
int mCellX;
int mCellY;
PathFinder mPathFinder;
};
}

View file

@ -18,6 +18,8 @@
#include "steering.hpp"
#include "movement.hpp"
namespace MWMechanics
{
static const int COUNT_BEFORE_RESET = 200; // TODO: maybe no longer needed
@ -26,6 +28,55 @@ namespace MWMechanics
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;
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
struct AiWanderStorage : AiTemporaryBase
{
// the z rotation angle (degrees) we want to reach
// used every frame when mRotate is true
Ogre::Radian mTargetAngle;
bool mRotate;
float mReaction; // update some actions infrequently
AiWander::GreetingState mSaidGreeting;
int mGreetingTimer;
// Cached current cell location
int mCellX;
int mCellY;
// Cell location multiplied by ESM::Land::REAL_SIZE
float mXCell;
float mYCell;
const MWWorld::CellStore* mCell; // for detecting cell change
// AiWander states
bool mChooseAction;
bool mIdleNow;
bool mMoveNow;
bool mWalking;
unsigned short mPlayedIdle;
AiWanderStorage():
mTargetAngle(0),
mRotate(false),
mReaction(0),
mSaidGreeting(AiWander::Greet_None),
mGreetingTimer(0),
mCellX(std::numeric_limits<int>::max()),
mCellY(std::numeric_limits<int>::max()),
mXCell(0),
mYCell(0),
mCell(NULL),
mChooseAction(true),
mIdleNow(false),
mMoveNow(false),
mWalking(false),
mPlayedIdle(0)
{};
};
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat)
{
@ -37,19 +88,11 @@ namespace MWMechanics
{
// NOTE: mDistance and mDuration must be set already
mCellX = std::numeric_limits<int>::max();
mCellY = std::numeric_limits<int>::max();
mXCell = 0;
mYCell = 0;
mCell = NULL;
mStuckCount = 0;// TODO: maybe no longer needed
mDoorCheckDuration = 0;
mTrimCurrentNode = false;
mReaction = 0;
mRotate = false;
mTargetAngle = 0;
mSaidGreeting = Greet_None;
mGreetingTimer = 0;
mHasReturnPosition = false;
mReturnPosition = Ogre::Vector3(0,0,0);
@ -61,13 +104,9 @@ namespace MWMechanics
mTimeOfDay = 0;
mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp();
mPlayedIdle = 0;
mStoredAvailableNodes = false;
mChooseAction = true;
mIdleNow = false;
mMoveNow = false;
mWalking = false;
}
AiPackage * MWMechanics::AiWander::clone() const
@ -125,53 +164,65 @@ namespace MWMechanics
* actors will enter combat (i.e. no longer wandering) and different pathfinding
* will kick in.
*/
bool AiWander::execute (const MWWorld::Ptr& actor,float duration)
bool AiWander::execute (const MWWorld::Ptr& actor, AiState& state, float duration)
{
// get or create temporary storage
AiWanderStorage& storage = state.get<AiWanderStorage>();
const MWWorld::CellStore*& currentCell = storage.mCell;
MWMechanics::CreatureStats& cStats = actor.getClass().getCreatureStats(actor);
if(cStats.isDead() || cStats.getHealth().getCurrent() <= 0)
return true; // Don't bother with dead actors
bool cellChange = mCell && (actor.getCell() != mCell);
if(!mCell || cellChange)
bool cellChange = currentCell && (actor.getCell() != currentCell);
if(!currentCell || cellChange)
{
mCell = actor.getCell();
currentCell = actor.getCell();
mStoredAvailableNodes = false; // prob. not needed since mDistance = 0
}
const ESM::Cell *cell = mCell->getCell();
const ESM::Cell *cell = currentCell->getCell();
cStats.setDrawState(DrawState_Nothing);
cStats.setMovementFlag(CreatureStats::Flag_Run, false);
ESM::Position pos = actor.getRefData().getPosition();
bool& idleNow = storage.mIdleNow;
bool& moveNow = storage.mMoveNow;
bool& walking = storage.mWalking;
// 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
mIdleNow && // but is in idle
!mWalking && // FIXME: some actors are idle while walking
idleNow && // but is in idle
!walking && // FIXME: some actors are idle while walking
proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6*1.6)) // NOTE: checks interior cells only
{
mIdleNow = false;
mMoveNow = true;
idleNow = false;
moveNow = true;
mTrimCurrentNode = false; // just in case
}
}
// Are we there yet?
if(mWalking &&
bool& chooseAction = storage.mChooseAction;
if(walking &&
mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
{
stopWalking(actor);
mMoveNow = false;
mWalking = false;
mChooseAction = true;
moveNow = false;
walking = false;
chooseAction = true;
mHasReturnPosition = false;
}
if(mWalking) // have not yet reached the destination
if(walking) // have not yet reached the destination
{
// turn towards the next point in mPath
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
@ -188,8 +239,8 @@ namespace MWMechanics
trimAllowedNodes(mAllowedNodes, mPathFinder);
mObstacleCheck.clear();
mPathFinder.clearPath();
mWalking = false;
mMoveNow = true;
walking = false;
moveNow = true;
}
else // probably walking into another NPC
{
@ -210,29 +261,33 @@ namespace MWMechanics
mObstacleCheck.clear();
stopWalking(actor);
mMoveNow = false;
mWalking = false;
mChooseAction = true;
moveNow = false;
walking = false;
chooseAction = true;
}
//#endif
}
if (mRotate)
Ogre::Radian& targetAngle = storage.mTargetAngle;
bool& rotate = storage.mRotate;
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, Ogre::Degree(mTargetAngle), Ogre::Degree(5)))
mRotate = false;
if (zTurn(actor, targetAngle, Ogre::Degree(5)))
rotate = false;
}
mReaction += duration;
if(mReaction < REACTION_INTERVAL)
float& lastReaction = storage.mReaction;
lastReaction += duration;
if(lastReaction < REACTION_INTERVAL)
{
return false;
}
else
mReaction = 0;
lastReaction = 0;
// NOTE: everything below get updated every REACTION_INTERVAL seconds
@ -263,6 +318,12 @@ namespace MWMechanics
}
}
int& cachedCellX = storage.mCellX;
int& cachedCellY = storage.mCellY;
float& cachedCellXposition = storage.mXCell;
float& cachedCellYposition = storage.mYCell;
// Initialization to discover & store allowed node points for this actor.
if(!mStoredAvailableNodes)
{
@ -271,8 +332,8 @@ namespace MWMechanics
pathgrid = world->getStore().get<ESM::Pathgrid>().search(*cell);
// cache the current cell location
mCellX = cell->mData.mX;
mCellY = cell->mData.mY;
cachedCellX = cell->mData.mX;
cachedCellY = cell->mData.mY;
// If there is no path this actor doesn't go anywhere. See:
// https://forum.openmw.org/viewtopic.php?t=1556
@ -286,12 +347,12 @@ namespace MWMechanics
// destinations within the allowed set of pathgrid points (nodes).
if(mDistance)
{
mXCell = 0;
mYCell = 0;
cachedCellXposition = 0;
cachedCellYposition = 0;
if(cell->isExterior())
{
mXCell = mCellX * ESM::Land::REAL_SIZE;
mYCell = mCellY * ESM::Land::REAL_SIZE;
cachedCellXposition = cachedCellX * ESM::Land::REAL_SIZE;
cachedCellYposition = cachedCellY * ESM::Land::REAL_SIZE;
}
// FIXME: There might be a bug here. The allowed node points are
@ -301,8 +362,8 @@ namespace MWMechanics
//
// convert npcPos to local (i.e. cell) co-ordinates
Ogre::Vector3 npcPos(pos.pos);
npcPos[0] = npcPos[0] - mXCell;
npcPos[1] = npcPos[1] - mYCell;
npcPos[0] = npcPos[0] - cachedCellXposition;
npcPos[1] = npcPos[1] - cachedCellYposition;
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
// NOTE: mPoints and mAllowedNodes are in local co-ordinates
@ -347,8 +408,8 @@ namespace MWMechanics
mHasReturnPosition = false;
if (mDistance == 0 && mHasReturnPosition && Ogre::Vector3(pos.pos).squaredDistance(mReturnPosition) > 20*20)
{
mChooseAction = false;
mIdleNow = false;
chooseAction = false;
idleNow = false;
if (!mPathFinder.isPathConstructed())
{
@ -370,30 +431,32 @@ namespace MWMechanics
if(mPathFinder.isPathConstructed())
{
mMoveNow = false;
mWalking = true;
moveNow = false;
walking = true;
}
}
}
if(mChooseAction)
AiWander::GreetingState& greetingState = storage.mSaidGreeting;
short unsigned& playedIdle = storage.mPlayedIdle;
if(chooseAction)
{
mPlayedIdle = 0;
getRandomIdle(); // NOTE: sets mPlayedIdle with a random selection
playedIdle = 0;
getRandomIdle(playedIdle); // NOTE: sets mPlayedIdle with a random selection
if(!mPlayedIdle && mDistance)
if(!playedIdle && mDistance)
{
mChooseAction = false;
mMoveNow = true;
chooseAction = false;
moveNow = true;
}
else
{
// Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander:
MWWorld::TimeStamp currentTime = world->getTimeStamp();
mStartTime = currentTime;
playIdle(actor, mPlayedIdle);
mChooseAction = false;
mIdleNow = true;
playIdle(actor, playedIdle);
chooseAction = false;
idleNow = true;
// Play idle voiced dialogue entries randomly
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified();
@ -417,7 +480,7 @@ namespace MWMechanics
}
// Allow interrupting a walking actor to trigger a greeting
if(mIdleNow || mWalking)
if(idleNow || walking)
{
// Play a random voice greeting if the player gets too close
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified();
@ -432,77 +495,76 @@ namespace MWMechanics
Ogre::Vector3 actorPos(actor.getRefData().getPosition().pos);
float playerDistSqr = playerPos.squaredDistance(actorPos);
if (mSaidGreeting == Greet_None)
int& greetingTimer = storage.mGreetingTimer;
if (greetingState == Greet_None)
{
if ((playerDistSqr <= helloDistance*helloDistance) &&
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
mGreetingTimer++;
greetingTimer++;
if (mGreetingTimer >= GREETING_SHOULD_START)
if (greetingTimer >= GREETING_SHOULD_START)
{
mSaidGreeting = Greet_InProgress;
greetingState = Greet_InProgress;
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
mGreetingTimer = 0;
greetingTimer = 0;
}
}
if(mSaidGreeting == Greet_InProgress)
if(greetingState == Greet_InProgress)
{
mGreetingTimer++;
greetingTimer++;
if(mWalking)
if(walking)
{
stopWalking(actor);
mMoveNow = false;
mWalking = false;
moveNow = false;
walking = false;
mObstacleCheck.clear();
mIdleNow = true;
getRandomIdle();
idleNow = true;
getRandomIdle(playedIdle);
}
if(!mRotate)
if(!rotate)
{
Ogre::Vector3 dir = playerPos - actorPos;
float length = dir.length();
float faceAngle = Ogre::Radian(Ogre::Math::ACos(dir.y / length) *
((Ogre::Math::ASin(dir.x / length).valueRadians()>0)?1.0:-1.0)).valueDegrees();
float actorAngle = actor.getRefData().getBaseNode()->getOrientation().getRoll().valueDegrees();
Ogre::Radian faceAngle = Ogre::Math::ATan2(dir.x,dir.y);
Ogre::Radian actorAngle = actor.getRefData().getBaseNode()->getOrientation().getRoll();
// an attempt at reducing the turning animation glitch
if(abs(abs(faceAngle) - abs(actorAngle)) >= 5) // TODO: is there a better way?
if( Ogre::Math::Abs( faceAngle - actorAngle ) >= Ogre::Degree(5) ) // TODO: is there a better way?
{
mTargetAngle = faceAngle;
mRotate = true;
targetAngle = faceAngle;
rotate = true;
}
}
if (mGreetingTimer >= GREETING_SHOULD_END)
if (greetingTimer >= GREETING_SHOULD_END)
{
mSaidGreeting = Greet_Done;
mGreetingTimer = 0;
greetingState = Greet_Done;
greetingTimer = 0;
}
}
if (mSaidGreeting == MWMechanics::AiWander::Greet_Done)
if (greetingState == MWMechanics::AiWander::Greet_Done)
{
static float fGreetDistanceReset = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("fGreetDistanceReset")->getFloat();
if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset)
mSaidGreeting = Greet_None;
greetingState = Greet_None;
}
// Check if idle animation finished
if(!checkIdle(actor, mPlayedIdle) && (playerDistSqr > helloDistance*helloDistance || mSaidGreeting == MWMechanics::AiWander::Greet_Done))
if(!checkIdle(actor, playedIdle) && (playerDistSqr > helloDistance*helloDistance || greetingState == MWMechanics::AiWander::Greet_Done))
{
mPlayedIdle = 0;
mIdleNow = false;
mChooseAction = true;
playedIdle = 0;
idleNow = false;
chooseAction = true;
}
}
if(mMoveNow && mDistance)
if(moveNow && mDistance)
{
// Construct a new path if there isn't one
if(!mPathFinder.isPathConstructed())
@ -516,8 +578,8 @@ namespace MWMechanics
// convert dest to use world co-ordinates
ESM::Pathgrid::Point dest;
dest.mX = destNodePos[0] + mXCell;
dest.mY = destNodePos[1] + mYCell;
dest.mX = destNodePos[0] + cachedCellXposition;
dest.mY = destNodePos[1] + cachedCellYposition;
dest.mZ = destNodePos[2];
// actor position is already in world co-ordinates
@ -548,8 +610,8 @@ namespace MWMechanics
mAllowedNodes.push_back(mCurrentNode);
mCurrentNode = temp;
mMoveNow = false;
mWalking = true;
moveNow = false;
walking = true;
}
// Choose a different node and delete this one from possible nodes because it is uncreachable:
else
@ -648,7 +710,7 @@ namespace MWMechanics
}
}
void AiWander::getRandomIdle()
void AiWander::getRandomIdle(short unsigned& playedIdle)
{
unsigned short idleRoll = 0;
@ -661,7 +723,7 @@ namespace MWMechanics
unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / fIdleChanceMultiplier));
if(randSelect < idleChance && randSelect > idleRoll)
{
mPlayedIdle = counter+2;
playedIdle = counter+2;
idleRoll = randSelect;
}
}

View file

@ -12,6 +12,9 @@
#include "../mwworld/timestamp.hpp"
#include "aistate.hpp"
namespace ESM
{
namespace AiSequence
@ -21,7 +24,9 @@ namespace ESM
}
namespace MWMechanics
{
{
/// \brief Causes the Actor to wander within a specified range
class AiWander : public AiPackage
{
@ -36,12 +41,11 @@ namespace MWMechanics
AiWander (const ESM::AiSequence::AiWander* wander);
// NOTE: mDistance and mDuration must be set already
void init();
virtual AiPackage *clone() const;
virtual bool execute (const MWWorld::Ptr& actor,float duration);
virtual bool execute (const MWWorld::Ptr& actor, AiState& state, float duration);
virtual int getTypeId() const;
@ -51,11 +55,20 @@ namespace MWMechanics
virtual void writeState(ESM::AiSequence::AiSequence &sequence) const;
enum GreetingState {
Greet_None,
Greet_InProgress,
Greet_Done
};
private:
// NOTE: mDistance and mDuration must be set already
void init();
void stopWalking(const MWWorld::Ptr& actor);
void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
void getRandomIdle();
void getRandomIdle(unsigned short& playedIdle);
int mDistance; // how far the actor can wander from the spawn point
int mDuration;
@ -63,36 +76,21 @@ namespace MWMechanics
std::vector<unsigned char> mIdle;
bool mRepeat;
enum GreetingState {
Greet_None,
Greet_InProgress,
Greet_Done
};
GreetingState mSaidGreeting;
int mGreetingTimer;
bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position,
// if we had the actor in the AiWander constructor...
Ogre::Vector3 mReturnPosition;
// Cached current cell location
int mCellX;
int mCellY;
// Cell location multiplied by ESM::Land::REAL_SIZE
float mXCell;
float mYCell;
const MWWorld::CellStore* mCell; // for detecting cell change
// if false triggers calculating allowed nodes based on mDistance
bool mStoredAvailableNodes;
// AiWander states
bool mChooseAction;
bool mIdleNow;
bool mMoveNow;
bool mWalking;
unsigned short mPlayedIdle;
MWWorld::TimeStamp mStartTime;
@ -103,18 +101,16 @@ namespace MWMechanics
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes,
const PathFinder& pathfinder);
PathFinder mPathFinder;
// PathFinder mPathFinder;
ObstacleCheck mObstacleCheck;
// ObstacleCheck mObstacleCheck;
float mDoorCheckDuration;
int mStuckCount;
// the z rotation angle (degrees) we want to reach
// used every frame when mRotate is true
float mTargetAngle;
bool mRotate;
float mReaction; // update some actions infrequently
};
}
#endif

View file

@ -6,6 +6,7 @@
#include <components/esm/loadmgef.hpp>
#include "../mwworld/ptr.hpp"
#include "aistate.hpp"
namespace MWWorld
{
@ -138,6 +139,9 @@ class CharacterController
{
MWWorld::Ptr mPtr;
MWRender::Animation *mAnimation;
//
AiState mAiState;
typedef std::deque<std::pair<std::string,size_t> > AnimationQueue;
AnimationQueue mAnimQueue;
@ -218,6 +222,8 @@ public:
{ return mDeathState != CharState_None; }
void forceStateUpdate();
AiState& getAiState() { return mAiState; }
};
void getWeaponGroup(WeaponType weaptype, std::string &group);

View file

@ -278,9 +278,8 @@ namespace MWMechanics
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
float directionX = nextPoint.mX - x;
float directionY = nextPoint.mY - y;
float directionResult = sqrt(directionX * directionX + directionY * directionY);
return Ogre::Radian(Ogre::Math::ACos(directionY / directionResult) * sgn(Ogre::Math::ASin(directionX / directionResult))).valueDegrees();
return Ogre::Math::ATan2(directionX,directionY).valueDegrees();
}
bool PathFinder::checkWaypoint(float x, float y, float z)