mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-21 09:23:51 +00:00
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.
This commit is contained in:
parent
c8d85dcf30
commit
b39437dfb6
6 changed files with 68 additions and 42 deletions
|
@ -30,12 +30,9 @@ namespace MWPhysics
|
||||||
return btScalar(1);
|
return btScalar(1);
|
||||||
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
|
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer());
|
||||||
const MWWorld::Ptr target = targetHolder->getPtr();
|
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->isValidTarget(target))
|
||||||
if (projectileHolder->getCaster() != target)
|
|
||||||
{
|
|
||||||
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
|
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
|
||||||
return btScalar(1);
|
return btScalar(1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
btVector3 hitNormalWorld;
|
btVector3 hitNormalWorld;
|
||||||
|
|
|
@ -537,6 +537,13 @@ namespace MWPhysics
|
||||||
if (actor->getStandingOnPtr() == old)
|
if (actor->getStandingOnPtr() == old)
|
||||||
actor->setStandingOnPtr(updated);
|
actor->setStandingOnPtr(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto& [_, projectile] : mProjectiles)
|
||||||
|
{
|
||||||
|
if (projectile->getCaster() == old)
|
||||||
|
projectile->setCaster(updated);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr)
|
Actor *PhysicsSystem::getActor(const MWWorld::Ptr &ptr)
|
||||||
|
|
|
@ -55,7 +55,7 @@ Projectile::~Projectile()
|
||||||
|
|
||||||
void Projectile::commitPositionChange()
|
void Projectile::commitPositionChange()
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
std::scoped_lock lock(mMutex);
|
||||||
if (mTransformUpdatePending)
|
if (mTransformUpdatePending)
|
||||||
{
|
{
|
||||||
mCollisionObject->setWorldTransform(mLocalTransform);
|
mCollisionObject->setWorldTransform(mLocalTransform);
|
||||||
|
@ -65,7 +65,7 @@ void Projectile::commitPositionChange()
|
||||||
|
|
||||||
void Projectile::setPosition(const osg::Vec3f &position)
|
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));
|
mLocalTransform.setOrigin(Misc::Convert::toBullet(position));
|
||||||
mTransformUpdatePending = true;
|
mTransformUpdatePending = true;
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ void Projectile::hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal)
|
||||||
{
|
{
|
||||||
if (!mActive.load(std::memory_order_acquire))
|
if (!mActive.load(std::memory_order_acquire))
|
||||||
return;
|
return;
|
||||||
std::unique_lock<std::mutex> lock(mPositionMutex);
|
std::scoped_lock lock(mMutex);
|
||||||
mHitTarget = target;
|
mHitTarget = target;
|
||||||
mHitPosition = pos;
|
mHitPosition = pos;
|
||||||
mHitNormal = normal;
|
mHitNormal = normal;
|
||||||
|
@ -86,4 +86,46 @@ void Projectile::activate()
|
||||||
assert(!mActive);
|
assert(!mActive);
|
||||||
mActive.store(true, std::memory_order_release);
|
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;
|
return mHitTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
MWWorld::Ptr getCaster() const { return mCaster; }
|
MWWorld::Ptr getCaster() const;
|
||||||
|
void setCaster(MWWorld::Ptr caster);
|
||||||
|
|
||||||
osg::Vec3f getHitPos() const
|
osg::Vec3f getHitPos() const
|
||||||
{
|
{
|
||||||
|
@ -73,6 +74,9 @@ namespace MWPhysics
|
||||||
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
|
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
|
||||||
void activate();
|
void activate();
|
||||||
|
|
||||||
|
void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
|
||||||
|
bool isValidTarget(const MWWorld::Ptr& target) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
std::unique_ptr<btCollisionShape> mShape;
|
std::unique_ptr<btCollisionShape> mShape;
|
||||||
|
@ -87,7 +91,9 @@ namespace MWPhysics
|
||||||
btVector3 mHitPosition;
|
btVector3 mHitPosition;
|
||||||
btVector3 mHitNormal;
|
btVector3 mHitNormal;
|
||||||
|
|
||||||
mutable std::mutex mPositionMutex;
|
std::vector<MWWorld::Ptr> mValidTargets;
|
||||||
|
|
||||||
|
mutable std::mutex mMutex;
|
||||||
|
|
||||||
osg::Vec3f mPosition;
|
osg::Vec3f mPosition;
|
||||||
|
|
||||||
|
|
|
@ -387,7 +387,7 @@ namespace MWWorld
|
||||||
if (magicBoltState.mToDelete)
|
if (magicBoltState.mToDelete)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
|
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
|
||||||
if (!projectile->isActive())
|
if (!projectile->isActive())
|
||||||
continue;
|
continue;
|
||||||
// If the actor caster is gone, the magic bolt needs to be removed from the scene during the next frame.
|
// 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;
|
std::vector<MWWorld::Ptr> targetActors;
|
||||||
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
|
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
|
||||||
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
|
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
|
||||||
|
projectile->setValidTargets(targetActors);
|
||||||
|
|
||||||
// Check for impact
|
// Check for impact
|
||||||
// TODO: use a proper btRigidBody / btGhostObject?
|
// TODO: use a proper btRigidBody / btGhostObject?
|
||||||
|
@ -469,7 +470,7 @@ namespace MWWorld
|
||||||
if (projectileState.mToDelete)
|
if (projectileState.mToDelete)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
|
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
|
||||||
if (!projectile->isActive())
|
if (!projectile->isActive())
|
||||||
continue;
|
continue;
|
||||||
// gravity constant - must be way lower than the gravity affecting actors, since we're not
|
// 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;
|
std::vector<MWWorld::Ptr> targetActors;
|
||||||
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
|
if (!caster.isEmpty() && caster.getClass().isActor() && caster != MWMechanics::getPlayer())
|
||||||
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
|
caster.getClass().getCreatureStats(caster).getAiSequence().getCombatTargets(targetActors);
|
||||||
|
projectile->setValidTargets(targetActors);
|
||||||
|
|
||||||
// Check for impact
|
// Check for impact
|
||||||
// TODO: use a proper btRigidBody / btGhostObject?
|
// TODO: use a proper btRigidBody / btGhostObject?
|
||||||
|
@ -547,7 +549,7 @@ namespace MWWorld
|
||||||
const auto pos = projectile->getHitPos();
|
const auto pos = projectile->getHitPos();
|
||||||
MWWorld::Ptr caster = projectileState.getCaster();
|
MWWorld::Ptr caster = projectileState.getCaster();
|
||||||
assert(target != caster);
|
assert(target != caster);
|
||||||
if (!isValidTarget(caster, target))
|
if (!projectile->isValidTarget(target))
|
||||||
{
|
{
|
||||||
projectile->activate();
|
projectile->activate();
|
||||||
continue;
|
continue;
|
||||||
|
@ -583,7 +585,7 @@ namespace MWWorld
|
||||||
const auto pos = projectile->getHitPos();
|
const auto pos = projectile->getHitPos();
|
||||||
MWWorld::Ptr caster = magicBoltState.getCaster();
|
MWWorld::Ptr caster = magicBoltState.getCaster();
|
||||||
assert(target != caster);
|
assert(target != caster);
|
||||||
if (!isValidTarget(caster, target))
|
if (!projectile->isValidTarget(target))
|
||||||
{
|
{
|
||||||
projectile->activate();
|
projectile->activate();
|
||||||
continue;
|
continue;
|
||||||
|
@ -607,32 +609,6 @@ namespace MWWorld
|
||||||
mMagicBolts.end());
|
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)
|
void ProjectileManager::cleanupProjectile(ProjectileManager::ProjectileState& state)
|
||||||
{
|
{
|
||||||
mParent->removeChild(state.mNode);
|
mParent->removeChild(state.mNode);
|
||||||
|
|
|
@ -132,8 +132,6 @@ namespace MWWorld
|
||||||
void moveProjectiles(float dt);
|
void moveProjectiles(float dt);
|
||||||
void moveMagicBolts(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,
|
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 = "");
|
bool rotate, bool createLight, osg::Vec4 lightDiffuseColor, std::string texture = "");
|
||||||
void update (State& state, float duration);
|
void update (State& state, float duration);
|
||||||
|
|
Loading…
Reference in a new issue