Merge branch 'projectiles_collisions_mk2' into 'master'

Projectiles collisions

See merge request OpenMW/openmw!369
pull/593/head
psi29a 4 years ago
commit 8d8a4a890c

@ -8,6 +8,7 @@
Bug #2473: Unable to overstock merchants
Bug #2798: Mutable ESM records
Bug #2976 [reopened]: Issues combining settings from the command line and both config files
Bug #3372: Projectiles and magic bolts go through moving targets
Bug #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #3789: Crash in visitEffectSources while in battle

@ -66,13 +66,13 @@ add_openmw_dir (mwworld
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
actionequip timestamp actionalchemy cellstore actionapply actioneat
store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
contentloader esmloader actiontrap cellreflist cellref physicssystem weather projectilemanager
contentloader esmloader actiontrap cellreflist cellref weather projectilemanager
cellpreloader datetimemanager
)
add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
closestnotmeconvexresultcallback raycasting mtphysics
)

@ -70,7 +70,7 @@ Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, Physic
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
mCollisionObject->setCollisionShape(mShape.get());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
mCollisionObject->setUserPointer(this);
updateRotation();
updateScale();

@ -2,6 +2,14 @@
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include <components/misc/convert.hpp>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
namespace MWPhysics
{
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot)
@ -10,11 +18,26 @@ namespace MWPhysics
{
}
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace)
{
if (convexResult.m_hitCollisionObject == mMe)
return btScalar(1);
if (convexResult.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Projectile)
{
auto* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer());
if (!projectileHolder->isActive())
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)
{
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
return btScalar(1);
}
}
btVector3 hitNormalWorld;
if (normalInWorldSpace)
hitNormalWorld = convexResult.m_hitNormalLocal;

@ -1,18 +1,22 @@
#include "closestnotmerayresultcallback.hpp"
#include <algorithm>
#include <utility>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
#include "ptrholder.hpp"
namespace MWPhysics
{
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to)
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj)
: btCollisionWorld::ClosestRayResultCallback(from, to)
, mMe(me), mTargets(targets)
, mMe(me), mTargets(std::move(targets)), mProjectile(proj)
{
}
@ -24,11 +28,27 @@ namespace MWPhysics
{
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
{
PtrHolder* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
auto* 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);
btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
if (mProjectile)
{
auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
if (auto* target = dynamic_cast<Actor*>(holder))
{
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
}
else if (auto* target = dynamic_cast<Projectile*>(holder))
{
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld);
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
}
}
return rayResult.m_hitFraction;
}
}

@ -9,15 +9,18 @@ class btCollisionObject;
namespace MWPhysics
{
class Projectile;
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
{
public:
ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to);
ClosestNotMeRayResultCallback(const btCollisionObject* me, std::vector<const btCollisionObject*> targets, const btVector3& from, const btVector3& to, Projectile* proj=nullptr);
btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
private:
const btCollisionObject* mMe;
const std::vector<const btCollisionObject*> mTargets;
Projectile* mProjectile;
};
}

@ -17,6 +17,7 @@
#include "mtphysics.hpp"
#include "object.hpp"
#include "physicssystem.hpp"
#include "projectile.hpp"
namespace
{
@ -455,6 +456,11 @@ namespace MWPhysics
object->commitPositionChange();
mCollisionWorld->updateSingleAabb(object->getCollisionObject());
}
else if (const auto projectile = std::dynamic_pointer_cast<Projectile>(p))
{
projectile->commitPositionChange();
mCollisionWorld->updateSingleAabb(projectile->getCollisionObject());
}
};
}

@ -24,7 +24,7 @@ namespace MWPhysics
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
mCollisionObject->setUserPointer(this);
setScale(ptr.getCellRef().getScale());
setRotation(Misc::Convert::toBullet(ptr.getRefData().getBaseNode()->getAttitude()));

@ -46,6 +46,8 @@
#include "collisiontype.hpp"
#include "actor.hpp"
#include "projectile.hpp"
#include "trace.h"
#include "object.hpp"
#include "heightfield.hpp"
@ -64,6 +66,7 @@ namespace MWPhysics
, mResourceSystem(resourceSystem)
, mDebugDrawEnabled(false)
, mTimeAccum(0.0f)
, mProjectileId(0)
, mWaterHeight(0)
, mWaterEnabled(false)
, mParentNode(parentNode)
@ -112,7 +115,7 @@ namespace MWPhysics
mObjects.clear();
mActors.clear();
mProjectiles.clear();
}
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
@ -248,7 +251,7 @@ namespace MWPhysics
return 0.f;
}
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const
RayCastingResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group, int projId) const
{
if (from == to)
{
@ -285,7 +288,7 @@ namespace MWPhysics
}
}
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo);
ClosestNotMeRayResultCallback resultCallback(me, targetCollisionObjects, btFrom, btTo, getProjectile(projId));
resultCallback.m_collisionFilterGroup = group;
resultCallback.m_collisionFilterMask = mask;
@ -502,6 +505,13 @@ namespace MWPhysics
}
}
void PhysicsSystem::removeProjectile(const int projectileId)
{
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
if (foundProjectile != mProjectiles.end())
mProjectiles.erase(foundProjectile);
}
void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
{
ObjectMap::iterator found = mObjects.find(old);
@ -553,6 +563,14 @@ namespace MWPhysics
return nullptr;
}
Projectile* PhysicsSystem::getProjectile(int projectileId) const
{
ProjectileMap::const_iterator found = mProjectiles.find(projectileId);
if (found != mProjectiles.end())
return found->second.get();
return nullptr;
}
void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
@ -572,6 +590,17 @@ namespace MWPhysics
}
}
void PhysicsSystem::updateProjectile(const int projectileId, const osg::Vec3f &position)
{
ProjectileMap::iterator foundProjectile = mProjectiles.find(projectileId);
if (foundProjectile != mProjectiles.end())
{
foundProjectile->second->setPosition(position);
mTaskScheduler->updateSingleAabb(foundProjectile->second);
return;
}
}
void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
{
ObjectMap::iterator found = mObjects.find(ptr);
@ -632,6 +661,15 @@ namespace MWPhysics
mActors.emplace(ptr, std::move(actor));
}
int PhysicsSystem::addProjectile (const MWWorld::Ptr& caster, const osg::Vec3f& position)
{
mProjectileId++;
auto projectile = std::make_shared<Projectile>(mProjectileId, caster, position, mTaskScheduler.get(), this);
mProjectiles.emplace(mProjectileId, std::move(projectile));
return mProjectileId;
}
bool PhysicsSystem::toggleCollisionMode()
{
ActorMap::iterator found = mActors.find(MWMechanics::getPlayer());

@ -56,6 +56,7 @@ namespace MWPhysics
class Object;
class Actor;
class PhysicsTaskScheduler;
class Projectile;
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>;
@ -127,6 +128,10 @@ namespace MWPhysics
void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World);
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh);
int addProjectile(const MWWorld::Ptr& caster, const osg::Vec3f& position);
void updateProjectile(const int projectileId, const osg::Vec3f &position);
void removeProjectile(const int projectileId);
void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
Actor* getActor(const MWWorld::Ptr& ptr);
@ -134,6 +139,8 @@ namespace MWPhysics
const Object* getObject(const MWWorld::ConstPtr& ptr) const;
Projectile* getProjectile(int projectileId) const;
// Object or Actor
void remove (const MWWorld::Ptr& ptr);
@ -141,7 +148,6 @@ namespace MWPhysics
void updateRotation (const MWWorld::Ptr& ptr);
void updatePosition (const MWWorld::Ptr& ptr);
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
void removeHeightField (int x, int y);
@ -172,7 +178,7 @@ namespace MWPhysics
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const 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 override;
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const override;
RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override;
@ -269,6 +275,9 @@ namespace MWPhysics
ActorMap mActors;
using ProjectileMap = std::map<int, std::shared_ptr<Projectile>>;
ProjectileMap mProjectiles;
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
HeightFieldMap mHeightFields;
@ -279,6 +288,8 @@ namespace MWPhysics
float mTimeAccum;
unsigned int mProjectileId;
float mWaterHeight;
bool mWaterEnabled;

@ -0,0 +1,89 @@
#include <memory>
#include <BulletCollision/CollisionShapes/btSphereShape.h>
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <LinearMath/btVector3.h>
#include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp>
#include <components/resource/bulletshape.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include "../mwworld/class.hpp"
#include "collisiontype.hpp"
#include "mtphysics.hpp"
#include "projectile.hpp"
namespace MWPhysics
{
Projectile::Projectile(int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem)
: mActive(true)
, mCaster(caster)
, mPhysics(physicssystem)
, mTaskScheduler(scheduler)
, mProjectileId(projectileId)
{
mShape.reset(new btSphereShape(1.f));
mConvexShape = static_cast<btConvexShape*>(mShape.get());
mCollisionObject.reset(new btCollisionObject);
mCollisionObject->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
mCollisionObject->setActivationState(DISABLE_DEACTIVATION);
mCollisionObject->setCollisionShape(mShape.get());
mCollisionObject->setUserPointer(this);
setPosition(position);
const int collisionMask = CollisionType_World | CollisionType_HeightMap |
CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile;
mTaskScheduler->addCollisionObject(mCollisionObject.get(), CollisionType_Projectile, collisionMask);
commitPositionChange();
}
Projectile::~Projectile()
{
if (mCollisionObject)
{
if (!mActive)
mPhysics->reportCollision(mHitPosition, mHitNormal);
mTaskScheduler->removeCollisionObject(mCollisionObject.get());
}
}
void Projectile::commitPositionChange()
{
std::unique_lock<std::mutex> lock(mPositionMutex);
if (mTransformUpdatePending)
{
mCollisionObject->setWorldTransform(mLocalTransform);
mTransformUpdatePending = false;
}
}
void Projectile::setPosition(const osg::Vec3f &position)
{
std::unique_lock<std::mutex> lock(mPositionMutex);
mLocalTransform.setOrigin(Misc::Convert::toBullet(position));
mTransformUpdatePending = true;
}
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);
mHitTarget = target;
mHitPosition = pos;
mHitNormal = normal;
mActive.store(false, std::memory_order_release);
}
void Projectile::activate()
{
assert(!mActive);
mActive.store(true, std::memory_order_release);
}
}

@ -0,0 +1,106 @@
#ifndef OPENMW_MWPHYSICS_PROJECTILE_H
#define OPENMW_MWPHYSICS_PROJECTILE_H
#include <atomic>
#include <memory>
#include <mutex>
#include "components/misc/convert.hpp"
#include "ptrholder.hpp"
class btCollisionObject;
class btCollisionShape;
class btConvexShape;
class btVector3;
namespace osg
{
class Vec3f;
}
namespace Resource
{
class BulletShape;
}
namespace MWPhysics
{
class PhysicsTaskScheduler;
class PhysicsSystem;
class Projectile final : public PtrHolder
{
public:
Projectile(const int projectileId, const MWWorld::Ptr& caster, const osg::Vec3f& position, PhysicsTaskScheduler* scheduler, PhysicsSystem* physicssystem);
~Projectile() override;
btConvexShape* getConvexShape() const { return mConvexShape; }
void commitPositionChange();
void setPosition(const osg::Vec3f& position);
btCollisionObject* getCollisionObject() const
{
return mCollisionObject.get();
}
int getProjectileId() const
{
return mProjectileId;
}
bool isActive() const
{
return mActive.load(std::memory_order_acquire);
}
MWWorld::Ptr getTarget() const
{
assert(!mActive);
return mHitTarget;
}
MWWorld::Ptr getCaster() const { return mCaster; }
osg::Vec3f getHitPos() const
{
assert(!mActive);
return Misc::Convert::toOsg(mHitPosition);
}
void hit(MWWorld::Ptr target, btVector3 pos, btVector3 normal);
void activate();
private:
std::unique_ptr<btCollisionShape> mShape;
btConvexShape* mConvexShape;
std::unique_ptr<btCollisionObject> mCollisionObject;
btTransform mLocalTransform;
bool mTransformUpdatePending;
std::atomic<bool> mActive;
MWWorld::Ptr mCaster;
MWWorld::Ptr mHitTarget;
btVector3 mHitPosition;
btVector3 mHitNormal;
mutable std::mutex mPositionMutex;
osg::Vec3f mPosition;
PhysicsSystem *mPhysics;
PhysicsTaskScheduler *mTaskScheduler;
Projectile(const Projectile&);
Projectile& operator=(const Projectile&);
int mProjectileId;
};
}
#endif

@ -29,7 +29,7 @@ namespace MWPhysics
/// @param me Optional, a Ptr to ignore in the list of results. targets are actors to filter for, ignoring all other actors.
virtual RayCastingResult castRay(const osg::Vec3f &from, const osg::Vec3f &to, const 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 = 0;
int mask = CollisionType_World|CollisionType_HeightMap|CollisionType_Actor|CollisionType_Door, int group=0xff, int projId=-1) const = 0;
virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0;

@ -5,6 +5,9 @@
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
#include <BulletCollision/CollisionShapes/btConvexShape.h>
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "collisiontype.hpp"
#include "actor.hpp"
#include "closestnotmeconvexresultcallback.hpp"

@ -2,6 +2,7 @@
#include <iomanip>
#include <memory>
#include <osg/PositionAttitudeTransform>
#include <components/debug/debuglog.hpp>
@ -43,6 +44,7 @@
#include "../mwsound/sound.hpp"
#include "../mwphysics/physicssystem.hpp"
#include "../mwphysics/projectile.hpp"
namespace
{
@ -284,7 +286,7 @@ namespace MWWorld
else
state.mActorId = -1;
std::string texture = "";
std::string texture;
state.mEffects = getMagicBoltData(state.mIdMagic, state.mSoundIds, state.mSpeed, texture, state.mSourceName, state.mSpellId);
@ -302,6 +304,7 @@ namespace MWWorld
MWWorld::Ptr ptr = ref.getPtr();
osg::Vec4 lightDiffuseColor = getMagicBoltLightDiffuseColor(state.mEffects);
createModel(state, ptr.getClass().getModel(ptr), pos, orient, true, true, lightDiffuseColor, texture);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
@ -312,7 +315,9 @@ namespace MWWorld
if (sound)
state.mSounds.push_back(sound);
}
state.mProjectileId = mPhysics->addProjectile(caster, pos);
state.mToDelete = false;
mMagicBolts.push_back(state);
}
@ -325,7 +330,6 @@ namespace MWWorld
state.mIdArrow = projectile.getCellRef().getRefId();
state.mCasterHandle = actor;
state.mAttackStrength = attackStrength;
int type = projectile.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(type)->mWeaponClass == ESM::WeaponType::Thrown;
@ -336,6 +340,8 @@ namespace MWWorld
if (!ptr.getClass().getEnchantment(ptr).empty())
SceneUtil::addEnchantedGlow(state.mNode, mResourceSystem, ptr.getClass().getEnchantmentColor(ptr));
state.mProjectileId = mPhysics->addProjectile(actor, pos);
state.mToDelete = false;
mProjectiles.push_back(state);
}
@ -360,63 +366,58 @@ namespace MWWorld
return (state.mNode->getPosition() - playerPos).length2() >= farawayThreshold*farawayThreshold;
};
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();)
for (auto& projectileState : mProjectiles)
{
if (isCleanable(*it))
{
cleanupProjectile(*it);
it = mProjectiles.erase(it);
}
else
++it;
if (isCleanable(projectileState))
cleanupProjectile(projectileState);
}
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
for (auto& magicBoltState : mMagicBolts)
{
if (isCleanable(*it))
{
cleanupMagicBolt(*it);
it = mMagicBolts.erase(it);
}
else
++it;
if (isCleanable(magicBoltState))
cleanupMagicBolt(magicBoltState);
}
}
}
void ProjectileManager::moveMagicBolts(float duration)
{
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
continue;
const 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.
MWWorld::Ptr caster = it->getCaster();
MWWorld::Ptr caster = magicBoltState.getCaster();
if (!caster.isEmpty() && caster.getClass().isActor())
{
if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead())
{
cleanupMagicBolt(*it);
it = mMagicBolts.erase(it);
cleanupMagicBolt(magicBoltState);
continue;
}
}
osg::Quat orient = it->mNode->getAttitude();
osg::Quat orient = magicBoltState.mNode->getAttitude();
static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fTargetSpellMaxSpeed")->mValue.getFloat();
float speed = fTargetSpellMaxSpeed * it->mSpeed;
float speed = fTargetSpellMaxSpeed * magicBoltState.mSpeed;
osg::Vec3f direction = orient * osg::Vec3f(0,1,0);
direction.normalize();
osg::Vec3f pos(it->mNode->getPosition());
osg::Vec3f pos(magicBoltState.mNode->getPosition());
osg::Vec3f newPos = pos + direction * duration * speed;
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++)
{
it->mSounds.at(soundIter)->setPosition(newPos);
}
for (const auto& sound : magicBoltState.mSounds)
sound->setPosition(newPos);
it->mNode->setPosition(newPos);
magicBoltState.mNode->setPosition(newPos);
update(*it, duration);
mPhysics->updateProjectile(magicBoltState.mProjectileId, newPos);
update(magicBoltState, duration);
// 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;
@ -425,7 +426,7 @@ namespace MWWorld
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile);
const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, magicBoltState.mProjectileId);
bool hit = false;
if (result.mHit)
@ -433,16 +434,16 @@ namespace MWWorld
hit = true;
if (result.mHitObject.isEmpty())
{
// terrain
// terrain or projectile
}
else
{
MWMechanics::CastSpell cast(caster, result.mHitObject);
cast.mHitPosition = pos;
cast.mId = it->mSpellId;
cast.mSourceName = it->mSourceName;
cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false;
cast.inflict(result.mHitObject, caster, it->mEffects, ESM::RT_Target, false, true);
cast.inflict(result.mHitObject, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
}
}
@ -453,47 +454,46 @@ namespace MWWorld
if (hit)
{
MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, result.mHitObject,
ESM::RT_Target, it->mSpellId, it->mSourceName);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
for (size_t soundIter = 0; soundIter != it->mSounds.size(); soundIter++)
sndMgr->stopSound(it->mSounds.at(soundIter));
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, result.mHitObject,
ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
mParent->removeChild(it->mNode);
it = mMagicBolts.erase(it);
continue;
cleanupMagicBolt(magicBoltState);
}
else
++it;
}
}
void ProjectileManager::moveProjectiles(float duration)
{
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();)
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
continue;
const 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
// simulating aerodynamics at all
it->mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration;
projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration;
osg::Vec3f pos(it->mNode->getPosition());
osg::Vec3f newPos = pos + it->mVelocity * duration;
osg::Vec3f pos(projectileState.mNode->getPosition());
osg::Vec3f newPos = pos + projectileState.mVelocity * duration;
// rotation does not work well for throwing projectiles - their roll angle will depend on shooting direction.
if (!it->mThrown)
if (!projectileState.mThrown)
{
osg::Quat orient;
orient.makeRotate(osg::Vec3f(0,1,0), it->mVelocity);
it->mNode->setAttitude(orient);
orient.makeRotate(osg::Vec3f(0,1,0), projectileState.mVelocity);
projectileState.mNode->setAttitude(orient);
}
it->mNode->setPosition(newPos);
projectileState.mNode->setPosition(newPos);
mPhysics->updateProjectile(projectileState.mProjectileId, newPos);
update(*it, duration);
update(projectileState, duration);
MWWorld::Ptr caster = it->getCaster();
MWWorld::Ptr caster = projectileState.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;
@ -502,50 +502,149 @@ namespace MWWorld
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
MWPhysics::RayCastingResult result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile);
const auto result = mPhysics->castRay(pos, newPos, caster, targetActors, 0xff, MWPhysics::CollisionType_Projectile, projectileState.mProjectileId);
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);
// Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && it->mIdArrow != it->mBowId)
if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), it->mBowId))
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt;
}
if (caster.isEmpty())
caster = result.mHitObject;
MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, it->mAttackStrength);
MWMechanics::projectileHit(caster, result.mHitObject, bow, projectileRef.getPtr(), result.mHit ? result.mHitPos : newPos, projectileState.mAttackStrength);
mPhysics->reportCollision(Misc::Convert::toBullet(result.mHitPos), Misc::Convert::toBullet(result.mHitNormal));
if (underwater)
mRendering->emitWaterRipple(newPos);
mParent->removeChild(it->mNode);
it = mProjectiles.erase(it);
cleanupProjectile(projectileState);
}
}
}
void ProjectileManager::processHits()
{
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(projectileState.mProjectileId);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = projectileState.getCaster();
assert(target != caster);
if (!isValidTarget(caster, target))
{
projectile->activate();
continue;
}
if (caster.isEmpty())
caster = target;
// Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), projectileState.mIdArrow);
MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty() && projectileState.mIdArrow != projectileState.mBowId)
{
MWWorld::InventoryStore& inv = caster.getClass().getInventoryStore(caster);
MWWorld::ContainerStoreIterator invIt = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
if (invIt != inv.end() && Misc::StringUtils::ciEqual(invIt->getCellRef().getRefId(), projectileState.mBowId))
bow = *invIt;
}
projectileState.mHitPosition = pos;
cleanupProjectile(projectileState);
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
}
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
continue;
auto* projectile = mPhysics->getProjectile(magicBoltState.mProjectileId);
if (projectile->isActive())
continue;
const auto target = projectile->getTarget();
const auto pos = projectile->getHitPos();
MWWorld::Ptr caster = magicBoltState.getCaster();
assert(target != caster);
if (!isValidTarget(caster, target))
{
projectile->activate();
continue;
}
++it;
magicBoltState.mHitPosition = pos;
cleanupMagicBolt(magicBoltState);
MWMechanics::CastSpell cast(caster, target);
cast.mHitPosition = pos;
cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName;
cast.mStack = false;
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
}
mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }),
mProjectiles.end());
mMagicBolts.erase(std::remove_if(mMagicBolts.begin(), mMagicBolts.end(), [](const State& state) { return state.mToDelete; }),
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);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
}
void ProjectileManager::cleanupMagicBolt(ProjectileManager::MagicBoltState& state)
{
mParent->removeChild(state.mNode);
mPhysics->removeProjectile(state.mProjectileId);
state.mToDelete = true;
for (size_t soundIter = 0; soundIter != state.mSounds.size(); soundIter++)
{
MWBase::Environment::get().getSoundManager()->stopSound(state.mSounds.at(soundIter));
@ -554,15 +653,12 @@ namespace MWWorld
void ProjectileManager::clear()
{
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{
cleanupProjectile(*it);
}
for (auto& mProjectile : mProjectiles)
cleanupProjectile(mProjectile);
mProjectiles.clear();
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{
cleanupMagicBolt(*it);
}
for (auto& mMagicBolt : mMagicBolts)
cleanupMagicBolt(mMagicBolt);
mMagicBolts.clear();
}
@ -619,6 +715,7 @@ namespace MWWorld
state.mVelocity = esm.mVelocity;
state.mIdArrow = esm.mId;
state.mAttackStrength = esm.mAttackStrength;
state.mToDelete = false;
std::string model;
try
@ -626,9 +723,10 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
int weaponType = ptr.get<ESM::Weapon>()->mBase->mData.mType;
state.mThrown = MWMechanics::getWeaponType(weaponType)->mWeaponClass == ESM::WeaponType::Thrown;
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
}
catch(...)
{
@ -640,7 +738,7 @@ namespace MWWorld
mProjectiles.push_back(state);
return true;
}
else if (type == ESM::REC_MPRJ)
if (type == ESM::REC_MPRJ)
{
ESM::MagicBoltState esm;
esm.load(reader);
@ -649,7 +747,8 @@ namespace MWWorld
state.mIdMagic.push_back(esm.mId);
state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId;
std::string texture = "";
state.mToDelete = false;
std::string texture;
try
{
@ -672,6 +771,7 @@ namespace MWWorld
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), state.mIdMagic.at(0));
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
state.mProjectileId = mPhysics->addProjectile(state.getCaster(), osg::Vec3f(esm.mPosition));
}
catch(...)
{

@ -56,6 +56,8 @@ namespace MWWorld
void update(float dt);
void processHits();
/// Removes all current projectiles. Should be called when switching to a new worldspace.
void clear();
@ -76,6 +78,9 @@ namespace MWWorld
std::shared_ptr<MWRender::EffectAnimationTime> mEffectAnimationTime;
int mActorId;
int mProjectileId;
osg::Vec3f mHitPosition;
// TODO: this will break when the game is saved and reloaded, since there is currently
// no way to write identifiers for non-actors to a savegame.
@ -88,6 +93,8 @@ namespace MWWorld
// MW-id of an arrow projectile
std::string mIdArrow;
bool mToDelete;
};
struct MagicBoltState : public State
@ -125,6 +132,8 @@ 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);

@ -1493,6 +1493,7 @@ namespace MWWorld
mProjectileManager->update(duration);
const auto results = mPhysics->applyQueuedMovement(duration, mDiscardMovements, frameStart, frameNumber, stats);
mProjectileManager->processHits();
mDiscardMovements = false;
for(const auto& [actor, position]: results)

Loading…
Cancel
Save