forked from teamnwah/openmw-tes3coop
Merge remote-tracking branch 'terrorfisch/aistate'
This commit is contained in:
commit
6beee95151
23 changed files with 537 additions and 307 deletions
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -199,30 +217,37 @@ namespace MWMechanics
|
|||
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;
|
||||
}
|
||||
|
||||
// start new attack
|
||||
if(mReadyToAttack)
|
||||
{
|
||||
if(mTimerAttack <= -attacksPeriod)
|
||||
{
|
||||
mAttack = true; // attack starts just now
|
||||
|
||||
if (!distantCombat) attackType = chooseBestAttack(weapon, mMovement);
|
||||
float& strength = storage.mStrength;
|
||||
// start new attack
|
||||
if(readyToAttack)
|
||||
{
|
||||
if(timerAttack <= -attacksPeriod)
|
||||
{
|
||||
attack = true; // attack starts just now
|
||||
|
||||
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())
|
||||
|
@ -412,12 +453,15 @@ namespace MWMechanics
|
|||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <list>
|
||||
|
||||
#include <components/esm/loadnpc.hpp>
|
||||
//#include "aistate.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
@ -18,10 +19,16 @@ 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. **/
|
||||
class AiSequence
|
||||
|
@ -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();
|
||||
|
|
136
apps/openmw/mwmechanics/aistate.hpp
Normal file
136
apps/openmw/mwmechanics/aistate.hpp
Normal 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
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
|
||||
#include "../mwworld/timestamp.hpp"
|
||||
|
||||
|
||||
#include "aistate.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
namespace AiSequence
|
||||
|
@ -22,6 +25,8 @@ 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
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <components/esm/loadmgef.hpp>
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "aistate.hpp"
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
|
@ -139,6 +140,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);
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue