From 6b5354157140891eaa2925d54192f5a300c9da48 Mon Sep 17 00:00:00 2001 From: Allofich Date: Thu, 2 Feb 2017 16:20:34 +0900 Subject: [PATCH] Prevent AI actors from hitting unintended targets (Fixes #3254) --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwclass/creature.cpp | 7 ++- apps/openmw/mwclass/npc.cpp | 7 ++- apps/openmw/mwmechanics/aisequence.cpp | 11 ++++ apps/openmw/mwmechanics/aisequence.hpp | 3 ++ apps/openmw/mwphysics/physicssystem.cpp | 65 +++++++++++++++++++---- apps/openmw/mwphysics/physicssystem.hpp | 9 ++-- apps/openmw/mwworld/projectilemanager.cpp | 22 ++++++-- apps/openmw/mwworld/worldimp.cpp | 24 +++++---- apps/openmw/mwworld/worldimp.hpp | 2 +- 10 files changed, 121 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index bba473834..59f28fcc8 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -255,7 +255,7 @@ namespace MWBase /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to /// use the "Head" node, or alternatively the "Bip01 Head" node as a basis. - virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance) = 0; + virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector &targets) = 0; virtual void adjustPosition (const MWWorld::Ptr& ptr, bool force) = 0; ///< Adjust position after load to be on ground. Must be called after model load. diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index f5b62ca4a..0be61ef51 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -249,7 +249,12 @@ namespace MWClass if (!weapon.isEmpty()) dist *= weapon.get()->mBase->mData.mReach; - std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist); + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + std::vector targetActors; + if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer()) + ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); + + std::pair result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors); if (result.first.isEmpty()) return; // Didn't hit anything diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 27d4643fa..8b102a487 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -566,8 +566,13 @@ namespace MWClass weapon.get()->mBase->mData.mReach : store.find("fHandToHandReach")->getFloat()); + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + std::vector targetActors; + if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer()) + ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors); + // TODO: Use second to work out the hit angle - std::pair result = world->getHitContact(ptr, dist); + std::pair result = world->getHitContact(ptr, dist, targetActors); MWWorld::Ptr victim = result.first; osg::Vec3f hitPosition (result.second); if(victim.isEmpty()) // Didn't hit anything diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 520bacf5a..84c8eca49 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -75,6 +75,17 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const return !targetActor.isEmpty(); } +bool AiSequence::getCombatTargets(std::vector &targetActors) const +{ + for (std::list::const_iterator it = mPackages.begin(); it != mPackages.end(); ++it) + { + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdCombat) + targetActors.push_back((*it)->getTarget()); + } + + return !targetActors.empty(); +} + std::list::const_iterator AiSequence::begin() const { return mPackages.begin(); diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index c7c32979e..4f8b542a4 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -79,6 +79,9 @@ namespace MWMechanics /// Return true and assign target if combat package is currently active, return false otherwise bool getCombatTarget (MWWorld::Ptr &targetActor) const; + /// Return true and assign targets for all combat packages, or return false if there are no combat packages + bool getCombatTargets(std::vector &targetActors) const; + /// Is there any combat package? bool isInCombat () const; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index c043c861e..de8774cfd 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -798,6 +798,7 @@ namespace MWPhysics class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback { const btCollisionObject* mMe; + const std::vector mTargets; // Store the real origin, since the shape's origin is its center btVector3 mOrigin; @@ -807,8 +808,8 @@ namespace MWPhysics btVector3 mContactPoint; btScalar mLeastDistSqr; - DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const btVector3 &origin) - : mMe(me), mOrigin(origin), mObject(NULL), mContactPoint(0,0,0), + DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const std::vector targets, const btVector3 &origin) + : mMe(me), mTargets(targets), mOrigin(origin), mObject(NULL), mContactPoint(0,0,0), mLeastDistSqr(std::numeric_limits::max()) { } @@ -819,6 +820,16 @@ namespace MWPhysics const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; if (collisionObject != mMe) { + if (!mTargets.empty()) + { + if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end())) + { + PtrHolder* holder = static_cast(collisionObject->getUserPointer()); + if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) + return 0.f; + } + } + btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); if(!mObject || distsqr < mLeastDistSqr) { @@ -835,7 +846,7 @@ namespace MWPhysics std::pair PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f &origin, const osg::Quat &orient, - float queryDistance) + float queryDistance, std::vector targets) { const MWWorld::Store &store = MWBase::Environment::get().getWorld()->getStore().get(); @@ -852,11 +863,23 @@ namespace MWPhysics object.setWorldTransform(btTransform(toBullet(orient), toBullet(center))); const btCollisionObject* me = NULL; + std::vector targetCollisionObjects; + const Actor* physactor = getActor(actor); if (physactor) me = physactor->getCollisionObject(); - DeepestNotMeContactTestResultCallback resultCallback(me, toBullet(origin)); + if (!targets.empty()) + { + for (std::vector::const_iterator it = targets.begin(); it != targets.end(); ++it) + { + const Actor* physactor2 = getActor(*it); + if (physactor2) + targetCollisionObjects.push_back(physactor2->getCollisionObject()); + } + } + + DeepestNotMeContactTestResultCallback resultCallback(me, targetCollisionObjects, toBullet(origin)); resultCallback.m_collisionFilterGroup = CollisionType_Actor; resultCallback.m_collisionFilterMask = CollisionType_World | CollisionType_Door | CollisionType_HeightMap | CollisionType_Actor; mCollisionWorld->contactTest(&object, resultCallback); @@ -903,28 +926,40 @@ namespace MWPhysics class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { public: - ClosestNotMeRayResultCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to) + ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector targets, const btVector3& from, const btVector3& to) : btCollisionWorld::ClosestRayResultCallback(from, to) - , mMe(me) + , mMe(me), mTargets(targets) { } - virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { if (rayResult.m_collisionObject == mMe) return 1.f; + if (!mTargets.empty()) + { + if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) + { + PtrHolder* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); + if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) + return 1.f; + } + } return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); } private: const btCollisionObject* mMe; + const std::vector mTargets; }; - PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::ConstPtr ignore, int mask, int group) const + PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::ConstPtr ignore, std::vector targets, int mask, int group) const { btVector3 btFrom = toBullet(from); btVector3 btTo = toBullet(to); const btCollisionObject* me = NULL; + std::vector targetCollisionObjects; + if (!ignore.isEmpty()) { const Actor* actor = getActor(ignore); @@ -938,7 +973,17 @@ namespace MWPhysics } } - ClosestNotMeRayResultCallback resultCallback(me, btFrom, btTo); + if (!targets.empty()) + { + for (std::vector::const_iterator it = targets.begin(); it != targets.end(); ++it) + { + const Actor* actor = getActor(*it); + if (actor) + targetCollisionObjects.push_back(actor->getCollisionObject()); + } + } + + ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo); resultCallback.m_collisionFilterGroup = group; resultCallback.m_collisionFilterMask = mask; @@ -991,7 +1036,7 @@ namespace MWPhysics osg::Vec3f pos1 (physactor1->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor1->getHalfExtents().z() * 0.9)); // eye level osg::Vec3f pos2 (physactor2->getCollisionObjectPosition() + osg::Vec3f(0,0,physactor2->getHalfExtents().z() * 0.9)); - RayResult result = castRay(pos1, pos2, MWWorld::Ptr(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); + RayResult result = castRay(pos1, pos2, MWWorld::ConstPtr(), std::vector(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door); return !result.mHit; } diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ec12f7681..9730cda74 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -95,7 +95,7 @@ namespace MWPhysics std::pair getHitContact(const MWWorld::ConstPtr& actor, const osg::Vec3f &origin, const osg::Quat &orientation, - float queryDistance); + float queryDistance, std::vector targets = std::vector()); /// Get distance from \a point to the collision shape of \a target. Uses a raycast to find where the @@ -112,9 +112,10 @@ namespace MWPhysics MWWorld::Ptr mHitObject; }; - /// @param me Optional, a Ptr to ignore in the list of results - RayResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::ConstPtr ignore = MWWorld::ConstPtr(), int mask = - CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const; + /// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors. + RayResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, MWWorld::ConstPtr ignore = MWWorld::ConstPtr(), + std::vector targets = std::vector(), + int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff) const; RayResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius); diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3f0c0e54e..5afdce700 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -6,8 +6,10 @@ #include #include + #include #include + #include #include #include @@ -25,6 +27,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/actorutil.hpp" +#include "../mwmechanics/aipackage.hpp" #include "../mwrender/effectmanager.hpp" #include "../mwrender/animation.hpp" @@ -49,8 +52,8 @@ namespace const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( iter->mEffectID); - // All the projectiles should use the same speed. From observations in the - // original engine, this seems to be the average of the constituent effects. + // Speed of multi-effect projectiles should be the average of the constituent effects, + // based on observation of the original engine. speed += magicEffect->mData.mSpeed; count++; @@ -337,9 +340,14 @@ namespace MWWorld MWWorld::Ptr caster = it->getCaster(); + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + std::vector targetActors; + if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) + caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); + // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, 0xff, MWPhysics::CollisionType_Projectile); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile); bool hit = false; if (result.mHit) @@ -404,11 +412,17 @@ namespace MWWorld MWWorld::Ptr caster = it->getCaster(); + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + std::vector targetActors; + if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer()) + caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors); + // Check for impact // TODO: use a proper btRigidBody / btGhostObject? - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, 0xff, MWPhysics::CollisionType_Projectile); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile); bool underwater = MWBase::Environment::get().getWorld()->isUnderwater(MWMechanics::getPlayer().getCell(), newPos); + if (result.mHit || underwater) { MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mIdArrow); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index dcec8ea85..d03fa263c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -7,10 +7,11 @@ #include #include +#include #include #include -#include + #include #include @@ -1064,7 +1065,7 @@ namespace MWWorld return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3()); } - std::pair World::getHitContact(const MWWorld::ConstPtr &ptr, float distance) + std::pair World::getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector &targets) { const ESM::Position &posdata = ptr.getRefData().getPosition(); @@ -1084,7 +1085,7 @@ namespace MWWorld distance += halfExtents.y(); } - std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance); + std::pair result = mPhysics->getHitContact(ptr, pos, rot, distance, targets); if(result.first.isEmpty()) return std::make_pair(MWWorld::Ptr(), osg::Vec3f()); @@ -1450,7 +1451,7 @@ namespace MWWorld { osg::Vec3f a(x1,y1,z1); osg::Vec3f b(x2,y2,z2); - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), MWPhysics::CollisionType_World|MWPhysics::CollisionType_Door); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(a, b, MWWorld::Ptr(), std::vector(), MWPhysics::CollisionType_World|MWPhysics::CollisionType_Door); return result.mHit; } @@ -2443,7 +2444,7 @@ namespace MWWorld if (includeWater) { collisionTypes |= MWPhysics::CollisionType_Water; } - MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), collisionTypes); + MWPhysics::PhysicsSystem::RayResult result = mPhysics->castRay(from, to, MWWorld::Ptr(), std::vector(), collisionTypes); if (!result.mHit) return maxDist; @@ -2664,12 +2665,17 @@ namespace MWWorld osg::Vec3f direction = orient * osg::Vec3f(0,1,0); osg::Vec3f dest = origin + direction * distance; + // For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result. + std::vector targetActors; + if (!actor.isEmpty() && actor != MWMechanics::getPlayer()) + actor.getClass().getCreatureStats(actor).getAiSequence().getCombatTargets(targetActors); + // For actor targets, we want to use bounding boxes (physics raycast). // This is to give a slight tolerance for errors, especially with creatures like the Skeleton that would be very hard to aim at otherwise. // For object targets, we want the detailed shapes (rendering raycast). // If we used the bounding boxes for static objects, then we would not be able to target e.g. objects lying on a shelf. - MWPhysics::PhysicsSystem::RayResult result1 = mPhysics->castRay(origin, dest, actor, MWPhysics::CollisionType_Actor); + MWPhysics::PhysicsSystem::RayResult result1 = mPhysics->castRay(origin, dest, actor, targetActors, MWPhysics::CollisionType_Actor); MWRender::RenderingManager::RayResult result2 = mRendering->castRay(origin, dest, true, true); @@ -2681,7 +2687,7 @@ namespace MWWorld if (result2.mHit) dist2 = (origin - result2.mHitPointWorld).length(); - if (dist1 <= dist2 && result1.mHit) + if (result1.mHit) { target = result1.mHitObject; hitPosition = result1.mHitPos; @@ -2692,7 +2698,7 @@ namespace MWWorld { target = result2.mHitObject; hitPosition = result2.mHitPointWorld; - if (dist2 > getMaxActivationDistance() && !target.isEmpty() && (target.getClass().isActor() || !target.getClass().canBeActivated(target))) + if (dist2 > getMaxActivationDistance() && !target.isEmpty() && !target.getClass().canBeActivated(target)) target = NULL; } @@ -2702,7 +2708,7 @@ namespace MWWorld if (!target.isEmpty() && target.getClass().isActor() && target.getClass().getCreatureStats (target).getAiSequence().isInCombat()) { distance = std::min (distance, getStore().get().find("fCombatDistance")->getFloat()); - if (distance < dist1 && distance < dist2) + if (distance < dist1) target = NULL; } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4b86ec14f..5d69f2e12 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -354,7 +354,7 @@ namespace MWWorld /// Returns a pointer to the object the provided object would hit (if within the /// specified distance), and the point where the hit occurs. This will attempt to /// use the "Head" node as a basis. - virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance); + virtual std::pair getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector &targets); /// @note No-op for items in containers. Use ContainerStore::removeItem instead. virtual void deleteObject (const Ptr& ptr);