From c02881a4f8992d41bb5c59ed3beea192563f6852 Mon Sep 17 00:00:00 2001 From: Austin Salgat Date: Tue, 12 Apr 2016 21:16:55 -0500 Subject: [PATCH 1/8] Fix creatures not wandering --- apps/openmw/mwmechanics/aiwander.cpp | 91 ++++++++++++++++++++++++---- apps/openmw/mwmechanics/aiwander.hpp | 8 +++ 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index f0295f331..b1df110fb 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -67,6 +67,11 @@ namespace MWMechanics // AiWander states AiWander::WanderState mState; + + // Wandering near spawn logic + bool mIsWanderingManually; + ESM::Pathgrid::Point mPreviousWanderingNearSpawnLocation; + int mStuckTimer; unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors @@ -81,9 +86,16 @@ namespace MWMechanics mGreetingTimer(0), mCell(NULL), mState(AiWander::Wander_ChooseAction), + mIsWanderingManually(false), + mStuckTimer(0), mIdleAnimation(0), mBadIdles() {}; + + void setState(const AiWander::WanderState wanderState, const bool isManualWander = false) { + mState = wanderState; + mIsWanderingManually = isManualWander; + } }; AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): @@ -227,8 +239,20 @@ namespace MWMechanics } // Actor becomes stationary - see above URL's for previous research - if(mAllowedNodes.empty()) + // If not an NPC and no pathgrid is available, randomly idle or wander around near spawn point + if(mAllowedNodes.empty() && !actor.getBase()->mClass->isNpc() && !storage.mIsWanderingManually) { + // Typically want to idle for a short time before the next wander + if (Misc::Rng::rollDice(100) >= 96) { + wanderNearSpawn(actor, storage); + } + } else if (mAllowedNodes.empty() && !storage.mIsWanderingManually) { mDistance = 0; + } + + // Detect obstacles if wandering manually + if (storage.mIsWanderingManually) { + detectManualWanderingObstacles(actor, storage); + } // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. if(mDistance && cellChange) @@ -303,8 +327,51 @@ namespace MWMechanics if (storage.mPathFinder.isPathConstructed()) { - storage.mState = Wander_Walking; + storage.setState(Wander_Walking); + } + } + } + + /* + * Commands actor to walk to a random location near original spawn location. + */ + void AiWander::wanderNearSpawn(const MWWorld::Ptr& actor, AiWanderStorage& storage) { + const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; + const ESM::Pathgrid::Point originalPosition = actor.getCellRef().getPosition().pos; + + // Determine a random location within radius of original position + const float pi = 3.14159265359f; + const float randomRadius = Misc::Rng::rollClosedProbability() * MINIMUM_WANDER_DISTANCE * 14.0f; + const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; + const float destinationX = originalPosition.mX + randomRadius * std::cos(randomDirection); + const float destinationY = originalPosition.mY + randomRadius * std::sin(randomDirection); + ESM::Pathgrid::Point destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, originalPosition.mZ); + + storage.mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), true); + storage.mPathFinder.addPointToPath(destinationPosition); + storage.mPreviousWanderingNearSpawnLocation = currentPosition; + storage.mStuckTimer = 0; + storage.setState(Wander_Walking, true); + } + + /* + * Detects if a manually wandering actor has spent too much time at one spot (stuck by an obstacle) + * and stops wandering when that occurs. Uses the unit's speed to help determine how long they should + * not be in one spot. + */ + void AiWander::detectManualWanderingObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage) { + const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; + const float actorSpeed = actor.getClass().getSpeed(actor); + const float minimumDistanceTraveled = actorSpeed / 5.0f; + if (distanceApart2d(storage.mPreviousWanderingNearSpawnLocation, currentPosition) < minimumDistanceTraveled) { + // Hit an obstacle and haven't moved much + if (++(storage.mStuckTimer) > 10) { + // Stuck too long, stop wandering + storage.setState(Wander_ChooseAction); + mDistance = 0; } + } else { + storage.mPreviousWanderingNearSpawnLocation = currentPosition; } } @@ -344,7 +411,7 @@ namespace MWMechanics if (mDistance && // actor is not intended to be stationary proximityToDoor(actor, MIN_DIST_TO_DOOR_SQUARED*1.6f*1.6f)) // NOTE: checks interior cells only { - storage.mState = Wander_MoveNow; + storage.setState(Wander_MoveNow); mTrimCurrentNode = false; // just in case return; } @@ -364,7 +431,7 @@ namespace MWMechanics GreetingState& greetingState = storage.mSaidGreeting; if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) { - storage.mState = Wander_ChooseAction; + storage.setState(Wander_ChooseAction); } } @@ -375,7 +442,7 @@ namespace MWMechanics if (storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { stopWalking(actor, storage); - storage.mState = Wander_ChooseAction; + storage.setState(Wander_ChooseAction); mHasReturnPosition = false; } else @@ -393,7 +460,7 @@ namespace MWMechanics if (!idleAnimation && mDistance) { - storage.mState = Wander_MoveNow; + storage.setState(Wander_MoveNow); return; } if(idleAnimation) @@ -403,14 +470,14 @@ namespace MWMechanics if(!playIdle(actor, idleAnimation)) { storage.mBadIdles.push_back(idleAnimation); - storage.mState = Wander_ChooseAction; + storage.setState(Wander_ChooseAction); return; } } } // Recreate vanilla (broken?) behavior of resetting start time of AIWander: mStartTime = MWBase::Environment::get().getWorld()->getTimeStamp(); - storage.mState = Wander_IdleNow; + storage.setState(Wander_IdleNow); } void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos) @@ -429,7 +496,7 @@ namespace MWMechanics trimAllowedNodes(mAllowedNodes, storage.mPathFinder); mObstacleCheck.clear(); storage.mPathFinder.clearPath(); - storage.mState = Wander_MoveNow; + storage.setState(Wander_MoveNow); } else // probably walking into another NPC { @@ -451,7 +518,7 @@ namespace MWMechanics mObstacleCheck.clear(); stopWalking(actor, storage); - storage.mState = Wander_ChooseAction; + storage.setState(Wander_ChooseAction); mStuckCount = 0; } } @@ -526,7 +593,7 @@ namespace MWMechanics { stopWalking(actor, storage); mObstacleCheck.clear(); - storage.mState = Wander_IdleNow; + storage.setState(Wander_IdleNow); } turnActorToFacePlayer(actorPos, playerPos, storage); @@ -579,7 +646,7 @@ namespace MWMechanics mAllowedNodes.push_back(mCurrentNode); mCurrentNode = temp; - storage.mState = Wander_Walking; + storage.setState(Wander_Walking); } // Choose a different node and delete this one from possible nodes because it is uncreachable: else diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 683c04bb0..b8001c701 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -94,6 +94,8 @@ namespace MWMechanics const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); + void wanderNearSpawn(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void detectManualWanderingObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); int mDistance; // how far the actor can wander from the spawn point int mDuration; @@ -158,6 +160,12 @@ namespace MWMechanics static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; static int OffsetToPreventOvercrowding(); + + float distanceApart2d(const ESM::Pathgrid::Point& first, const ESM::Pathgrid::Point& second) { + const float deltaX = second.mX - first.mX; + const float deltaY = second.mY - first.mY; + return std::sqrt(deltaX*deltaX + deltaY*deltaY); + } }; From 92b352989ace68319e700959269b9777333841a2 Mon Sep 17 00:00:00 2001 From: Austin Salgat Date: Wed, 13 Apr 2016 20:48:08 -0500 Subject: [PATCH 2/8] Add logic for NPC wandering without pathgrids --- apps/openmw/mwmechanics/aiwander.cpp | 35 ++++++++++++++++------------ apps/openmw/mwmechanics/aiwander.hpp | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index b1df110fb..0e3d20d4a 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -232,6 +232,12 @@ namespace MWMechanics return true; } + if (!mStoredInitialActorPosition) + { + mInitialActorPosition = actor.getRefData().getPosition().asVec3(); + mStoredInitialActorPosition = true; + } + // Initialization to discover & store allowed node points for this actor. if (mPopulateAvailableNodes) { @@ -239,11 +245,12 @@ namespace MWMechanics } // Actor becomes stationary - see above URL's for previous research - // If not an NPC and no pathgrid is available, randomly idle or wander around near spawn point - if(mAllowedNodes.empty() && !actor.getBase()->mClass->isNpc() && !storage.mIsWanderingManually) { + // If a creature or an NPC with a wander distance and no pathgrid is available, + // randomly idle or wander around near spawn point + if(mAllowedNodes.empty() && (mDistance > 0 || !actor.getClass().isNpc()) && !storage.mIsWanderingManually) { // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100) >= 96) { - wanderNearSpawn(actor, storage); + wanderNearStart(actor, storage, mDistance, actor.getClass().isNpc()); } } else if (mAllowedNodes.empty() && !storage.mIsWanderingManually) { mDistance = 0; @@ -334,18 +341,22 @@ namespace MWMechanics /* * Commands actor to walk to a random location near original spawn location. + * + * Creatures simply wander a certain distance from their starting location, while NPCs wander a scripted + * distance (mDistance) from the position where they started the wander package. + * http://www.uesp.net/wiki/Tes3Mod:AIWander */ - void AiWander::wanderNearSpawn(const MWWorld::Ptr& actor, AiWanderStorage& storage) { + void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance, bool isNpc) { const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; - const ESM::Pathgrid::Point originalPosition = actor.getCellRef().getPosition().pos; // Determine a random location within radius of original position const float pi = 3.14159265359f; - const float randomRadius = Misc::Rng::rollClosedProbability() * MINIMUM_WANDER_DISTANCE * 14.0f; + const float maxWanderDistance = isNpc ? wanderDistance : MINIMUM_WANDER_DISTANCE * 14.0f; + const float wanderRadius = Misc::Rng::rollClosedProbability() * maxWanderDistance; const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; - const float destinationX = originalPosition.mX + randomRadius * std::cos(randomDirection); - const float destinationY = originalPosition.mY + randomRadius * std::sin(randomDirection); - ESM::Pathgrid::Point destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, originalPosition.mZ); + const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); + const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); + ESM::Pathgrid::Point destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, mInitialActorPosition.z()); storage.mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), true); storage.mPathFinder.addPointToPath(destinationPosition); @@ -787,12 +798,6 @@ namespace MWMechanics void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) { - if (!mStoredInitialActorPosition) - { - mInitialActorPosition = actor.getRefData().getPosition().asVec3(); - mStoredInitialActorPosition = true; - } - // infrequently used, therefore no benefit in caching it as a member const ESM::Pathgrid * pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index b8001c701..dde87079a 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -94,7 +94,7 @@ namespace MWMechanics const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); - void wanderNearSpawn(const MWWorld::Ptr& actor, AiWanderStorage& storage); + void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance, bool isNpc); void detectManualWanderingObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); int mDistance; // how far the actor can wander from the spawn point From 34726c24d99b8e242a79bc22dc97a31801304292 Mon Sep 17 00:00:00 2001 From: Austin Salgat Date: Fri, 15 Apr 2016 21:56:41 -0500 Subject: [PATCH 3/8] Fix mDistance being reset prematurely This was causing wandering without pathgrids to become disabled for most wandering units. Additionally, wandering now behaves the same for both NPCs and creatures. --- apps/openmw/mwmechanics/aiwander.cpp | 36 ++++++++++++++++------------ apps/openmw/mwmechanics/aiwander.hpp | 4 ++-- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 0e3d20d4a..e9957dd60 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -16,6 +16,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/customdata.hpp" #include "creaturestats.hpp" #include "steering.hpp" @@ -72,6 +73,8 @@ namespace MWMechanics bool mIsWanderingManually; ESM::Pathgrid::Point mPreviousWanderingNearSpawnLocation; int mStuckTimer; + + bool mCanWanderAlongPathGrid; unsigned short mIdleAnimation; std::vector mBadIdles; // Idle animations that when called cause errors @@ -88,6 +91,7 @@ namespace MWMechanics mState(AiWander::Wander_ChooseAction), mIsWanderingManually(false), mStuckTimer(0), + mCanWanderAlongPathGrid(true), mIdleAnimation(0), mBadIdles() {}; @@ -227,6 +231,9 @@ namespace MWMechanics bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos) { + if (mDistance <= 0) + storage.mCanWanderAlongPathGrid = false; + if (isPackageCompleted(actor, storage)) { return true; @@ -241,19 +248,19 @@ namespace MWMechanics // Initialization to discover & store allowed node points for this actor. if (mPopulateAvailableNodes) { - getAllowedNodes(actor, currentCell->getCell()); + getAllowedNodes(actor, currentCell->getCell(), storage); } // Actor becomes stationary - see above URL's for previous research // If a creature or an NPC with a wander distance and no pathgrid is available, // randomly idle or wander around near spawn point - if(mAllowedNodes.empty() && (mDistance > 0 || !actor.getClass().isNpc()) && !storage.mIsWanderingManually) { + if(mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100) >= 96) { - wanderNearStart(actor, storage, mDistance, actor.getClass().isNpc()); + wanderNearStart(actor, storage, mDistance); } } else if (mAllowedNodes.empty() && !storage.mIsWanderingManually) { - mDistance = 0; + storage.mCanWanderAlongPathGrid = false; } // Detect obstacles if wandering manually @@ -281,7 +288,7 @@ namespace MWMechanics playGreetingIfPlayerGetsTooClose(actor, storage); } - if ((wanderState == Wander_MoveNow) && mDistance) + if ((wanderState == Wander_MoveNow) && storage.mCanWanderAlongPathGrid) { // Construct a new path if there isn't one if(!storage.mPathFinder.isPathConstructed()) @@ -346,13 +353,12 @@ namespace MWMechanics * distance (mDistance) from the position where they started the wander package. * http://www.uesp.net/wiki/Tes3Mod:AIWander */ - void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance, bool isNpc) { + void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) { const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; // Determine a random location within radius of original position const float pi = 3.14159265359f; - const float maxWanderDistance = isNpc ? wanderDistance : MINIMUM_WANDER_DISTANCE * 14.0f; - const float wanderRadius = Misc::Rng::rollClosedProbability() * maxWanderDistance; + const float wanderRadius = Misc::Rng::rollClosedProbability() * wanderDistance; const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); @@ -376,10 +382,10 @@ namespace MWMechanics const float minimumDistanceTraveled = actorSpeed / 5.0f; if (distanceApart2d(storage.mPreviousWanderingNearSpawnLocation, currentPosition) < minimumDistanceTraveled) { // Hit an obstacle and haven't moved much - if (++(storage.mStuckTimer) > 10) { - // Stuck too long, stop wandering + if (++(storage.mStuckTimer) > 8) { + // Stuck too long, wander elsewhere storage.setState(Wander_ChooseAction); - mDistance = 0; + wanderNearStart(actor, storage, mDistance); } } else { storage.mPreviousWanderingNearSpawnLocation = currentPosition; @@ -769,7 +775,7 @@ namespace MWMechanics return; if (mPopulateAvailableNodes) - getAllowedNodes(actor, actor.getCell()->getCell()); + getAllowedNodes(actor, actor.getCell()->getCell(), state.get()); if (mAllowedNodes.empty()) return; @@ -796,7 +802,7 @@ namespace MWMechanics return static_cast(DESTINATION_TOLERANCE * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); } - void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) + void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage) { // infrequently used, therefore no benefit in caching it as a member const ESM::Pathgrid * @@ -810,14 +816,14 @@ namespace MWMechanics // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 // Note: In order to wander, need at least two points. if(!pathgrid || (pathgrid->mPoints.size() < 2)) - mDistance = 0; + storage.mCanWanderAlongPathGrid = false; // A distance value passed into the constructor indicates how far the // actor can wander from the spawn position. AiWander assumes that // pathgrid points are available, and uses them to randomly select wander // destinations within the allowed set of pathgrid points (nodes). // ... pathgrids don't usually include water, so swimmers ignore them - if (mDistance && !actor.getClass().isPureWaterCreature(actor)) + if (mDistance && storage.mCanWanderAlongPathGrid && !actor.getClass().isPureWaterCreature(actor)) { // get NPC's position in local (i.e. cell) co-ordinates osg::Vec3f npcPos(mInitialActorPosition); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index dde87079a..32fda3331 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -94,7 +94,7 @@ namespace MWMechanics const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); - void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance, bool isNpc); + void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); void detectManualWanderingObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); int mDistance; // how far the actor can wander from the spawn point @@ -126,7 +126,7 @@ namespace MWMechanics // FIXME: move to AiWanderStorage std::vector mAllowedNodes; - void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell); + void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); // FIXME: move to AiWanderStorage ESM::Pathgrid::Point mCurrentNode; From fc03216d488a18d7b6d92bce4f83422f235302ec Mon Sep 17 00:00:00 2001 From: Austin Salgat Date: Sat, 16 Apr 2016 12:14:00 -0500 Subject: [PATCH 4/8] Refactor to reuse existing obstacle detection --- apps/openmw/mwmechanics/aiwander.cpp | 42 ++++------------------------ apps/openmw/mwmechanics/aiwander.hpp | 9 +----- apps/openmw/mwmechanics/obstacle.cpp | 4 +-- apps/openmw/mwmechanics/obstacle.hpp | 2 +- 4 files changed, 9 insertions(+), 48 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index e9957dd60..e47105055 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -69,11 +69,7 @@ namespace MWMechanics // AiWander states AiWander::WanderState mState; - // Wandering near spawn logic bool mIsWanderingManually; - ESM::Pathgrid::Point mPreviousWanderingNearSpawnLocation; - int mStuckTimer; - bool mCanWanderAlongPathGrid; unsigned short mIdleAnimation; @@ -90,7 +86,6 @@ namespace MWMechanics mCell(NULL), mState(AiWander::Wander_ChooseAction), mIsWanderingManually(false), - mStuckTimer(0), mCanWanderAlongPathGrid(true), mIdleAnimation(0), mBadIdles() @@ -222,14 +217,14 @@ namespace MWMechanics if (REACTION_INTERVAL <= lastReaction) { lastReaction = 0; - return reactionTimeActions(actor, storage, currentCell, cellChange, pos); + return reactionTimeActions(actor, storage, currentCell, cellChange, pos, duration); } else return false; } bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, - const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos) + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration) { if (mDistance <= 0) storage.mCanWanderAlongPathGrid = false; @@ -263,9 +258,9 @@ namespace MWMechanics storage.mCanWanderAlongPathGrid = false; } - // Detect obstacles if wandering manually - if (storage.mIsWanderingManually) { - detectManualWanderingObstacles(actor, storage); + // If Wandering manually and hit an obstacle, stop + if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration*10.0f, 2.5f)) { + stopWalking(actor, storage); } // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. @@ -348,10 +343,6 @@ namespace MWMechanics /* * Commands actor to walk to a random location near original spawn location. - * - * Creatures simply wander a certain distance from their starting location, while NPCs wander a scripted - * distance (mDistance) from the position where they started the wander package. - * http://www.uesp.net/wiki/Tes3Mod:AIWander */ void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) { const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; @@ -366,32 +357,9 @@ namespace MWMechanics storage.mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), true); storage.mPathFinder.addPointToPath(destinationPosition); - storage.mPreviousWanderingNearSpawnLocation = currentPosition; - storage.mStuckTimer = 0; storage.setState(Wander_Walking, true); } - /* - * Detects if a manually wandering actor has spent too much time at one spot (stuck by an obstacle) - * and stops wandering when that occurs. Uses the unit's speed to help determine how long they should - * not be in one spot. - */ - void AiWander::detectManualWanderingObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage) { - const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; - const float actorSpeed = actor.getClass().getSpeed(actor); - const float minimumDistanceTraveled = actorSpeed / 5.0f; - if (distanceApart2d(storage.mPreviousWanderingNearSpawnLocation, currentPosition) < minimumDistanceTraveled) { - // Hit an obstacle and haven't moved much - if (++(storage.mStuckTimer) > 8) { - // Stuck too long, wander elsewhere - storage.setState(Wander_ChooseAction); - wanderNearStart(actor, storage, mDistance); - } - } else { - storage.mPreviousWanderingNearSpawnLocation = currentPosition; - } - } - void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos) { switch (storage.mState) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 32fda3331..af2349989 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -91,11 +91,10 @@ namespace MWMechanics void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos); void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage); bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, - const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos); + const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration); bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); - void detectManualWanderingObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage); int mDistance; // how far the actor can wander from the spawn point int mDuration; @@ -160,12 +159,6 @@ namespace MWMechanics static const std::string sIdleSelectToGroupName[GroupIndex_MaxIdle - GroupIndex_MinIdle + 1]; static int OffsetToPreventOvercrowding(); - - float distanceApart2d(const ESM::Pathgrid::Point& first, const ESM::Pathgrid::Point& second) { - const float deltaX = second.mX - first.mX; - const float deltaY = second.mY - first.mY; - return std::sqrt(deltaX*deltaX + deltaY*deltaY); - } }; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 11bbd1e31..4394168a5 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -115,13 +115,13 @@ namespace MWMechanics * u = how long to move sideways * */ - bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration) + bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance) { const MWWorld::Class& cls = actor.getClass(); ESM::Position pos = actor.getRefData().getPosition(); if(mDistSameSpot == -1) - mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor); + mDistSameSpot = DIST_SAME_SPOT * cls.getSpeed(actor) * scaleMinimumDistance; float distSameSpot = mDistSameSpot * duration; diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 98cc4e7a0..c8c83d68f 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -36,7 +36,7 @@ namespace MWMechanics // Returns true if there is an obstacle and an evasive action // should be taken - bool check(const MWWorld::Ptr& actor, float duration); + bool check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f); // change direction to try to fix "stuck" actor void takeEvasiveAction(MWMechanics::Movement& actorMovement); From 84179c262f8a7ee2448a41640c6b130f758e3ed0 Mon Sep 17 00:00:00 2001 From: Austin Salgat Date: Sat, 16 Apr 2016 16:39:13 -0500 Subject: [PATCH 5/8] Update manual wandering to prevent actor from leaving/entering water Water creatures will stay in the water, while land creatures will stay on land when wandering. --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 54 ++++++++++++++++++++++------ apps/openmw/mwmechanics/aiwander.hpp | 2 ++ apps/openmw/mwworld/worldimp.cpp | 9 +++-- apps/openmw/mwworld/worldimp.hpp | 2 +- 5 files changed, 53 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index e9a76c565..7376e6b51 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -428,7 +428,7 @@ namespace MWBase virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor) = 0; ///< get Line of Sight (morrowind stupid implementation) - virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist) = 0; + virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false) = 0; virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable) = 0; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index e47105055..7e4d5ea7d 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -346,18 +346,50 @@ namespace MWMechanics */ void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) { const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos; + const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ); + + std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here + osg::Vec3f destination; + ESM::Pathgrid::Point destinationPosition; + bool isWaterCreature = actor.getClass().isPureWaterCreature(actor); + do { + // Determine a random location within radius of original position + const float pi = 3.14159265359f; + const float wanderRadius = Misc::Rng::rollClosedProbability() * wanderDistance; + const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; + const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); + const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); + const float destinationZ = mInitialActorPosition.z(); + destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, destinationZ); + destination = osg::Vec3f(destinationX, destinationY, destinationZ); + + // Check if land creature will walk onto water or if water creature will swim onto land + if ((!isWaterCreature && !destinationIsAtWater(actor, destination)) || + (isWaterCreature && destinationThroughGround(currentPositionVec3f, destination))) { + storage.mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), true); + storage.mPathFinder.addPointToPath(destinationPosition); + storage.setState(Wander_Walking, true); + return; + } + } while (attempts--); + } - // Determine a random location within radius of original position - const float pi = 3.14159265359f; - const float wanderRadius = Misc::Rng::rollClosedProbability() * wanderDistance; - const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; - const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); - const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); - ESM::Pathgrid::Point destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, mInitialActorPosition.z()); - - storage.mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), true); - storage.mPathFinder.addPointToPath(destinationPosition); - storage.setState(Wander_Walking, true); + /* + * Returns true if the position provided is above water. + */ + bool AiWander::destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination) { + float heightToGroundOrWater = destination.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(destination, osg::Vec3f(0,0,-1), 1000.0, true); + osg::Vec3f positionBelowSurface = destination; + positionBelowSurface[2] = positionBelowSurface[2] - heightToGroundOrWater - DESTINATION_TOLERANCE; + return MWBase::Environment::get().getWorld()->isUnderwater(actor.getCell(), positionBelowSurface); + } + + /* + * Returns true if the start to end point travels through a collision point (land). + */ + bool AiWander::destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination) { + return MWBase::Environment::get().getWorld()->castRay(startPoint.x(), startPoint.y(), startPoint.z(), + destination.x(), destination.y(), destination.z()); } void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index af2349989..57d1253d2 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -95,6 +95,8 @@ namespace MWMechanics bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage); void returnToStartLocation(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos); void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); + bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); + bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); int mDistance; // how far the actor can wander from the spawn point int mDuration; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 8f3fcbd41..a72ee9021 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2389,14 +2389,17 @@ namespace MWWorld return mPhysics->getLineOfSight(actor, targetActor); } - float World::getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist) + float World::getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater) { osg::Vec3f to (dir); to.normalize(); to = from + (to * maxDist); - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), - MWPhysics::CollisionType_World|MWPhysics::CollisionType_HeightMap|MWPhysics::CollisionType_Door); + int collisionTypes = MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Door; + if (includeWater) { + collisionTypes |= MWPhysics::CollisionType_Water; + } + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), collisionTypes); if (!result.mHit) return maxDist; diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index c9a1d415d..7af632d23 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -527,7 +527,7 @@ namespace MWWorld virtual bool getLOS(const MWWorld::ConstPtr& actor,const MWWorld::ConstPtr& targetActor); ///< get Line of Sight (morrowind stupid implementation) - virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist); + virtual float getDistToNearestRayHit(const osg::Vec3f& from, const osg::Vec3f& dir, float maxDist, bool includeWater = false); virtual void enableActorCollision(const MWWorld::Ptr& actor, bool enable); From 3dec10c686f3008256f81185b72266e8e8ec483f Mon Sep 17 00:00:00 2001 From: Austin Salgat Date: Sat, 16 Apr 2016 17:51:13 -0500 Subject: [PATCH 6/8] Update variable to prevent underflowing While not an issue presently, the variable may be used in the future in a way that could create issues if it underflows. --- apps/openmw/mwmechanics/aiwander.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 7e4d5ea7d..291d3ec73 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -371,7 +371,7 @@ namespace MWMechanics storage.setState(Wander_Walking, true); return; } - } while (attempts--); + } while (--attempts); } /* From e9157e920035f1db77718499c851e7bd9d6074e2 Mon Sep 17 00:00:00 2001 From: Austin Salgat Date: Sat, 16 Apr 2016 20:38:58 -0500 Subject: [PATCH 7/8] Fix idle animation and Fix water creature manual wandering --- apps/openmw/mwmechanics/aiwander.cpp | 17 +++++++++++++---- apps/openmw/mwmechanics/aiwander.hpp | 1 + 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 291d3ec73..a07106c83 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -16,7 +16,6 @@ #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/customdata.hpp" #include "creaturestats.hpp" #include "steering.hpp" @@ -253,14 +252,16 @@ namespace MWMechanics // Typically want to idle for a short time before the next wander if (Misc::Rng::rollDice(100) >= 96) { wanderNearStart(actor, storage, mDistance); + } else { + storage.setState(Wander_IdleNow); } } else if (mAllowedNodes.empty() && !storage.mIsWanderingManually) { storage.mCanWanderAlongPathGrid = false; } // If Wandering manually and hit an obstacle, stop - if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration*10.0f, 2.5f)) { - stopWalking(actor, storage); + if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration, 2.0f)) { + completeManualWalking(actor, storage); } // Don't try to move if you are in a new cell (ie: positioncell command called) but still play idles. @@ -293,6 +294,8 @@ namespace MWMechanics setPathToAnAllowedNode(actor, storage, pos); } } + } else if (storage.mIsWanderingManually && storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) { + completeManualWalking(actor, storage); } return false; // AiWander package not yet completed @@ -365,7 +368,7 @@ namespace MWMechanics // Check if land creature will walk onto water or if water creature will swim onto land if ((!isWaterCreature && !destinationIsAtWater(actor, destination)) || - (isWaterCreature && destinationThroughGround(currentPositionVec3f, destination))) { + (isWaterCreature && !destinationThroughGround(currentPositionVec3f, destination))) { storage.mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), true); storage.mPathFinder.addPointToPath(destinationPosition); storage.setState(Wander_Walking, true); @@ -392,6 +395,12 @@ namespace MWMechanics destination.x(), destination.y(), destination.z()); } + void AiWander::completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage) { + stopWalking(actor, storage); + mObstacleCheck.clear(); + storage.setState(Wander_IdleNow); + } + void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos) { switch (storage.mState) diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index 57d1253d2..1155100b8 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -97,6 +97,7 @@ namespace MWMechanics void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance); bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination); bool destinationThroughGround(const osg::Vec3f& startPoint, const osg::Vec3f& destination); + void completeManualWalking(const MWWorld::Ptr &actor, AiWanderStorage &storage); int mDistance; // how far the actor can wander from the spawn point int mDuration; From be16857f96d17b640a2b49b092087cf4d8e7d5dd Mon Sep 17 00:00:00 2001 From: Austin Salgat Date: Sun, 17 Apr 2016 10:46:09 -0500 Subject: [PATCH 8/8] Fix wrong height being used for water detection --- apps/openmw/mwmechanics/aiwander.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index a07106c83..2bdbfe894 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -381,9 +381,9 @@ namespace MWMechanics * Returns true if the position provided is above water. */ bool AiWander::destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination) { - float heightToGroundOrWater = destination.z() - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(destination, osg::Vec3f(0,0,-1), 1000.0, true); + float heightToGroundOrWater = MWBase::Environment::get().getWorld()->getDistToNearestRayHit(destination, osg::Vec3f(0,0,-1), 1000.0, true); osg::Vec3f positionBelowSurface = destination; - positionBelowSurface[2] = positionBelowSurface[2] - heightToGroundOrWater - DESTINATION_TOLERANCE; + positionBelowSurface[2] = positionBelowSurface[2] - heightToGroundOrWater - 1.0f; return MWBase::Environment::get().getWorld()->isUnderwater(actor.getCell(), positionBelowSurface); }