Improvements to smooth NPC steering

actorid
scrawl 11 years ago
parent 2b15b8b484
commit 39d86a9468

@ -74,7 +74,7 @@ add_openmw_dir (mwmechanics
mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects
drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow
aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting aiescort aiactivate aicombat repair enchanting pathfinding security spellsuccess spellcasting
disease pickpocket levelledlist combat disease pickpocket levelledlist combat steering
) )
add_openmw_dir (mwbase add_openmw_dir (mwbase

@ -574,8 +574,6 @@ namespace MWInput
double x = arg.xrel * mCameraSensitivity * (1.0f/256.f); double x = arg.xrel * mCameraSensitivity * (1.0f/256.f);
double y = arg.yrel * mCameraSensitivity * (1.0f/256.f) * (mInvertY ? -1 : 1) * mCameraYMultiplier; double y = arg.yrel * mCameraSensitivity * (1.0f/256.f) * (mInvertY ? -1 : 1) * mCameraYMultiplier;
float scale = MWBase::Environment::get().getFrameDuration();
if(scale <= 0.0f) scale = 1.0f;
float rot[3]; float rot[3];
rot[0] = -y; rot[0] = -y;
@ -585,8 +583,8 @@ namespace MWInput
// Only actually turn player when we're not in vanity mode // Only actually turn player when we're not in vanity mode
if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot)) if(!MWBase::Environment::get().getWorld()->vanityRotateCamera(rot))
{ {
mPlayer->yaw(x/scale); mPlayer->yaw(x);
mPlayer->pitch(-y/scale); mPlayer->pitch(-y);
} }
if (arg.zrel && mControlSwitch["playerviewswitch"]) //Check to make sure you are allowed to zoomout and there is a change if (arg.zrel && mControlSwitch["playerviewswitch"]) //Check to make sure you are allowed to zoomout and there is a change

@ -1,23 +1,22 @@
#include "aicombat.hpp" #include "aicombat.hpp"
#include "aifollow.hpp"
#include "movement.hpp" #include <OgreMath.h>
#include <OgreVector3.h>
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/timestamp.hpp" #include "../mwworld/timestamp.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/dialoguemanager.hpp"
#include "character.hpp"
#include "../mwworld/inventorystore.hpp"
#include "creaturestats.hpp"
#include "npcstats.hpp" #include "npcstats.hpp"
#include "steering.hpp"
#include <OgreMath.h> #include "movement.hpp"
#include <OgreVector3.h> #include "character.hpp" // fixme: for getActiveWeapon
namespace namespace
{ {
@ -43,7 +42,9 @@ namespace MWMechanics
mReadyToAttack(false), mReadyToAttack(false),
mStrike(false), mStrike(false),
mCombatMove(false), mCombatMove(false),
mMovement() mRotate(false),
mMovement(),
mTargetAngle(0)
{ {
} }
@ -68,10 +69,16 @@ namespace MWMechanics
mCombatMove = false; mCombatMove = false;
} }
} }
actor.getClass().getMovementSettings(actor) = mMovement; actor.getClass().getMovementSettings(actor) = mMovement;
if (mRotate)
{
if (zTurn(actor, Ogre::Degree(mTargetAngle)))
mRotate = false;
}
//actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mReadyToAttack);
mTimerAttack -= duration; mTimerAttack -= duration;
actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike); actor.getClass().getCreatureStats(actor).setAttackingOrSpell(mStrike);
@ -156,12 +163,7 @@ namespace MWMechanics
weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit) weapRange = 150; //TODO: use true attack range (the same problem in Creature::hit)
} }
//MWWorld::Class::get(actor).getCreatureStats(actor).setAttackingOrSpell(false);
ESM::Position pos = actor.getRefData().getPosition(); ESM::Position pos = actor.getRefData().getPosition();
float zAngle;
float rangeMelee; float rangeMelee;
float rangeCloseUp; float rangeCloseUp;
@ -189,12 +191,8 @@ namespace MWMechanics
//Melee and Close-up combat //Melee and Close-up combat
vDir.z = 0; vDir.z = 0;
float dirLen = vDir.length(); float dirLen = vDir.length();
zAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees(); mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / dirLen) * sgn(Ogre::Math::ASin(vDir.x / dirLen)) ).valueDegrees();
mRotate = true;
// TODO: use movement settings instead of rotating directly
MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
//MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
//bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget); //bool LOS = MWBase::Environment::get().getWorld()->getLOS(actor, mTarget);
if (mFollowTarget && distBetween > rangeMelee) if (mFollowTarget && distBetween > rangeMelee)
@ -237,12 +235,6 @@ namespace MWMechanics
else else
{ {
//target is at far distance: build path to target OR follow target (if previously actor had reached it once) //target is at far distance: build path to target OR follow target (if previously actor had reached it once)
/*
//apply when AIFOLLOW package implementation will be existent
if(mFollowTarget)
actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiFollow(mTarget));*/
mFollowTarget = false; mFollowTarget = false;
buildNewPath(actor); buildNewPath(actor);
@ -252,13 +244,10 @@ namespace MWMechanics
//try shortcut //try shortcut
if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget)) if(vDir.length() < mPathFinder.getDistToNext(pos.pos[0],pos.pos[1],pos.pos[2]) && MWBase::Environment::get().getWorld()->getLOS(actor, mTarget))
zAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees(); mTargetAngle = Ogre::Radian( Ogre::Math::ACos(vDir.y / vDir.length()) * sgn(Ogre::Math::ASin(vDir.x / vDir.length())) ).valueDegrees();
else else
zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); mTargetAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
mRotate = true;
// TODO: use movement settings instead of rotating directly
MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
//mMovement.mRotation[2] = 10*(Ogre::Degree(zAngle).valueRadians()-pos.rot[2]);
mMovement.mPosition[1] = 1; mMovement.mPosition[1] = 1;
mReadyToAttack = false; mReadyToAttack = false;
@ -294,6 +283,8 @@ namespace MWMechanics
} }
} }
actor.getClass().getMovementSettings(actor) = mMovement;
return false; return false;
} }

@ -29,16 +29,21 @@ namespace MWMechanics
private: private:
PathFinder mPathFinder; PathFinder mPathFinder;
//controls duration of the actual strike // controls duration of the actual strike
float mTimerAttack; float mTimerAttack;
float mTimerReact; float mTimerReact;
//controls duration of the sideway & forward moves // controls duration of the sideway & forward moves
//when mCombatMove is true // when mCombatMove is true
float mTimerCombatMove; float mTimerCombatMove;
// the z rotation angle (degrees) we want to reach
// used every frame when mRotate is true
float mTargetAngle;
bool mReadyToAttack, mStrike; bool mReadyToAttack, mStrike;
bool mFollowTarget; bool mFollowTarget;
bool mCombatMove; bool mCombatMove;
bool mRotate;
MWMechanics::Movement mMovement; MWMechanics::Movement mMovement;
MWWorld::Ptr mTarget; MWWorld::Ptr mTarget;

@ -8,6 +8,8 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"
#include "steering.hpp"
namespace namespace
{ {
float sgn(float a) float sgn(float a)
@ -33,7 +35,7 @@ namespace MWMechanics
{ {
mMaxDist = 470; mMaxDist = 470;
// The CS Help File states that if a duration is givin, the AI package will run for that long // The CS Help File states that if a duration is given, the AI package will run for that long
// BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location.
if(mX != 0 || mY != 0 || mZ != 0) if(mX != 0 || mY != 0 || mZ != 0)
mDuration = 0; mDuration = 0;
@ -52,7 +54,7 @@ namespace MWMechanics
{ {
mMaxDist = 470; mMaxDist = 470;
// The CS Help File states that if a duration is givin, the AI package will run for that long // The CS Help File states that if a duration is given, the AI package will run for that long
// BUT if a location is givin, it "trumps" the duration so it will simply escort to that location. // BUT if a location is givin, it "trumps" the duration so it will simply escort to that location.
if(mX != 0 || mY != 0 || mZ != 0) if(mX != 0 || mY != 0 || mZ != 0)
mDuration = 0; mDuration = 0;
@ -89,25 +91,23 @@ namespace MWMechanics
if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX) if(actor.getCell()->mCell->mData.mX != player.getCell()->mCell->mData.mX)
{ {
int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX); int sideX = sgn(actor.getCell()->mCell->mData.mX - player.getCell()->mCell->mData.mX);
// Check if actor is near the border of an inactive cell. If so, disable AiEscort. // Check if actor is near the border of an inactive cell. If so, pause walking.
// FIXME: This *should* pause the AiEscort package instead of terminating it.
if(sideX * (pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / if(sideX * (pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE /
2.0 - 200)) 2.0 - 200))
{ {
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
return true; return false;
} }
} }
if(actor.getCell()->mCell->mData.mY != player.getCell()->mCell->mData.mY) if(actor.getCell()->mCell->mData.mY != player.getCell()->mCell->mData.mY)
{ {
int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY); int sideY = sgn(actor.getCell()->mCell->mData.mY - player.getCell()->mCell->mData.mY);
// Check if actor is near the border of an inactive cell. If so, disable AiEscort. // Check if actor is near the border of an inactive cell. If so, pause walking.
// FIXME: This *should* pause the AiEscort package instead of terminating it.
if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE /
2.0 - 200)) 2.0 - 200))
{ {
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0; MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
return true; return false;
} }
} }
@ -151,8 +151,7 @@ namespace MWMechanics
if(distanceBetweenResult <= mMaxDist * mMaxDist) if(distanceBetweenResult <= mMaxDist * mMaxDist)
{ {
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
// TODO: use movement settings instead of rotating directly zTurn(actor, Ogre::Degree(zAngle));
MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false);
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
mMaxDist = 470; mMaxDist = 470;
} }

@ -6,15 +6,17 @@
#include "movement.hpp" #include "movement.hpp"
#include <OgreMath.h> #include <OgreMath.h>
#include "steering.hpp"
MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z)
: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0) : mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), mTimer(0), mStuckTimer(0)
{ {
} }
MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z)
: mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0) : mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), mTimer(0), mStuckTimer(0)
{ {
} }
bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration) bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
{ {
@ -45,14 +47,14 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
} }
} }
ESM::Pathgrid::Point dest; ESM::Pathgrid::Point dest;
dest.mX = target.getRefData().getPosition().pos[0]; dest.mX = target.getRefData().getPosition().pos[0];
dest.mY = target.getRefData().getPosition().pos[1]; dest.mY = target.getRefData().getPosition().pos[1];
dest.mZ = target.getRefData().getPosition().pos[2]; dest.mZ = target.getRefData().getPosition().pos[2];
ESM::Pathgrid::Point start; ESM::Pathgrid::Point start;
start.mX = pos.pos[0]; start.mX = pos.pos[0];
start.mY = pos.pos[1]; start.mY = pos.pos[1];
start.mZ = pos.pos[2]; start.mZ = pos.pos[2];
if(mPathFinder.getPath().empty()) if(mPathFinder.getPath().empty())
@ -88,18 +90,14 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)
if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]))
{ {
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
//MWBase::Environment::get().getWorld()->rotateObject(actor, 0, 0, zAngle, false); }
MWWorld::Class::get(actor).getMovementSettings(actor).mRotation[2] = 10*(Ogre::Degree(zAngle).valueRadians()-pos.rot[2]);
//std::cout << Ogre::Degree(zAngle).valueDegrees()-Ogre::Radian(actor.getRefData().getPosition().rot[2]).valueDegrees() << " "<< pos.rot[2] << " " << zAngle << "\n"; if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2])
//MWWorld::Class::get(actor).get < 100*100)
} actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
if((dest.mX - pos.pos[0])*(dest.mX - pos.pos[0])+(dest.mY - pos.pos[1])*(dest.mY - pos.pos[1])+(dest.mZ - pos.pos[2])*(dest.mZ - pos.pos[2])
< 100*100)
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
else else
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
return false; return false;
} }
@ -109,12 +107,12 @@ std::string MWMechanics::AiFollow::getFollowedActor()
return mActorId; return mActorId;
} }
MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const
{ {
return new AiFollow(*this); return new AiFollow(*this);
} }
int MWMechanics::AiFollow::getTypeId() const int MWMechanics::AiFollow::getTypeId() const
{ {
return TypeIdFollow; return TypeIdFollow;
} }

@ -1,11 +1,12 @@
#include "aitravel.hpp" #include "aitravel.hpp"
#include "movement.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "steering.hpp"
#include "movement.hpp"
namespace namespace
{ {
float sgn(float a) float sgn(float a)
@ -86,9 +87,7 @@ namespace MWMechanics
return true; return true;
} }
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]); zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
// TODO: use movement settings instead of rotating directly
world->rotateObject(actor, 0, 0, zAngle, false);
movement.mPosition[1] = 1; movement.mPosition[1] = 1;
return false; return false;

@ -11,6 +11,8 @@
#include "creaturestats.hpp" #include "creaturestats.hpp"
#include <OgreVector3.h> #include <OgreVector3.h>
#include "steering.hpp"
namespace namespace
{ {
float sgn(float a) float sgn(float a)
@ -282,11 +284,6 @@ namespace MWMechanics
if(mWalking) if(mWalking)
{ {
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
// TODO: use movement settings instead of rotating directly
world->rotateObject(actor, 0, 0, zAngle, false);
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
{ {
stopWalking(actor); stopWalking(actor);
@ -294,6 +291,12 @@ namespace MWMechanics
mWalking = false; mWalking = false;
mChooseAction = true; mChooseAction = true;
} }
else
{
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
}
} }
return false; return false;

@ -1111,9 +1111,9 @@ void CharacterController::update(float duration)
if (!mSkipAnim) if (!mSkipAnim)
{ {
rot *= Ogre::Math::RadiansToDegrees(1.0f);
if(mHitState != CharState_KnockDown) if(mHitState != CharState_KnockDown)
{ {
rot *= duration * Ogre::Math::RadiansToDegrees(1.0f);
world->rotateObject(mPtr, rot.x, rot.y, rot.z, true); world->rotateObject(mPtr, rot.x, rot.y, rot.z, true);
} }
else //avoid z-rotating for knockdown else //avoid z-rotating for knockdown

@ -3,7 +3,6 @@
#include <components/esm/loadpgrd.hpp> #include <components/esm/loadpgrd.hpp>
#include <list> #include <list>
#include <boost/graph/adjacency_list.hpp>
namespace MWWorld namespace MWWorld
{ {
@ -26,8 +25,10 @@ namespace MWMechanics
bool checkPathCompleted(float x, float y, float z); bool checkPathCompleted(float x, float y, float z);
///< \Returns true if the last point of the path has been reached. ///< \Returns true if the last point of the path has been reached.
bool checkWaypoint(float x, float y, float z); bool checkWaypoint(float x, float y, float z);
///< \Returns true if a way point was reached ///< \Returns true if a way point was reached
float getZAngleToNext(float x, float y) const; float getZAngleToNext(float x, float y) const;
float getDistToNext(float x, float y, float z); float getDistToNext(float x, float y, float z);

@ -0,0 +1,43 @@
#include "steering.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwbase/environment.hpp"
#include "movement.hpp"
namespace MWMechanics
{
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle)
{
Ogre::Radian currentAngle (actor.getRefData().getPosition().rot[2]);
Ogre::Radian diff (targetAngle - currentAngle);
if (diff >= Ogre::Degree(180))
{
// Turning the other way would be a better idea
diff = diff-Ogre::Degree(360);
}
else if (diff <= Ogre::Degree(-180))
{
diff = Ogre::Degree(360)-diff;
}
Ogre::Radian absDiff = Ogre::Math::Abs(diff);
// The turning animation actually moves you slightly, so the angle will be wrong again.
// Use epsilon to prevent jerkiness.
const Ogre::Degree epsilon (0.5);
if (absDiff < epsilon)
return true;
// Max. speed of 10 radian per sec
Ogre::Radian limit = Ogre::Radian(10) * MWBase::Environment::get().getFrameDuration();
if (absDiff > limit)
diff = Ogre::Math::Sign(diff) * limit;
actor.getClass().getMovementSettings(actor).mRotation[2] = diff.valueRadians();
return false;
}
}

@ -0,0 +1,19 @@
#ifndef OPENMW_MECHANICS_STEERING_H
#include <OgreMath.h>
namespace MWWorld
{
class Ptr;
}
namespace MWMechanics
{
/// configure rotation settings for an actor to reach this target angle (eventually)
/// @return have we reached the target angle?
bool zTurn(const MWWorld::Ptr& actor, Ogre::Radian targetAngle);
}
#endif
Loading…
Cancel
Save