diff --git a/CHANGELOG.md b/CHANGELOG.md index b21639d144..ddc46cd890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ Bug #6283: Avis Dorsey follows you after her death Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters Bug #6302: Teleporting disabled actor breaks its disabled state + Bug #6307: Pathfinding in Infidelities quest from Tribunal addon is broken Feature #890: OpenMW-CS: Column filtering Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 1dbb2b96f6..c8036385db 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -653,7 +653,8 @@ namespace MWBase virtual bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; - virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const = 0; + virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors = nullptr) const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index ad0a3943ad..b1f4355a39 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -448,7 +448,11 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: result |= DetourNavigator::Flag_swim; if (actorClass.canWalk(actor) && actor.getClass().getWalkSpeed(actor) > 0) + { result |= DetourNavigator::Flag_walk; + if (getTypeId() == AiPackageTypeId::Travel) + result |= DetourNavigator::Flag_usePathgrid; + } if (canOpenDoors(actor) && getTypeId() != AiPackageTypeId::Wander) result |= DetourNavigator::Flag_openDoor; @@ -462,20 +466,31 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P const DetourNavigator::Flags flags = getNavigatorFlags(actor); const MWWorld::Class& actorClass = actor.getClass(); - if (flags & DetourNavigator::Flag_swim) - costs.mWater = divOrMax(costs.mWater, actorClass.getSwimSpeed(actor)); + const float swimSpeed = (flags & DetourNavigator::Flag_swim) == 0 + ? 0.0f + : actorClass.getSwimSpeed(actor); - if (flags & DetourNavigator::Flag_walk) + const float walkSpeed = [&] { - float walkCost; + if ((flags & DetourNavigator::Flag_walk) == 0) + return 0.0f; if (getTypeId() == AiPackageTypeId::Wander) - walkCost = divOrMax(1.0, actorClass.getWalkSpeed(actor)); - else - walkCost = divOrMax(1.0, actorClass.getRunSpeed(actor)); - costs.mDoor = costs.mDoor * walkCost; - costs.mPathgrid = costs.mPathgrid * walkCost; - costs.mGround = costs.mGround * walkCost; - } + return actorClass.getWalkSpeed(actor); + return actorClass.getRunSpeed(actor); + } (); + + const float maxSpeed = std::max(swimSpeed, walkSpeed); + + if (maxSpeed == 0) + return costs; + + const float swimFactor = swimSpeed / maxSpeed; + const float walkFactor = walkSpeed / maxSpeed; + + costs.mWater = divOrMax(costs.mWater, swimFactor); + costs.mDoor = divOrMax(costs.mDoor, walkFactor); + costs.mPathgrid = divOrMax(costs.mPathgrid, walkFactor); + costs.mGround = divOrMax(costs.mGround, walkFactor); return costs; } diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 91fe96a2aa..2594adcb34 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -1,5 +1,7 @@ #include "aitravel.hpp" +#include + #include #include "../mwbase/environment.hpp" @@ -23,6 +25,11 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2) return (pos1 - pos2).length2() <= 7168*7168; } + float getActorRadius(const MWWorld::ConstPtr& actor) + { + const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor); + return std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + } } namespace MWMechanics @@ -70,16 +77,24 @@ namespace MWMechanics // Unfortunately, with vanilla assets destination is sometimes blocked by other actor. // If we got close to target, check for actors nearby. If they are, finish AI package. - int destinationTolerance = 64; - if (distance(actorPos, targetPos) <= destinationTolerance) + if (mDestinationCheck.update(duration) == Misc::TimerStatus::Elapsed) { - std::vector targetActors; - std::pair result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors); - - if (!result.first.isEmpty()) + std::vector occupyingActors; + if (isAreaOccupiedByOtherActor(actor, targetPos, &occupyingActors)) { - actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - return true; + const float actorRadius = getActorRadius(actor); + const float distanceToTarget = distance(actorPos, targetPos); + for (const MWWorld::Ptr& other : occupyingActors) + { + const float otherRadius = getActorRadius(other); + const auto [minRadius, maxRadius] = std::minmax(actorRadius, otherRadius); + constexpr float toleranceFactor = 1.25; + if (minRadius * toleranceFactor + maxRadius > distanceToTarget) + { + actor.getClass().getMovementSettings(actor).mPosition[1] = 0; + return true; + } + } } } diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index 2ea2a8f717..ee4ff9ad4d 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -52,6 +52,8 @@ namespace MWMechanics const float mZ; const bool mHidden; + + AiReactionTimer mDestinationCheck; }; struct AiInternalTravel final : public AiTravel diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 624abe10f9..9c84555404 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -85,14 +85,6 @@ namespace MWMechanics return MWBase::Environment::get().getWorld()->castRay(position, visibleDestination, mask, actor); } - bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr &actor, const osg::Vec3f& destination) - { - const auto world = MWBase::Environment::get().getWorld(); - const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); - const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); - return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor); - } - void stopMovement(const MWWorld::Ptr& actor) { actor.getClass().getMovementSettings(actor).mPosition[0] = 0; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 88325ee7c7..344cc82058 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -4,6 +4,8 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "movement.hpp" @@ -72,6 +74,15 @@ namespace MWMechanics return MWWorld::Ptr(); // none found } + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, + std::vector* occupyingActors) + { + const auto world = MWBase::Environment::get().getWorld(); + const osg::Vec3f halfExtents = world->getPathfindingHalfExtents(actor); + const auto maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z())); + return world->isAreaOccupiedByOtherActor(destination, 2 * maxHalfExtent, actor, occupyingActors); + } + ObstacleCheck::ObstacleCheck() : mWalkState(WalkState::Initial) , mStateDuration(0) diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index b574bab67f..24bd5ed1c1 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -3,9 +3,12 @@ #include +#include + namespace MWWorld { class Ptr; + class ConstPtr; } namespace MWMechanics @@ -21,6 +24,9 @@ namespace MWMechanics /** \return Pointer to the door, or empty pointer if none exists **/ const MWWorld::Ptr getNearbyDoor(const MWWorld::Ptr& actor, float minDist); + bool isAreaOccupiedByOtherActor(const MWWorld::ConstPtr& actor, const osg::Vec3f& destination, + std::vector* occupyingActors = nullptr); + class ObstacleCheck { public: diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 7e63190edb..4b06993a49 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -397,7 +397,7 @@ namespace MWMechanics mPath.clear(); } - if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty()) + if (status != DetourNavigator::Status::NavMeshNotFound && mPath.empty() && (flags & DetourNavigator::Flag_usePathgrid) == 0) { status = buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags | DetourNavigator::Flag_usePathgrid, areaCosts, endTolerance, pathType, std::back_inserter(mPath)); diff --git a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp index 275325cf67..fc8725f5f4 100644 --- a/apps/openmw/mwphysics/hasspherecollisioncallback.hpp +++ b/apps/openmw/mwphysics/hasspherecollisioncallback.hpp @@ -22,28 +22,36 @@ namespace MWPhysics return nearest.distance(position) < radius; } + template class HasSphereCollisionCallback final : public btBroadphaseAabbCallback { public: HasSphereCollisionCallback(const btVector3& position, const btScalar radius, btCollisionObject* object, - const int mask, const int group) + const int mask, const int group, OnCollision* onCollision) : mPosition(position), mRadius(radius), mCollisionObject(object), mCollisionFilterMask(mask), - mCollisionFilterGroup(group) + mCollisionFilterGroup(group), + mOnCollision(onCollision) { } bool process(const btBroadphaseProxy* proxy) override { - if (mResult) + if (mResult && mOnCollision == nullptr) return false; const auto collisionObject = static_cast(proxy->m_clientObject); - if (collisionObject == mCollisionObject) + if (collisionObject == mCollisionObject + || !needsCollision(*proxy) + || !testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius)) return true; - if (needsCollision(*proxy)) - mResult = testAabbAgainstSphere(proxy->m_aabbMin, proxy->m_aabbMax, mPosition, mRadius); + mResult = true; + if (mOnCollision != nullptr) + { + (*mOnCollision)(collisionObject); + return true; + } return !mResult; } @@ -58,6 +66,7 @@ namespace MWPhysics btCollisionObject* mCollisionObject; int mCollisionFilterMask; int mCollisionFilterGroup; + OnCollision* mOnCollision; bool mResult = false; bool needsCollision(const btBroadphaseProxy& proxy) const diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 23ece987ef..a8c39efee2 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -920,7 +920,8 @@ namespace MWPhysics CollisionType_Actor|CollisionType_Projectile); } - bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + bool PhysicsSystem::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const { btCollisionObject* object = nullptr; const auto it = mActors.find(ignore.mRef); @@ -931,7 +932,19 @@ namespace MWPhysics const auto aabbMax = bulletPosition + btVector3(radius, radius, radius); const int mask = MWPhysics::CollisionType_Actor; const int group = 0xff; - HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group); + if (occupyingActors == nullptr) + { + HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group, + static_cast(nullptr)); + mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); + return callback.getResult(); + } + const auto onCollision = [&] (const btCollisionObject* object) + { + if (PtrHolder* holder = static_cast(object->getUserPointer())) + occupyingActors->push_back(holder->getPtr()); + }; + HasSphereCollisionCallback callback(bulletPosition, radius, object, mask, group, &onCollision); mTaskScheduler->aabbTest(aabbMin, aabbMax, callback); return callback.getResult(); } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index bc358cb3c4..4fb7a3518f 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -252,7 +252,8 @@ namespace MWPhysics std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function); } - bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const; + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const; void reportStats(unsigned int frameNumber, osg::Stats& stats) const; void reportCollision(const btVector3& position, const btVector3& normal); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index da5daed295..df7357da27 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3935,9 +3935,10 @@ namespace MWWorld return btRayAabb(localFrom, localTo, aabbMin, aabbMax, hitDistance, hitNormal); } - bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const + bool World::isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const { - return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore); + return mPhysics->isAreaOccupiedByOtherActor(position, radius, ignore, occupyingActors); } void World::reportStats(unsigned int frameNumber, osg::Stats& stats) const diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 32fc9b101a..4d9841a79a 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -735,7 +735,8 @@ namespace MWWorld bool hasCollisionWithDoor(const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const override; - bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, const MWWorld::ConstPtr& ignore) const override; + bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, + const MWWorld::ConstPtr& ignore, std::vector* occupyingActors) const override; void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;