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);