From b39437dfb63156fa32a93515d0404e01efdf70e2 Mon Sep 17 00:00:00 2001 From: fredzio Date: Mon, 14 Dec 2020 22:23:01 +0100 Subject: [PATCH] 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. --- .../closestnotmeconvexresultcallback.cpp | 7 +-- apps/openmw/mwphysics/physicssystem.cpp | 7 +++ apps/openmw/mwphysics/projectile.cpp | 48 +++++++++++++++++-- apps/openmw/mwphysics/projectile.hpp | 10 +++- apps/openmw/mwworld/projectilemanager.cpp | 36 +++----------- apps/openmw/mwworld/projectilemanager.hpp | 2 - 6 files changed, 68 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp index 8f7242276..3ec09d00d 100644 --- a/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp @@ -30,12 +30,9 @@ namespace MWPhysics return btScalar(1); auto* targetHolder = static_cast(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; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 8106a78ea..151801e67 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -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) diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 5f5bc778b..a8aaeb72a 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -55,7 +55,7 @@ Projectile::~Projectile() void Projectile::commitPositionChange() { - std::unique_lock 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 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 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& 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; +} + } diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index ed0fdce15..5ae963b9a 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -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& targets); + bool isValidTarget(const MWWorld::Ptr& target) const; + private: std::unique_ptr mShape; @@ -87,7 +91,9 @@ namespace MWPhysics btVector3 mHitPosition; btVector3 mHitNormal; - mutable std::mutex mPositionMutex; + std::vector mValidTargets; + + mutable std::mutex mMutex; osg::Vec3f mPosition; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index dba4289a2..08c9e4662 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -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 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 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 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); diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index d589e985e..2cd570847 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -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);