Merge remote-tracking branch 'dteviot/refactoringAiWander'

sceneinput
Marc Zinnschlag 10 years ago
commit d22f8703e5

@ -117,7 +117,7 @@ namespace MWMechanics
mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp();
mStoredAvailableNodes = false; mPopulateAvailableNodes = true;
} }
@ -191,7 +191,7 @@ namespace MWMechanics
if(!currentCell || cellChange) if(!currentCell || cellChange)
{ {
currentCell = actor.getCell(); currentCell = actor.getCell();
mStoredAvailableNodes = false; // prob. not needed since mDistance = 0 mPopulateAvailableNodes = true;
} }
cStats.setDrawState(DrawState_Nothing); cStats.setDrawState(DrawState_Nothing);
@ -225,8 +225,6 @@ namespace MWMechanics
storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE))
{ {
stopWalking(actor, storage); stopWalking(actor, storage);
moveNow = false;
walking = false;
chooseAction = true; chooseAction = true;
mHasReturnPosition = false; mHasReturnPosition = false;
} }
@ -239,45 +237,7 @@ namespace MWMechanics
zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]))); zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1])));
actor.getClass().getMovementSettings(actor).mPosition[1] = 1; actor.getClass().getMovementSettings(actor).mPosition[1] = 1;
// Returns true if evasive action needs to be taken evadeObstacles(actor, storage, duration);
if(mObstacleCheck.check(actor, duration))
{
// first check if we're walking into a door
if(proximityToDoor(actor)) // NOTE: checks interior cells only
{
// remove allowed points then select another random destination
mTrimCurrentNode = true;
trimAllowedNodes(mAllowedNodes, storage.mPathFinder);
mObstacleCheck.clear();
storage.mPathFinder.clearPath();
walking = false;
moveNow = true;
}
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, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
}
mStuckCount++; // TODO: maybe no longer needed
}
//#if 0
// TODO: maybe no longer needed
if(mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
{
//std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl;
mObstacleCheck.clear();
stopWalking(actor, storage);
moveNow = false;
walking = false;
chooseAction = true;
mStuckCount = 0;
}
//#endif
} }
@ -325,32 +285,7 @@ namespace MWMechanics
} }
} }
// Play idle voiced dialogue entries randomly playIdleDialogueRandomly(actor);
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified();
if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor)
&& MWBase::Environment::get().getSoundManager()->sayDone(actor))
{
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
float roll = Misc::Rng::rollProbability() * 10000.0f;
// In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds
// due to the roll being an integer.
// Our implementation does not have these issues, so needs to be recalibrated. We chose to
// use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration.
float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f);
// Only say Idle voices when player is in LOS
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
// voices going through walls?
if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2()
< 3000*3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}
float& lastReaction = storage.mReaction; float& lastReaction = storage.mReaction;
lastReaction += duration; lastReaction += duration;
@ -367,17 +302,8 @@ namespace MWMechanics
{ {
// End package if duration is complete or mid-night hits: // End package if duration is complete or mid-night hits:
MWWorld::TimeStamp currentTime = world->getTimeStamp(); MWWorld::TimeStamp currentTime = world->getTimeStamp();
if(currentTime.getHour() >= mStartTime.getHour() + mDuration) if((currentTime.getHour() >= mStartTime.getHour() + mDuration) ||
{ (int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay()))
if(!mRepeat)
{
stopWalking(actor, storage);
return true;
}
else
mStartTime = currentTime;
}
else if(int(currentTime.getHour()) == 0 && currentTime.getDay() != mStartTime.getDay())
{ {
if(!mRepeat) if(!mRepeat)
{ {
@ -390,7 +316,7 @@ namespace MWMechanics
} }
// Initialization to discover & store allowed node points for this actor. // Initialization to discover & store allowed node points for this actor.
if(!mStoredAvailableNodes) if (mPopulateAvailableNodes)
{ {
getAllowedNodes(actor, currentCell->getCell()); getAllowedNodes(actor, currentCell->getCell());
} }
@ -432,111 +358,193 @@ namespace MWMechanics
// Allow interrupting a walking actor to trigger a greeting // Allow interrupting a walking actor to trigger a greeting
if(idleNow || walking) if(idleNow || walking)
{ {
// Play a random voice greeting if the player gets too close playGreetingIfPlayerGetsTooClose(actor, storage);
int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); }
float helloDistance = static_cast<float>(hello);
static int iGreetDistanceMultiplier =MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
helloDistance *= iGreetDistanceMultiplier;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
float playerDistSqr = (playerPos - actorPos).length2();
int& greetingTimer = storage.mGreetingTimer; if(moveNow && mDistance)
if (greetingState == Greet_None) {
// Construct a new path if there isn't one
if(!storage.mPathFinder.isPathConstructed())
{ {
if ((playerDistSqr <= helloDistance*helloDistance) && if (mAllowedNodes.size())
!player.getClass().getCreatureStats(player).isDead() && MWBase::Environment::get().getWorld()->getLOS(player, actor)
&& MWBase::Environment::get().getMechanicsManager()->awarenessCheck(player, actor))
greetingTimer++;
if (greetingTimer >= GREETING_SHOULD_START)
{ {
greetingState = Greet_InProgress; setPathToAnAllowedNode(actor, storage, pos);
MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
greetingTimer = 0;
} }
}
}
return false; // AiWander package not yet completed
}
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration)
{
if (mObstacleCheck.check(actor, duration))
{
// first check if we're walking into a door
if (proximityToDoor(actor)) // NOTE: checks interior cells only
{
// remove allowed points then select another random destination
mTrimCurrentNode = true;
trimAllowedNodes(mAllowedNodes, storage.mPathFinder);
mObstacleCheck.clear();
storage.mPathFinder.clearPath();
storage.mWalking = false;
storage.mMoveNow = true;
} }
else // probably walking into another NPC
if(greetingState == Greet_InProgress)
{ {
greetingTimer++; // TODO: diagonal should have same animation as walk forward
// but doesn't seem to do that?
if(walking) actor.getClass().getMovementSettings(actor).mPosition[0] = 1;
{ actor.getClass().getMovementSettings(actor).mPosition[1] = 0.1f;
stopWalking(actor, storage); // change the angle a bit, too
moveNow = false; const ESM::Position& pos = actor.getRefData().getPosition();
walking = false; zTurn(actor, osg::DegreesToRadians(storage.mPathFinder.getZAngleToNext(pos.pos[0] + 1, pos.pos[1])));
mObstacleCheck.clear(); }
idleNow = true; mStuckCount++; // TODO: maybe no longer needed
getRandomIdle(playedIdle); }
} //#if 0
// TODO: maybe no longer needed
if (mStuckCount >= COUNT_BEFORE_RESET) // something has gone wrong, reset
{
//std::cout << "Reset \""<< cls.getName(actor) << "\"" << std::endl;
mObstacleCheck.clear();
if(!rotate) stopWalking(actor, storage);
{ storage.mChooseAction = true;
osg::Vec3f dir = playerPos - actorPos; mStuckCount = 0;
}
//#endif
}
float faceAngleRadians = std::atan2(dir.x(), dir.y()); void AiWander::playIdleDialogueRandomly(const MWWorld::Ptr& actor)
targetAngleRadians = faceAngleRadians; {
rotate = true; int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
} if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor)
&& MWBase::Environment::get().getSoundManager()->sayDone(actor))
if (greetingTimer >= GREETING_SHOULD_END) {
{ MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
greetingState = Greet_Done;
greetingTimer = 0; static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore()
} .get<ESM::GameSetting>().find("fVoiceIdleOdds")->getFloat();
}
float roll = Misc::Rng::rollProbability() * 10000.0f;
if (greetingState == MWMechanics::AiWander::Greet_Done)
// In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds
// due to the roll being an integer.
// Our implementation does not have these issues, so needs to be recalibrated. We chose to
// use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration.
float x = fVoiceIdleOdds * 0.6f * (MWBase::Environment::get().getFrameDuration() / 0.1f);
// Only say Idle voices when player is in LOS
// A bit counterintuitive, likely vanilla did this to reduce the appearance of
// voices going through walls?
const ESM::Position& pos = actor.getRefData().getPosition();
if (roll < x && (player.getRefData().getPosition().asVec3() - pos.asVec3()).length2()
< 3000 * 3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead
&& MWBase::Environment::get().getWorld()->getLOS(player, actor))
MWBase::Environment::get().getDialogueManager()->say(actor, "idle");
}
}
void AiWander::playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage)
{
// Play a random voice greeting if the player gets too close
int hello = actor.getClass().getCreatureStats(actor).getAiSetting(CreatureStats::AI_Hello).getModified();
float helloDistance = static_cast<float>(hello);
static int iGreetDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("iGreetDistanceMultiplier")->getInt();
helloDistance *= iGreetDistanceMultiplier;
MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
osg::Vec3f playerPos(player.getRefData().getPosition().asVec3());
osg::Vec3f actorPos(actor.getRefData().getPosition().asVec3());
float playerDistSqr = (playerPos - actorPos).length2();
int& greetingTimer = storage.mGreetingTimer;
GreetingState& greetingState = storage.mSaidGreeting;
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))
greetingTimer++;
if (greetingTimer >= GREETING_SHOULD_START)
{ {
float resetDist = 2*helloDistance; greetingState = Greet_InProgress;
if (playerDistSqr >= resetDist*resetDist) MWBase::Environment::get().getDialogueManager()->say(actor, "hello");
greetingState = Greet_None; greetingTimer = 0;
} }
} }
if(moveNow && mDistance) if (greetingState == Greet_InProgress)
{ {
// Construct a new path if there isn't one greetingTimer++;
if(!storage.mPathFinder.isPathConstructed())
if (storage.mWalking)
{ {
assert(mAllowedNodes.size()); stopWalking(actor, storage);
unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size()); mObstacleCheck.clear();
ESM::Pathgrid::Point dest(mAllowedNodes[randNode]); storage.mIdleNow = true;
ToWorldCoordinates(dest, currentCell->getCell()); getRandomIdle(storage.mPlayedIdle);
}
// actor position is already in world co-ordinates if (!storage.mRotate)
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); {
osg::Vec3f dir = playerPos - actorPos;
// don't take shortcuts for wandering float faceAngleRadians = std::atan2(dir.x(), dir.y());
storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false); storage.mTargetAngleRadians = faceAngleRadians;
storage.mRotate = true;
}
if(storage.mPathFinder.isPathConstructed()) if (greetingTimer >= GREETING_SHOULD_END)
{ {
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node): greetingState = Greet_Done;
ESM::Pathgrid::Point temp = mAllowedNodes[randNode]; greetingTimer = 0;
mAllowedNodes.erase(mAllowedNodes.begin() + randNode); }
// check if mCurrentNode was taken out of mAllowedNodes }
if(mTrimCurrentNode && mAllowedNodes.size() > 1)
mTrimCurrentNode = false;
else
mAllowedNodes.push_back(mCurrentNode);
mCurrentNode = temp;
moveNow = false; if (greetingState == MWMechanics::AiWander::Greet_Done)
walking = true; {
} float resetDist = 2 * helloDistance;
// Choose a different node and delete this one from possible nodes because it is uncreachable: if (playerDistSqr >= resetDist*resetDist)
else greetingState = Greet_None;
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
}
} }
}
return false; // AiWander package not yet completed void AiWander::setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos)
{
unsigned int randNode = Misc::Rng::rollDice(mAllowedNodes.size());
ESM::Pathgrid::Point dest(mAllowedNodes[randNode]);
ToWorldCoordinates(dest, storage.mCell->getCell());
// actor position is already in world co-ordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos));
// don't take shortcuts for wandering
storage.mPathFinder.buildSyncedPath(start, dest, actor.getCell(), false);
if (storage.mPathFinder.isPathConstructed())
{
// 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);
// check if mCurrentNode was taken out of mAllowedNodes
if (mTrimCurrentNode && mAllowedNodes.size() > 1)
mTrimCurrentNode = false;
else
mAllowedNodes.push_back(mCurrentNode);
mCurrentNode = temp;
storage.mMoveNow = false;
storage.mWalking = true;
}
// Choose a different node and delete this one from possible nodes because it is uncreachable:
else
mAllowedNodes.erase(mAllowedNodes.begin() + randNode);
} }
void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell) void AiWander::ToWorldCoordinates(ESM::Pathgrid::Point& point, const ESM::Cell * cell)
@ -583,6 +591,8 @@ namespace MWMechanics
{ {
storage.mPathFinder.clearPath(); storage.mPathFinder.clearPath();
actor.getClass().getMovementSettings(actor).mPosition[1] = 0; actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
storage.mMoveNow = false;
storage.mWalking = false;
} }
void AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect) void AiWander::playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect)
@ -640,7 +650,7 @@ namespace MWMechanics
if (mDistance == 0) if (mDistance == 0)
return; return;
if (!mStoredAvailableNodes) if (mPopulateAvailableNodes)
getAllowedNodes(actor, actor.getCell()->getCell()); getAllowedNodes(actor, actor.getCell()->getCell());
if (mAllowedNodes.empty()) if (mAllowedNodes.empty())
@ -660,7 +670,7 @@ namespace MWMechanics
actor.getClass().adjustPosition(actor, false); actor.getClass().adjustPosition(actor, false);
// may have changed cell // may have changed cell
mStoredAvailableNodes = false; mPopulateAvailableNodes = true;
} }
int AiWander::OffsetToPreventOvercrowding() int AiWander::OffsetToPreventOvercrowding()
@ -722,8 +732,9 @@ namespace MWMechanics
{ {
SetCurrentNodeToClosestAllowedNode(npcPos); SetCurrentNodeToClosestAllowedNode(npcPos);
} }
mStoredAvailableNodes = true; // set only if successful in finding allowed nodes
} }
mPopulateAvailableNodes = false;
} }
// When only one path grid point in wander distance, // When only one path grid point in wander distance,

@ -71,6 +71,10 @@ namespace MWMechanics
void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); void playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect); bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
void getRandomIdle(unsigned short& playedIdle); void getRandomIdle(unsigned short& playedIdle);
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration);
void playIdleDialogueRandomly(const MWWorld::Ptr& actor);
int mDistance; // how far the actor can wander from the spawn point int mDistance; // how far the actor can wander from the spawn point
int mDuration; int mDuration;
@ -88,8 +92,8 @@ namespace MWMechanics
// if false triggers calculating allowed nodes based on mDistance // do we need to calculate allowed nodes based on mDistance
bool mStoredAvailableNodes; bool mPopulateAvailableNodes;

Loading…
Cancel
Save