Changed implementations of aifollow/pursue/activate slightly, added ability for NPCs to go through unlocked doors (They even try locked ones), and step back from opening doors (Although it still needs some work)

Notes - When the door hits them while it's about to finish closing they will try to walk through the door.
      - Considerably more works is needed in making the NPC work out troublesome areas where they get stuck
This commit is contained in:
Thomas 2014-05-13 03:58:32 -04:00
parent 2c74ea381e
commit cbfa282f8d
14 changed files with 149 additions and 128 deletions

View file

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

View file

@ -390,10 +390,14 @@ namespace MWBase
virtual void setupPlayer() = 0;
virtual void renderPlayer() = 0;
/// if activated, should this door be opened or closed?
virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door) = 0;
///< if activated, should this door be opened or closed?
/// activate (open or close) an non-teleport door
virtual void activateDoor(const MWWorld::Ptr& door) = 0;
///< activate (open or close) an non-teleport door
/// Is door currently opening/closing?
virtual bool getIsMovingDoor(const MWWorld::Ptr& door) = 0;
virtual bool getPlayerStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if the player is standing on \a object
virtual bool getActorStandingOn (const MWWorld::Ptr& object) = 0; ///< @return true if any actor is standing on \a object

View file

@ -19,83 +19,27 @@ MWMechanics::AiActivate *MWMechanics::AiActivate::clone() const
return new AiActivate(*this);
}
bool MWMechanics::AiActivate::execute (const MWWorld::Ptr& actor,float duration)
{
MWBase::World *world = MWBase::Environment::get().getWorld();
ESM::Position pos = actor.getRefData().getPosition();
Movement &movement = actor.getClass().getMovementSettings(actor);
const ESM::Cell *cell = actor.getCell()->getCell();
MWWorld::Ptr player = world->getPlayerPtr();
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
{
int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) >
sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
if(cell->mData.mY != player.getCell()->getCell()->mData.mY)
{
int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
//check if actor is near the border of an inactive cell. If so, stop walking.
if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) >
sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
{
movement.mPosition[1] = 0;
return false;
}
}
MWWorld::Ptr target = world->searchPtr(mObjectId,false);
if(target == MWWorld::Ptr()) return true;
ESM::Position targetPos = target.getRefData().getPosition();
bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;
if(!mPathFinder.isPathConstructed() || cellChange)
{
mCellX = cell->mData.mX;
mCellY = cell->mData.mY;
ESM::Pathgrid::Point dest;
dest.mX = targetPos.pos[0];
dest.mY = targetPos.pos[1];
dest.mZ = targetPos.pos[2];
ESM::Pathgrid::Point start;
start.mX = pos.pos[0];
start.mY = pos.pos[1];
start.mZ = pos.pos[2];
mPathFinder.buildPath(start, dest, actor.getCell(), true);
}
if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+
(pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+
(pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 200*200)
{
movement.mPosition[1] = 0;
MWWorld::Ptr target = world->getPtr(mObjectId,false);
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
return true;
}
if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
{
movement.mPosition[1] = 0;
MWWorld::Ptr target = world->getPtr(mObjectId,false);
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
return true;
}
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
zTurn(actor, Ogre::Degree(zAngle));
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
movement.mPosition[1] = 1;
{
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
if(target == MWWorld::Ptr())
return true; //Target doesn't exist
//Set the target desition from the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 200 || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) { //Stop when you get close
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false);
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); //Arrest player
return true;
}
else {
pathTo(actor, dest, duration); //Go to the destination
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
}
return false;
}

View file

@ -8,7 +8,8 @@
namespace MWMechanics
{
/// \brief Causes actor to walk to activatable object and activate it
/// \brief Causes actor to walk to activatable object and activate it
/** Will actiavte when close to object or path grid complete **/
class AiActivate : public AiPackage
{
public:
@ -21,8 +22,6 @@ namespace MWMechanics
private:
std::string mObjectId;
PathFinder mPathFinder;
int mCellX;
int mCellY;
};

View file

@ -12,23 +12,17 @@
#include "steering.hpp"
MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z)
: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId("")
: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(""), AiPackage()
{
mTimer = 0;
mStuckTimer = 0;
}
MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z)
: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId)
: mAlwaysFollow(false), mDuration(duration), mX(x), mY(y), mZ(z), mActorId(actorId), mCellId(cellId), AiPackage()
{
mTimer = 0;
mStuckTimer = 0;
}
MWMechanics::AiFollow::AiFollow(const std::string &actorId)
: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId("")
: mAlwaysFollow(true), mDuration(0), mX(0), mY(0), mZ(0), mActorId(actorId), mCellId(""), AiPackage()
{
mTimer = 0;
mStuckTimer = 0;
}
bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor,float duration)

View file

@ -1,5 +1,5 @@
#ifndef GAME_MWMECHANICS_AIFALLOW_H
#define GAME_MWMECHANICS_AIFALLOW_H
#ifndef GAME_MWMECHANICS_AIFOLLOW_H
#define GAME_MWMECHANICS_AIFOLLOW_H
#include "aipackage.hpp"
#include <string>

View file

@ -8,6 +8,7 @@
#include "../mwworld/cellstore.hpp"
#include "creaturestats.hpp"
#include "movement.hpp"
#include "../mwworld/action.hpp"
#include <OgreMath.h>
@ -15,13 +16,18 @@
MWMechanics::AiPackage::~AiPackage() {}
MWMechanics::AiPackage::AiPackage() : mLastDoorChecked(NULL), mTimer(0), mStuckTimer(0) {
}
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration)
{
//Update various Timers
mTimer = mTimer + duration; //Update timer
mStuckTimer = mStuckTimer + duration; //Update stuck timer
mTotalTime = mTotalTime + duration; //Update total time following
mTimer += duration; //Update timer
mStuckTimer += duration; //Update stuck timer
mTotalTime += duration; //Update total time following
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
@ -78,18 +84,45 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Po
//************************
/// Checks if you aren't moving; attempts to unstick you
//************************
if(mStuckTimer>0.5) //Checks every half of a second
if(mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2])) //Path finished?
return true;
else if(mStuckTimer>0.5) //Every half second see if we need to take action to avoid something
{
if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < 10) //NPC hasn't moved much is half a second, he's stuck
mPathFinder.buildPath(start, dest, actor.getCell(), true);
mStuckTimer = 0;
mStuckPos = pos;
/// TODO (tluppi#1#): Use ObstacleCheck here. Not working for some reason
//if(mObstacleCheck.check(actor, duration)) {
if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < 10) { //Actually stuck
// first check if we're walking into a door
MWWorld::LiveCellRef<ESM::Door>* door = getNearbyDoor(actor);
if(door != NULL) // NOTE: checks interior cells only
{
if(door->mRef.mTrap.empty() && mLastDoorChecked != door) { //Open the door if untrapped
door->mClass->activate(MWBase::Environment::get().getWorld()->getPtr(door->mRef.mRefID,false), actor).get()->execute(actor);
mLastDoorChecked = door;
}
}
else // probably walking into another NPC
{
// TODO: diagonal should have same animation as walk forward
// but doesn't seem to do that?
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f;
// change the angle a bit, too
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
}
/*else if(distance(start, mStuckPos.pos[0], mStuckPos.pos[1], mStuckPos.pos[2]) < 10) { //NPC hasn't moved much is half a second, he's stuck
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
}*/
}
else {
mStuckTimer = 0;
mStuckPos = pos;
mLastDoorChecked = NULL; //Resets it, in case he gets stuck behind the door again
}
}
else {
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
}
//Checks if the path isn't over, turn tomards the direction that you're going
if(!mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]))
{
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
}
zTurn(actor, Ogre::Degree(mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
}

View file

@ -4,6 +4,8 @@
#include "pathfinding.hpp"
#include "../../../components/esm/defs.hpp"
#include "obstacle.hpp"
namespace MWWorld
{
class Ptr;
@ -24,9 +26,13 @@ namespace MWMechanics
TypeIdFollow = 3,
TypeIdActivate = 4,
TypeIdCombat = 5,
TypeIdPursue = 6
TypeIdPursue = 6,
TypeIdAvoidDoor = 7
};
///Default constructor
AiPackage();
///Default Deconstructor
virtual ~AiPackage();
@ -50,10 +56,14 @@ namespace MWMechanics
bool pathTo(const MWWorld::Ptr& actor, ESM::Pathgrid::Point dest, float duration);
PathFinder mPathFinder;
ObstacleCheck mObstacleCheck;
float mDoorCheckDuration;
float mTimer;
float mStuckTimer;
float mTotalTime;
float mTotalTime;
MWWorld::LiveCellRef<ESM::Door>* mLastDoorChecked; //Used to ensure we don't try to CONSTANTLY open a door
ESM::Position mStuckPos;
};

View file

@ -31,16 +31,15 @@ bool MWMechanics::AiPursue::execute (const MWWorld::Ptr& actor, float duration)
//Set the target desition from the actor
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
pathTo(actor, dest, duration); //Go to the destination
if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) { //Stop when you get close
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr(mObjectId,false);
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor); //Arrest player
return true;
}
else
actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
else {
pathTo(actor, dest, duration); //Go to the destination
}
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run

View file

@ -9,7 +9,9 @@
namespace MWMechanics
{
/// \brief Makes the actor very closely follow the actor
/** Used for arresting players. Causes the actor to run to the pursued actor and activate them, to arrest them. **/
/** Used for arresting players. Causes the actor to run to the pursued actor and activate them, to arrest them.
Note that while very similar to AiActivate, it will ONLY activate when evry close to target (Not also when the
path is completed). **/
class AiPursue : public AiPackage
{
public:

View file

@ -19,11 +19,19 @@ namespace MWMechanics
// Limitation: there can be false detections, and does not test whether the
// actor is facing the door.
bool proximityToDoor(const MWWorld::Ptr& actor, float minSqr, bool closed)
{
if(getNearbyDoor(actor, minSqr, closed)!=NULL)
return true;
else
return false;
}
MWWorld::LiveCellRef<ESM::Door>* getNearbyDoor(const MWWorld::Ptr& actor, float minSqr, bool closed)
{
MWWorld::CellStore *cell = actor.getCell();
if(cell->getCell()->isExterior())
return false; // check interior cells only
return NULL; // check interior cells only
// Check all the doors in this cell
MWWorld::CellRefList<ESM::Door>& doors = cell->get<ESM::Door>();
@ -31,14 +39,14 @@ namespace MWMechanics
MWWorld::CellRefList<ESM::Door>::List::iterator it = refList.begin();
Ogre::Vector3 pos(actor.getRefData().getPosition().pos);
// TODO: How to check whether the actor is facing a door? Below code is for
// the player, perhaps it can be adapted.
/// TODO: How to check whether the actor is facing a door? Below code is for
/// the player, perhaps it can be adapted.
//MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject();
//if(!ptr.isEmpty())
//std::cout << "faced door " << ptr.getClass().getName(ptr) << std::endl;
// TODO: The in-game observation of rot[2] value seems to be the
// opposite of the code in World::activateDoor() ::confused::
/// TODO: The in-game observation of rot[2] value seems to be the
/// opposite of the code in World::activateDoor() ::confused::
for (; it != refList.end(); ++it)
{
MWWorld::LiveCellRef<ESM::Door>& ref = *it;
@ -46,10 +54,10 @@ namespace MWMechanics
if((closed && ref.mData.getLocalRotation().rot[2] == 0) ||
(!closed && ref.mData.getLocalRotation().rot[2] >= 1))
{
return true; // found, stop searching
return &ref; // found, stop searching
}
}
return false; // none found
return NULL; // none found
}
ObstacleCheck::ObstacleCheck():

View file

@ -1,6 +1,10 @@
#ifndef OPENMW_MECHANICS_OBSTACLE_H
#define OPENMW_MECHANICS_OBSTACLE_H
//#include "../mwbase/world.hpp"
//#include "../mwworld/class.hpp"
#include "../mwworld/cellstore.hpp"
namespace MWWorld
{
class Ptr;
@ -8,14 +12,20 @@ namespace MWWorld
namespace MWMechanics
{
// NOTE: determined empirically based on in-game behaviour
/// NOTE: determined empirically based on in-game behaviour
static const float MIN_DIST_TO_DOOR_SQUARED = 128*128;
// tests actor's proximity to a closed door by default
/// tests actor's proximity to a closed door by default
bool proximityToDoor(const MWWorld::Ptr& actor,
float minSqr = MIN_DIST_TO_DOOR_SQUARED,
bool closed = true);
/// Returns door pointer within range. No guarentee is given as too which one
/** \return Pointer to the door, or NULL if none exists **/
MWWorld::LiveCellRef<ESM::Door>* getNearbyDoor(const MWWorld::Ptr& actor,
float minSqr = MIN_DIST_TO_DOOR_SQUARED,
bool closed = true);
class ObstacleCheck
{
public:

View file

@ -30,6 +30,7 @@
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/levelledlist.hpp"
#include "../mwmechanics/combat.hpp"
#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
#include "../mwrender/sky.hpp"
#include "../mwrender/animation.hpp"
@ -1210,7 +1211,11 @@ namespace MWWorld
MWWorld::Ptr ptr = getPtrViaHandle(*cit);
if (MWWorld::Class::get(ptr).isActor())
{
// we collided with an actor, we need to undo the rotation
// Collided with actor, ask actor to try to avoid door
MWMechanics::AiSequence& seq = MWWorld::Class::get(ptr).getCreatureStats(ptr).getAiSequence();
if(seq.getTypeId() != MWMechanics::AiPackage::TypeIdAvoidDoor) //Only add it once
seq.stack(MWMechanics::AiAvoidDoor(it->first),ptr);
// we need to undo the rotation
localRotateObject(it->first, 0, 0, oldRot);
break;
}
@ -1853,6 +1858,16 @@ namespace MWWorld
return door.getRefData().getLocalRotation().rot[2] == 0;
}
bool World::getIsMovingDoor(const Ptr& door)
{
//This more expensive comparison is needed for some reason
// TODO (tluppi#1#): Figure out why comparing Ptr isn't working
for(std::map<MWWorld::Ptr, int>::iterator it = mDoorStates.begin(); it != mDoorStates.end(); it++)
if(it->first.getCellRef().mRefID == door.getCellRef().mRefID)
return true;
return false;
}
bool World::getPlayerStandingOn (const MWWorld::Ptr& object)
{
MWWorld::Ptr player = mPlayer->getPlayer();
@ -1919,7 +1934,7 @@ namespace MWWorld
out.push_back(searchPtrViaHandle(*it));
}
}
bool World::getLOS(const MWWorld::Ptr& npc,const MWWorld::Ptr& targetNpc)
{
if (!targetNpc.getRefData().isEnabled() || !npc.getRefData().isEnabled())

View file

@ -493,10 +493,13 @@ namespace MWWorld
virtual void setupPlayer();
virtual void renderPlayer();
/// if activated, should this door be opened or closed?
virtual bool getOpenOrCloseDoor(const MWWorld::Ptr& door);
///< if activated, should this door be opened or closed?
/// activate (open or close) an non-teleport door
virtual void activateDoor(const MWWorld::Ptr& door);
///< activate (open or close) an non-teleport door
virtual bool getIsMovingDoor(const MWWorld::Ptr& door);
virtual bool getPlayerStandingOn (const MWWorld::Ptr& object); ///< @return true if the player is standing on \a object
virtual bool getActorStandingOn (const MWWorld::Ptr& object); ///< @return true if any actor is standing on \a object