1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-02-01 18:15:32 +00:00

Merge branch 'projectiles_collisions_mk2' into 'master'

Projectiles collisions

See merge request OpenMW/openmw!369
This commit is contained in:
psi29a 2020-12-08 09:24:36 +00:00
commit 8d8a4a890c
17 changed files with 505 additions and 95 deletions

View file

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

View file

@ -66,13 +66,13 @@ add_openmw_dir (mwworld
cells localscripts customdata inventorystore ptr actionopen actionread actionharvest cells localscripts customdata inventorystore ptr actionopen actionread actionharvest
actionequip timestamp actionalchemy cellstore actionapply actioneat actionequip timestamp actionalchemy cellstore actionapply actioneat
store esmstore recordcmp fallback actionrepair actionsoulgem livecellref actiondoor 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 cellpreloader datetimemanager
) )
add_openmw_dir (mwphysics add_openmw_dir (mwphysics
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver projectile
closestnotmeconvexresultcallback raycasting mtphysics closestnotmeconvexresultcallback raycasting mtphysics
) )

View file

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

View file

@ -2,6 +2,14 @@
#include <BulletCollision/CollisionDispatch/btCollisionObject.h> #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 namespace MWPhysics
{ {
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot) 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) if (convexResult.m_hitCollisionObject == mMe)
return btScalar(1); 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; btVector3 hitNormalWorld;
if (normalInWorldSpace) if (normalInWorldSpace)
hitNormalWorld = convexResult.m_hitNormalLocal; hitNormalWorld = convexResult.m_hitNormalLocal;

View file

@ -1,18 +1,22 @@
#include "closestnotmerayresultcallback.hpp" #include "closestnotmerayresultcallback.hpp"
#include <algorithm> #include <algorithm>
#include <utility>
#include <BulletCollision/CollisionDispatch/btCollisionObject.h> #include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp"
#include "projectile.hpp"
#include "ptrholder.hpp" #include "ptrholder.hpp"
namespace MWPhysics 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) : 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())) 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()) if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
return 1.f; 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;
} }
} }

View file

@ -9,15 +9,18 @@ class btCollisionObject;
namespace MWPhysics namespace MWPhysics
{ {
class Projectile;
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
{ {
public: 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; btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
private: private:
const btCollisionObject* mMe; const btCollisionObject* mMe;
const std::vector<const btCollisionObject*> mTargets; const std::vector<const btCollisionObject*> mTargets;
Projectile* mProjectile;
}; };
} }

View file

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

View file

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

View file

@ -46,6 +46,8 @@
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "actor.hpp" #include "actor.hpp"
#include "projectile.hpp"
#include "trace.h" #include "trace.h"
#include "object.hpp" #include "object.hpp"
#include "heightfield.hpp" #include "heightfield.hpp"
@ -64,6 +66,7 @@ namespace MWPhysics
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
, mDebugDrawEnabled(false) , mDebugDrawEnabled(false)
, mTimeAccum(0.0f) , mTimeAccum(0.0f)
, mProjectileId(0)
, mWaterHeight(0) , mWaterHeight(0)
, mWaterEnabled(false) , mWaterEnabled(false)
, mParentNode(parentNode) , mParentNode(parentNode)
@ -112,7 +115,7 @@ namespace MWPhysics
mObjects.clear(); mObjects.clear();
mActors.clear(); mActors.clear();
mProjectiles.clear();
} }
void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue) void PhysicsSystem::setUnrefQueue(SceneUtil::UnrefQueue *unrefQueue)
@ -248,7 +251,7 @@ namespace MWPhysics
return 0.f; 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) 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_collisionFilterGroup = group;
resultCallback.m_collisionFilterMask = mask; 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) void PhysicsSystem::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
{ {
ObjectMap::iterator found = mObjects.find(old); ObjectMap::iterator found = mObjects.find(old);
@ -553,6 +563,14 @@ namespace MWPhysics
return nullptr; 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) void PhysicsSystem::updateScale(const MWWorld::Ptr &ptr)
{ {
ObjectMap::iterator found = mObjects.find(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) void PhysicsSystem::updateRotation(const MWWorld::Ptr &ptr)
{ {
ObjectMap::iterator found = mObjects.find(ptr); ObjectMap::iterator found = mObjects.find(ptr);
@ -632,6 +661,15 @@ namespace MWPhysics
mActors.emplace(ptr, std::move(actor)); 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() bool PhysicsSystem::toggleCollisionMode()
{ {
ActorMap::iterator found = mActors.find(MWMechanics::getPlayer()); ActorMap::iterator found = mActors.find(MWMechanics::getPlayer());

View file

@ -56,6 +56,7 @@ namespace MWPhysics
class Object; class Object;
class Actor; class Actor;
class PhysicsTaskScheduler; class PhysicsTaskScheduler;
class Projectile;
using ActorMap = std::map<MWWorld::ConstPtr, std::shared_ptr<Actor>>; 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 addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType = CollisionType_World);
void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); 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); void updatePtr (const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
Actor* getActor(const MWWorld::Ptr& ptr); Actor* getActor(const MWWorld::Ptr& ptr);
@ -134,6 +139,8 @@ namespace MWPhysics
const Object* getObject(const MWWorld::ConstPtr& ptr) const; const Object* getObject(const MWWorld::ConstPtr& ptr) const;
Projectile* getProjectile(int projectileId) const;
// Object or Actor // Object or Actor
void remove (const MWWorld::Ptr& ptr); void remove (const MWWorld::Ptr& ptr);
@ -141,7 +148,6 @@ namespace MWPhysics
void updateRotation (const MWWorld::Ptr& ptr); void updateRotation (const MWWorld::Ptr& ptr);
void updatePosition (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 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); 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. /// @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(), 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>(), 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; RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const override;
@ -269,6 +275,9 @@ namespace MWPhysics
ActorMap mActors; ActorMap mActors;
using ProjectileMap = std::map<int, std::shared_ptr<Projectile>>;
ProjectileMap mProjectiles;
using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>; using HeightFieldMap = std::map<std::pair<int, int>, HeightField *>;
HeightFieldMap mHeightFields; HeightFieldMap mHeightFields;
@ -279,6 +288,8 @@ namespace MWPhysics
float mTimeAccum; float mTimeAccum;
unsigned int mProjectileId;
float mWaterHeight; float mWaterHeight;
bool mWaterEnabled; bool mWaterEnabled;

View file

@ -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);
}
}

View file

@ -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

View file

@ -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. /// @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(), 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>(), 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; virtual RayCastingResult castSphere(const osg::Vec3f& from, const osg::Vec3f& to, float radius) const = 0;

View file

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

View file

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

View file

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

View file

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