Commit 6b535415 authored by Allofich's avatar Allofich

Prevent AI actors from hitting unintended targets

(Fixes #3254)
parent 03a10f21
......@@ -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<MWWorld::Ptr,osg::Vec3f> getHitContact(const MWWorld::ConstPtr &ptr, float distance) = 0;
virtual std::pair<MWWorld::Ptr,osg::Vec3f> getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector<MWWorld::Ptr> &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.
......
......@@ -249,7 +249,12 @@ namespace MWClass
if (!weapon.isEmpty())
dist *= weapon.get<ESM::Weapon>()->mBase->mData.mReach;
std::pair<MWWorld::Ptr, osg::Vec3f> 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<MWWorld::Ptr> targetActors;
if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer())
ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors);
if (result.first.isEmpty())
return; // Didn't hit anything
......
......@@ -566,8 +566,13 @@ namespace MWClass
weapon.get<ESM::Weapon>()->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<MWWorld::Ptr> 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<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist);
std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist, targetActors);
MWWorld::Ptr victim = result.first;
osg::Vec3f hitPosition (result.second);
if(victim.isEmpty()) // Didn't hit anything
......
......@@ -75,6 +75,17 @@ bool AiSequence::getCombatTarget(MWWorld::Ptr &targetActor) const
return !targetActor.isEmpty();
}
bool AiSequence::getCombatTargets(std::vector<MWWorld::Ptr> &targetActors) const
{
for (std::list<AiPackage*>::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<AiPackage*>::const_iterator AiSequence::begin() const
{
return mPackages.begin();
......
......@@ -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<MWWorld::Ptr> &targetActors) const;
/// Is there any combat package?
bool isInCombat () const;
......
......@@ -798,6 +798,7 @@ namespace MWPhysics
class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback
{
const btCollisionObject* mMe;
const std::vector<const btCollisionObject*> 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<const btCollisionObject*> targets, const btVector3 &origin)
: mMe(me), mTargets(targets), mOrigin(origin), mObject(NULL), mContactPoint(0,0,0),
mLeastDistSqr(std::numeric_limits<float>::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<PtrHolder*>(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<MWWorld::Ptr, osg::Vec3f> PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor,
const osg::Vec3f &origin,
const osg::Quat &orient,
float queryDistance)
float queryDistance, std::vector<MWWorld::Ptr> targets)
{
const MWWorld::Store<ESM::GameSetting> &store = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
......@@ -852,11 +863,23 @@ namespace MWPhysics
object.setWorldTransform(btTransform(toBullet(orient), toBullet(center)));
const btCollisionObject* me = NULL;
std::vector<const btCollisionObject*> targetCollisionObjects;
const Actor* physactor = getActor(actor);
if (physactor)
me = physactor->getCollisionObject();
DeepestNotMeContactTestResultCallback resultCallback(me, toBullet(origin));
if (!targets.empty())
{
for (std::vector<MWWorld::Ptr>::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<const btCollisionObject*> 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<PtrHolder*>(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<const btCollisionObject*> 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<MWWorld::Ptr> targets, int mask, int group) const
{
btVector3 btFrom = toBullet(from);
btVector3 btTo = toBullet(to);
const btCollisionObject* me = NULL;
std::vector<const btCollisionObject*> 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<MWWorld::Ptr>::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<MWWorld::Ptr>(), CollisionType_World|CollisionType_HeightMap|CollisionType_Door);
return !result.mHit;
}
......
......@@ -95,7 +95,7 @@ namespace MWPhysics
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::ConstPtr& actor,
const osg::Vec3f &origin,
const osg::Quat &orientation,
float queryDistance);
float queryDistance, std::vector<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>());
/// 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<MWWorld::Ptr> targets = std::vector<MWWorld::Ptr>(),
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);
......
......@@ -6,8 +6,10 @@
#include <components/esm/esmwriter.hpp>
#include <components/esm/projectilestate.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/controller.hpp>
#include <components/sceneutil/visitor.hpp>
#include <components/sceneutil/lightmanager.hpp>
......@@ -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<ESM::MagicEffect>().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<MWWorld::Ptr> 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<MWWorld::Ptr> 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);
......
......@@ -7,10 +7,11 @@
#include <components/esm/esmwriter.hpp>
#include <components/esm/cellid.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/rng.hpp>
#include <components/files/collections.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
......@@ -1064,7 +1065,7 @@ namespace MWWorld
return osg::Matrixf::translate(actor.getRefData().getPosition().asVec3());
}
std::pair<MWWorld::Ptr,osg::Vec3f> World::getHitContact(const MWWorld::ConstPtr &ptr, float distance)
std::pair<MWWorld::Ptr,osg::Vec3f> World::getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector<MWWorld::Ptr> &targets)
{
const ESM::Position &posdata = ptr.getRefData().getPosition();
......@@ -1084,7 +1085,7 @@ namespace MWWorld
distance += halfExtents.y();
}
std::pair<MWWorld::Ptr,osg::Vec3f> result = mPhysics->getHitContact(ptr, pos, rot, distance);
std::pair<MWWorld::Ptr,osg::Vec3f> 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<MWWorld::Ptr>(), 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<MWWorld::Ptr>(), 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<MWWorld::Ptr> 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<ESM::GameSetting>().find("fCombatDistance")->getFloat());
if (distance < dist1 && distance < dist2)
if (distance < dist1)
target = NULL;
}
......
......@@ -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<MWWorld::Ptr,osg::Vec3f> getHitContact(const MWWorld::ConstPtr &ptr, float distance);
virtual std::pair<MWWorld::Ptr,osg::Vec3f> getHitContact(const MWWorld::ConstPtr &ptr, float distance, std::vector<MWWorld::Ptr> &targets);
/// @note No-op for items in containers. Use ContainerStore::removeItem instead.
virtual void deleteObject (const Ptr& ptr);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment