Don't allow projectiles to stand still when they hit an ally.

When an NPC fire a projectile, it should affect only its targeted actor.
To this end, after a hit is detected the target is checked against the
list of AI targets and reactivated if necessary.
Problem occurs when the hit occurs as a result of a friendly actor going
into the projectile (detected in ClosestNotMeConvexResultCallback):
while the projectile is inside the friend's collision box, it is
deactivated, just to be immediately reactivated. Effectively, the
projectile does nothing until the actor moves out.

Add a check inside the ClosestNotMeConvexResultCallback before declaring
a hit.
Since the necessary data is not safely accessible from the async thread,
maintain a copy inside the Projectile class.
pull/593/head
fredzio 4 years ago
parent c8d85dcf30
commit b39437dfb6

@ -30,12 +30,9 @@ namespace MWPhysics
return btScalar(1);
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
const MWWorld::Ptr target = targetHolder->getPtr();
// do nothing if we hit the caster. Sometimes the launching origin is inside of caster collision shape
if (projectileHolder->getCaster() != target)
{
if (projectileHolder->isValidTarget(target))
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
return btScalar(1);
}
return btScalar(1);
}
btVector3 hitNormalWorld;

@ -537,6 +537,13 @@ namespace MWPhysics
if (actor->getStandingOnPtr() == old)
actor->setStandingOnPtr(updated);
}
for (auto& [_, projectile] : mProjectiles)
{
if (projectile->getCaster() == old)
projectile->setCaster(updated);
}
}
Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr)

@ -55,7 +55,7 @@ Projectile::~Projectile()
void Projectile::commitPositionChange()
{
std::unique_lock<std::mutex> lock(mPositionMutex);
std::scoped_lock lock(mMutex);
if (mTransformUpdatePending)
{
mCollisionObject->setWorldTransform(mLocalTransform);
@ -65,7 +65,7 @@ void Projectile::commitPositionChange()
void Projectile::setPosition(const osg::Vec3f &position)
{
std::unique_lock<std::mutex> lock(mPositionMutex);
std::scoped_lock lock(mMutex);
mLocalTransform.setOrigin(Misc::Convert::toBullet(position));
mTransformUpdatePending = true;
}
@ -74,7 +74,7 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
{
if (!mActive.load(std::memory_order_acquire))
return;
std::unique_lock<std::mutex> lock(mPositionMutex);
std::scoped_lock lock(mMutex);
mHitTarget = target;
mHitPosition = pos;
mHitNormal = normal;
@ -86,4 +86,46 @@ void Projectile::activate()
assert(!mActive);
mActive.store(true, std::memory_order_release);
}
MWWorld::Ptr Projectile::getCaster() const
{
std::scoped_lock lock(mMutex);
return mCaster;
}
void Projectile::setCaster(MWWorld::Ptr caster)
{
std::scoped_lock lock(mMutex);
mCaster = caster;
}
void Projectile::setValidTargets(const std::vector<MWWorld::Ptr>& targets)
{
std::scoped_lock lock(mMutex);
mValidTargets = targets;
}
bool Projectile::isValidTarget(const MWWorld::Ptr& target) const
{
std::scoped_lock lock(mMutex);
if (mCaster == target)
return false;
if (!mValidTargets.empty())
{
bool validTarget = false;
for (const auto& targetActor : mValidTargets)
{
if (targetActor == target)
{
validTarget = true;
break;
}
}
return validTarget;
}
return true;
}
}

@ -62,7 +62,8 @@ namespace MWPhysics
return mHitTarget;
}
MWWorld::Ptr getCaster() const { return mCaster; }
MWWorld::Ptr getCaster() const;
void setCaster(MWWorld::Ptr caster);
osg::Vec3f getHitPos() const
{
@ -73,6 +74,9 @@ namespace MWPhysics
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
void activate();
void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
bool isValidTarget(const MWWorld::Ptr& target) const;
private:
std::unique_ptr<btCollisionShape> mShape;
@ -87,7 +91,9 @@ namespace MWPhysics
btVector3 mHitPosition;
btVector3 mHitNormal;
mutable std::mutex mPositionMutex;
std::vector<MWWorld::Ptr> mValidTargets;
mutable std::mutex mMutex;
osg::Vec3f mPosition;

@ -387,7 +387,7 @@ namespace MWWorld
if (magicBoltState.mToDelete)
continue;
const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (!projectile->isActive())
continue;
// If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame.
@ -423,6 +423,7 @@ namespace MWWorld
std::vector<MWWorld::Ptr> targetActors;
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
@ -469,7 +470,7 @@ namespace MWWorld
if (projectileState.mToDelete)
continue;
const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (!projectile->isActive())
continue;
// gravity constant - must be way lower than the gravity affecting actors, since we're not
@ -499,6 +500,7 @@ namespace MWWorld
std::vector<MWWorld::Ptr> targetActors;
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
projectile->setValidTargets(targetActors);
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
@ -547,7 +549,7 @@ namespace MWWorld
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = projectileState.getCaster();
assert(target != caster);
if (!isValidTarget(caster, target))
if (!projectile->isValidTarget(target))
{
projectile->activate();
continue;
@ -583,7 +585,7 @@ namespace MWWorld
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = magicBoltState.getCaster();
assert(target != caster);
if (!isValidTarget(caster, target))
if (!projectile->isValidTarget(target))
{
projectile->activate();
continue;
@ -607,32 +609,6 @@ namespace MWWorld
mMagicBolts.end());
}
bool ProjectileManager::isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{
// 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);
if (!targetActors.empty())
{
bool validTarget = false;
for (MWWorld::Ptr& targetActor : targetActors)
{
if (targetActor == target)
{
validTarget = true;
break;
}
}
return validTarget;
}
}
return true;
}
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
{
mParent->removeChild(state.mNode);

@ -132,8 +132,6 @@ namespace MWWorld
void moveProjectiles(float dt);
void moveMagicBolts(float dt);
bool isValidTarget(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
void createModel (State& state, const std::string& model, const osg::Vec3f& pos, const osg::Quat& orient,
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = "");
void update (State& state, float duration);

Loading…
Cancel
Save