|
|
|
@ -1,9 +1,54 @@
|
|
|
|
|
#include "aiwander.hpp"
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
#include "movement.hpp"
|
|
|
|
|
|
|
|
|
|
#include "../mwworld/class.hpp"
|
|
|
|
|
#include "../mwworld/player.hpp"
|
|
|
|
|
#include "../mwbase/world.hpp"
|
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
|
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
|
|
|
|
|
|
|
|
#include <OgreVector3.h>
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
float sgn(float a)
|
|
|
|
|
{
|
|
|
|
|
if(a > 0) return 1.0;
|
|
|
|
|
else return -1.0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MWMechanics::AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<int>& idle, bool repeat):
|
|
|
|
|
mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat)
|
|
|
|
|
{
|
|
|
|
|
std::cout << "AIWander: " << mDistance << " " << mDuration << " " << mTimeOfDay << " ";
|
|
|
|
|
for(unsigned short counter = 0; counter < mIdle.size(); counter++)
|
|
|
|
|
{
|
|
|
|
|
std::cout << mIdle[counter] << " ";
|
|
|
|
|
if(mIdle[counter] >= 127 || mIdle[counter] < 0)
|
|
|
|
|
mIdle[counter] = 0;
|
|
|
|
|
}
|
|
|
|
|
std::cout << mRepeat << std::endl;
|
|
|
|
|
|
|
|
|
|
if(mDistance < 0)
|
|
|
|
|
mDistance = 0;
|
|
|
|
|
if(mDuration < 0)
|
|
|
|
|
mDuration = 0;
|
|
|
|
|
if(mDuration == 0)
|
|
|
|
|
mTimeOfDay = 0;
|
|
|
|
|
|
|
|
|
|
srand(time(NULL));
|
|
|
|
|
mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp();
|
|
|
|
|
mChanceMultiplyer = 0.75;
|
|
|
|
|
mPlayedIdle = 0;
|
|
|
|
|
|
|
|
|
|
mStoredAvailableNodes = false;
|
|
|
|
|
mChooseAction = true;
|
|
|
|
|
mIdleNow = false;
|
|
|
|
|
mMoveNow = false;
|
|
|
|
|
mWalking = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MWMechanics::AiPackage * MWMechanics::AiWander::clone() const
|
|
|
|
@ -13,11 +58,259 @@ MWMechanics::AiPackage * MWMechanics::AiWander::clone() const
|
|
|
|
|
|
|
|
|
|
bool MWMechanics::AiWander::execute (const MWWorld::Ptr& actor)
|
|
|
|
|
{
|
|
|
|
|
// Return completed
|
|
|
|
|
return true;
|
|
|
|
|
if(mDuration)
|
|
|
|
|
{
|
|
|
|
|
// End package if duration is complete or mid-night hits:
|
|
|
|
|
MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp();
|
|
|
|
|
if(currentTime.getHour() >= mStartTime.getHour() + mDuration)
|
|
|
|
|
{
|
|
|
|
|
if(!mRepeat)
|
|
|
|
|
{
|
|
|
|
|
stopWalking(actor, mPathFinder);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
mStartTime = currentTime;
|
|
|
|
|
}
|
|
|
|
|
else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())
|
|
|
|
|
{
|
|
|
|
|
stopWalking(actor, mPathFinder);
|
|
|
|
|
|
|
|
|
|
if(!mRepeat)
|
|
|
|
|
{
|
|
|
|
|
stopWalking(actor, mPathFinder);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
mStartTime = currentTime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ESM::Position pos = actor.getRefData().getPosition();
|
|
|
|
|
|
|
|
|
|
if(!mStoredAvailableNodes)
|
|
|
|
|
{
|
|
|
|
|
mStoredAvailableNodes = true;
|
|
|
|
|
mPathgrid =
|
|
|
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*actor.getCell()->mCell);
|
|
|
|
|
|
|
|
|
|
mCellX = actor.getCell()->mCell->mData.mX;
|
|
|
|
|
mCellY = actor.getCell()->mCell->mData.mY;
|
|
|
|
|
|
|
|
|
|
if(mDistance != 0 && !mPathgrid->mPoints.empty())
|
|
|
|
|
{
|
|
|
|
|
// TODO: Limit selecting range in the Z axis.
|
|
|
|
|
mXCell = 0;
|
|
|
|
|
mYCell = 0;
|
|
|
|
|
if(actor.getCell()->mCell->isExterior())
|
|
|
|
|
{
|
|
|
|
|
mXCell = mCellX * ESM::Land::REAL_SIZE;
|
|
|
|
|
mYCell = mCellY * ESM::Land::REAL_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ogre::Vector3 npcPos(actor.getRefData().getPosition().pos);
|
|
|
|
|
npcPos[0] = npcPos[0] - mXCell;
|
|
|
|
|
npcPos[1] = npcPos[1] - mYCell;
|
|
|
|
|
|
|
|
|
|
for(unsigned int counter = 0; counter < mPathgrid->mPoints.size(); counter++)
|
|
|
|
|
{
|
|
|
|
|
Ogre::Vector3 nodePos(mPathgrid->mPoints[counter].mX, mPathgrid->mPoints[counter].mY, mPathgrid->mPoints[counter].mZ);
|
|
|
|
|
if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance)
|
|
|
|
|
mAllowedNodes.push_back(mPathgrid->mPoints[counter]);
|
|
|
|
|
}
|
|
|
|
|
if(!mAllowedNodes.empty())
|
|
|
|
|
{
|
|
|
|
|
Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ);
|
|
|
|
|
float closestNode = npcPos.squaredDistance(firstNodePos);
|
|
|
|
|
unsigned int index = 0;
|
|
|
|
|
for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++)
|
|
|
|
|
{
|
|
|
|
|
Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, mAllowedNodes[counterThree].mZ);
|
|
|
|
|
float tempDist = npcPos.squaredDistance(nodePos);
|
|
|
|
|
if(tempDist < closestNode)
|
|
|
|
|
index = counterThree;
|
|
|
|
|
}
|
|
|
|
|
mCurrentNode = mAllowedNodes[index];
|
|
|
|
|
mAllowedNodes.erase(mAllowedNodes.begin() + index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer();
|
|
|
|
|
bool cellChange = actor.getCell()->mCell->mData.mX != mCellX || actor.getCell()->mCell->mData.mY != mCellY;
|
|
|
|
|
|
|
|
|
|
if(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 AiWander.
|
|
|
|
|
// FIXME: This *should* pause the AiWander package instead of terminating it.
|
|
|
|
|
if(sideX*(pos.pos[0] - actor.getCell()->mCell->mData.mX * ESM::Land::REAL_SIZE) > sideX * (ESM::Land::REAL_SIZE / 2.0 - 200))
|
|
|
|
|
{
|
|
|
|
|
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(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 AiWander.
|
|
|
|
|
// FIXME: This *should* pause the AiWander package instead of terminating it.
|
|
|
|
|
if(sideY*(pos.pos[1] - actor.getCell()->mCell->mData.mY * ESM::Land::REAL_SIZE) > sideY * (ESM::Land::REAL_SIZE / 2.0 - 200))
|
|
|
|
|
{
|
|
|
|
|
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles.
|
|
|
|
|
if(cellChange || (mCellX != actor.getCell()->mCell->mData.mX || mCellY != actor.getCell()->mCell->mData.mY))
|
|
|
|
|
mDistance = 0;
|
|
|
|
|
|
|
|
|
|
if(mChooseAction)
|
|
|
|
|
{
|
|
|
|
|
mPlayedIdle = 0;
|
|
|
|
|
unsigned short idleRoll = 0;
|
|
|
|
|
|
|
|
|
|
for(unsigned int counter = 1; counter < mIdle.size(); counter++)
|
|
|
|
|
{
|
|
|
|
|
unsigned short idleChance = mChanceMultiplyer * mIdle[counter];
|
|
|
|
|
unsigned short randSelect = (int)(rand() / ((double)RAND_MAX + 1) * int(100 / mChanceMultiplyer));
|
|
|
|
|
if(randSelect < idleChance && randSelect > idleRoll)
|
|
|
|
|
{
|
|
|
|
|
mPlayedIdle = counter;
|
|
|
|
|
idleRoll = randSelect;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!mPlayedIdle && mDistance)
|
|
|
|
|
{
|
|
|
|
|
std::cout << "Walking!" << std::endl;
|
|
|
|
|
mChooseAction = false;
|
|
|
|
|
mMoveNow = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Play idle animation and recreate vanilla (broken?) behavior of resetting start time of AIWander:
|
|
|
|
|
std::cout << "Idling!" << std::endl;
|
|
|
|
|
MWWorld::TimeStamp currentTime = MWBase::Environment::get().getWorld()->getTimeStamp();
|
|
|
|
|
mStartTime = currentTime;
|
|
|
|
|
playIdle(actor, mPlayedIdle + 1);
|
|
|
|
|
mChooseAction = false;
|
|
|
|
|
mIdleNow = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(mIdleNow)
|
|
|
|
|
{
|
|
|
|
|
// TODO: This is where we should be checking to see if the current idle animation is done, if it is then
|
|
|
|
|
// set mChooseAction to true, because there is no function for this yet we will only set mChooseAction to true.
|
|
|
|
|
if(!checkIdle(actor, mPlayedIdle + 1))
|
|
|
|
|
{
|
|
|
|
|
std::cout << "Idle Really Completed" << std::endl;
|
|
|
|
|
mPlayedIdle = 0;
|
|
|
|
|
mIdleNow = false;
|
|
|
|
|
mChooseAction = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(mMoveNow == true && (mDistance != 0 && !mAllowedNodes.empty()))
|
|
|
|
|
{
|
|
|
|
|
if(!mPathFinder.isPathConstructed())
|
|
|
|
|
{
|
|
|
|
|
unsigned int randNode = (int)(rand() / ((double)RAND_MAX + 1) * mAllowedNodes.size());
|
|
|
|
|
Ogre::Vector3 destNodePos(mAllowedNodes[randNode].mX, mAllowedNodes[randNode].mY, mAllowedNodes[randNode].mZ);
|
|
|
|
|
|
|
|
|
|
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
|
|
|
|
|
ESM::Pathgrid::Point temp = mAllowedNodes[randNode];
|
|
|
|
|
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
|
|
|
|
|
mAllowedNodes.push_back(mCurrentNode);
|
|
|
|
|
mCurrentNode = temp;
|
|
|
|
|
|
|
|
|
|
ESM::Pathgrid::Point dest;
|
|
|
|
|
dest.mX = destNodePos[0] + mXCell;
|
|
|
|
|
dest.mY = destNodePos[1] + mYCell;
|
|
|
|
|
dest.mZ = destNodePos[2];
|
|
|
|
|
|
|
|
|
|
ESM::Pathgrid::Point start;
|
|
|
|
|
start.mX = pos.pos[0];
|
|
|
|
|
start.mY = pos.pos[1];
|
|
|
|
|
start.mZ = pos.pos[2];
|
|
|
|
|
|
|
|
|
|
mPathFinder.buildPath(start,dest,mPathgrid,mXCell,mYCell);
|
|
|
|
|
mWalking = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(mWalking)
|
|
|
|
|
{
|
|
|
|
|
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0],pos.pos[1],pos.pos[2]);
|
|
|
|
|
MWBase::Environment::get().getWorld()->rotateObject(actor,0,0,zAngle,false);
|
|
|
|
|
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(mWalking && mPathFinder.checkIfNextPointReached(pos.pos[0],pos.pos[1],pos.pos[2]))
|
|
|
|
|
{
|
|
|
|
|
stopWalking(actor, mPathFinder);
|
|
|
|
|
mMoveNow = false;
|
|
|
|
|
mWalking = false;
|
|
|
|
|
mChooseAction = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int MWMechanics::AiWander::getTypeId() const
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MWMechanics::AiWander::stopWalking(const MWWorld::Ptr& actor, PathFinder& path)
|
|
|
|
|
{
|
|
|
|
|
PathFinder pathClearer;
|
|
|
|
|
path = pathClearer;
|
|
|
|
|
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MWMechanics::AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect)
|
|
|
|
|
{
|
|
|
|
|
if(idleSelect == 2)
|
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle2", 0, 1);
|
|
|
|
|
else if(idleSelect == 3)
|
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
|
|
|
|
|
else if(idleSelect == 4)
|
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle4", 0, 1);
|
|
|
|
|
else if(idleSelect == 5)
|
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle5", 0, 1);
|
|
|
|
|
else if(idleSelect == 6)
|
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle6", 0, 1);
|
|
|
|
|
else if(idleSelect == 7)
|
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle7", 0, 1);
|
|
|
|
|
else if(idleSelect == 8)
|
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle8", 0, 1);
|
|
|
|
|
else if(idleSelect == 9)
|
|
|
|
|
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle9", 0, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MWMechanics::AiWander::checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect)
|
|
|
|
|
{
|
|
|
|
|
if(idleSelect == 2)
|
|
|
|
|
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle2");
|
|
|
|
|
else if(idleSelect == 3)
|
|
|
|
|
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle3");
|
|
|
|
|
else if(idleSelect == 4)
|
|
|
|
|
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle4");
|
|
|
|
|
else if(idleSelect == 5)
|
|
|
|
|
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle5");
|
|
|
|
|
else if(idleSelect == 6)
|
|
|
|
|
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle6");
|
|
|
|
|
else if(idleSelect == 7)
|
|
|
|
|
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle7");
|
|
|
|
|
else if(idleSelect == 8)
|
|
|
|
|
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle8");
|
|
|
|
|
else if(idleSelect == 9)
|
|
|
|
|
return MWBase::Environment::get().getMechanicsManager()->checkAnimationPlaying(actor, "idle9");
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|