Refactor projectiles to no longer use MW-objects

deque
scrawl 11 years ago
parent 18852c09d0
commit e5a21aca53

@ -57,7 +57,7 @@ add_openmw_dir (mwworld
cells localscripts customdata weather inventorystore ptr actionopen actionread
actionequip timestamp actionalchemy cellstore actionapply actioneat
esmstore store recordcmp fallback actionrepair actionsoulgem livecellref actiondoor
contentloader esmloader omwloader actiontrap cellreflist
contentloader esmloader omwloader actiontrap cellreflist projectilemanager
)
add_openmw_dir (mwclass

@ -110,18 +110,25 @@ namespace MWBase
///< Play a sound, independently of 3D-position
///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts.
virtual SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId,
float volume, float pitch, PlayType type=Play_TypeSfx,
PlayMode mode=Play_Normal, float offset=0) = 0;
///< Play a sound from an object
virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId,
float volume, float pitch, PlayType type=Play_TypeSfx,
PlayMode mode=Play_Normal, float offset=0) = 0;
///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified.
///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts.
virtual MWBase::SoundPtr playManualSound3D(const Ogre::Vector3& initialPos, const std::string& soundId,
float volume, float pitch, PlayType type, PlayMode mode, float offset=0) = 0;
///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated manually using Sound::setPosition.
virtual void stopSound3D(const MWWorld::Ptr &reference, const std::string& soundId) = 0;
///< Stop the given object from playing the given sound,
virtual void stopSound3D(const MWWorld::Ptr &reference) = 0;
///< Stop the given object from playing all sounds.
virtual void stopSound(MWBase::SoundPtr sound) = 0;
///< Stop the given sound handle
virtual void stopSound(const MWWorld::CellStore *cell) = 0;
///< Stop all sounds for the given cell.

@ -467,7 +467,8 @@ namespace MWBase
virtual void castSpell (const MWWorld::Ptr& actor) = 0;
virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects,
virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId,
float speed, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& actor, const std::string& sourceName) = 0;
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile,
const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed) = 0;
@ -513,7 +514,7 @@ namespace MWBase
/// Spawn a blood effect for \a ptr at \a worldPosition
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0;
virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects,
virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects,
const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0;
};
}

@ -21,6 +21,41 @@
#include "magiceffects.hpp"
#include "npcstats.hpp"
namespace
{
/// Get projectile properties (model, sound and speed) for a spell with the given effects
/// If \a model is empty, the spell has no ranged effects and should not spawn a projectile.
void getProjectileInfo (const ESM::EffectList& effects, std::string& model, std::string& sound, float& speed)
{
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
iter!=effects.mList.end(); ++iter)
{
if (iter->mRange != ESM::RT_Target)
continue;
const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
iter->mEffectID);
model = magicEffect->mBolt;
if (model.empty())
model = "VFX_DefaultBolt";
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
if (!magicEffect->mBoltSound.empty())
sound = magicEffect->mBoltSound;
else
sound = schools[magicEffect->mData.mSchool] + " bolt";
speed = magicEffect->mData.mSpeed;
break;
}
}
}
namespace MWMechanics
{
@ -409,7 +444,7 @@ namespace MWMechanics
}
if (!exploded)
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, mTarget, effects, caster, mId, mSourceName);
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, mId, mSourceName);
if (!reflectedEffects.mList.empty())
inflict(caster, target, reflectedEffects, range, true);
@ -603,7 +638,13 @@ namespace MWMechanics
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
}
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, enchantment->mEffects, mCaster, mSourceName);
std::string projectileModel;
std::string sound;
float speed = 0;
getProjectileInfo(enchantment->mEffects, projectileModel, sound, speed);
if (!projectileModel.empty())
MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed,
false, enchantment->mEffects, mCaster, mSourceName);
return true;
}
@ -682,7 +723,15 @@ namespace MWMechanics
}
}
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, false, spell->mEffects, mCaster, mSourceName);
std::string projectileModel;
std::string sound;
float speed = 0;
getProjectileInfo(spell->mEffects, projectileModel, sound, speed);
if (!projectileModel.empty())
MWBase::Environment::get().getWorld()->launchMagicBolt(projectileModel, sound, mId, speed,
false, spell->mEffects, mCaster, mSourceName);
return true;
}

@ -11,18 +11,6 @@
namespace MWRender
{
class EffectAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
float mTime;
public:
EffectAnimationTime() : mTime(0) { }
void addTime(float time) { mTime += time; }
virtual Ogre::Real getValue() const { return mTime; }
virtual void setValue(Ogre::Real value) {}
};
EffectManager::EffectManager(Ogre::SceneManager *sceneMgr)
: mSceneMgr(sceneMgr)
{

@ -5,6 +5,20 @@
namespace MWRender
{
class EffectAnimationTime : public Ogre::ControllerValue<Ogre::Real>
{
private:
float mTime;
public:
EffectAnimationTime() : mTime(0) { }
void addTime(float time) { mTime += time; }
virtual Ogre::Real getValue() const { return mTime; }
virtual void setValue(Ogre::Real value) {}
};
// Note: effects attached to another object should be managed by MWRender::Animation::addEffect.
// This class manages "free" effects, i.e. attached to a dedicated scene node in the world.
class EffectManager

@ -356,6 +356,44 @@ namespace MWSound
return sound;
}
MWBase::SoundPtr SoundManager::playManualSound3D(const Ogre::Vector3& initialPos, const std::string& soundId,
float volume, float pitch, PlayType type, PlayMode mode, float offset)
{
MWBase::SoundPtr sound;
if(!mOutput->isInitialized())
return sound;
try
{
// Look up the sound in the ESM data
float basevol = volumeFromType(type);
float min, max;
std::string file = lookup(soundId, volume, min, max);
sound = mOutput->playSound3D(file, initialPos, volume, basevol, pitch, min, max, mode|type, offset);
mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId);
}
catch(std::exception &e)
{
//std::cout <<"Sound Error: "<<e.what()<< std::endl;
}
return sound;
}
void SoundManager::stopSound (MWBase::SoundPtr sound)
{
SoundMap::iterator snditer = mActiveSounds.begin();
while(snditer != mActiveSounds.end())
{
if(snditer->first == sound)
{
snditer->first->stop();
mActiveSounds.erase(snditer++);
}
else
++snditer;
}
}
void SoundManager::stopSound3D(const MWWorld::Ptr &ptr, const std::string& soundId)
{
SoundMap::iterator snditer = mActiveSounds.begin();

@ -115,6 +115,13 @@ namespace MWSound
virtual MWBase::SoundPtr playSound3D(const MWWorld::Ptr &reference, const std::string& soundId,
float volume, float pitch, PlayType type=Play_TypeSfx,
PlayMode mode=Play_Normal, float offset=0);
///< Play a 3D sound attached to an MWWorld::Ptr. Will be updated automatically with the Ptr's position, unless Play_NoTrack is specified.
///< @param offset Value from [0,1] meaning from which fraction the sound the playback starts.
virtual MWBase::SoundPtr playManualSound3D(const Ogre::Vector3& initialPos, const std::string& soundId,
float volume, float pitch, PlayType type, PlayMode mode, float offset=0);
///< Play a 3D sound at \a initialPos. If the sound should be moving, it must be updated manually using Sound::setPosition.
///< Play a sound from an object
///< @param offset value from [0,1], when to start playback. 0 is beginning, 1 is end.
@ -124,6 +131,9 @@ namespace MWSound
virtual void stopSound3D(const MWWorld::Ptr &reference);
///< Stop the given object from playing all sounds.
virtual void stopSound(MWBase::SoundPtr sound);
///< Stop the given sound handle
virtual void stopSound(const MWWorld::CellStore *cell);
///< Stop all sounds for the given cell.

@ -0,0 +1,261 @@
#include "projectilemanager.hpp"
#include <OgreSceneManager.h>
#include <libs/openengine/bullet/physic.hpp>
#include "../mwworld/manualref.hpp"
#include "../mwworld/class.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwmechanics/combat.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwrender/effectmanager.hpp"
#include "../mwsound/sound.hpp"
namespace MWWorld
{
ProjectileManager::ProjectileManager(Ogre::SceneManager* sceneMgr, OEngine::Physic::PhysicEngine &engine)
: mPhysEngine(engine)
, mSceneMgr(sceneMgr)
{
}
void ProjectileManager::createModel(State &state, const std::string &model)
{
state.mObject = NifOgre::Loader::createObjects(state.mNode, model);
for(size_t i = 0;i < state.mObject->mControllers.size();i++)
{
if(state.mObject->mControllers[i].getSource().isNull())
state.mObject->mControllers[i].setSource(Ogre::SharedPtr<MWRender::EffectAnimationTime> (new MWRender::EffectAnimationTime()));
}
}
void ProjectileManager::update(NifOgre::ObjectScenePtr object, float duration)
{
for(size_t i = 0; i < object->mControllers.size() ;i++)
{
MWRender::EffectAnimationTime* value = dynamic_cast<MWRender::EffectAnimationTime*>(object->mControllers[i].getSource().get());
if (value)
value->addTime(duration);
object->mControllers[i].update();
}
}
void ProjectileManager::launchMagicBolt(const std::string &model, const std::string &sound,
const std::string &spellId, float speed, bool stack,
const ESM::EffectList &effects, const Ptr &actor, const std::string &sourceName)
{
// Spawn at 0.75 * ActorHeight
float height = mPhysEngine.getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75;
Ogre::Vector3 pos(actor.getRefData().getPosition().pos);
pos.z += height;
Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) *
Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X);
MagicBoltState state;
state.mSourceName = sourceName;
state.mId = spellId;
state.mActorId = actor.getClass().getCreatureStats(actor).getActorId();
state.mSpeed = speed;
state.mStack = stack;
// Only interested in "on target" effects
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
iter!=effects.mList.end(); ++iter)
{
if (iter->mRange == ESM::RT_Target)
state.mEffects.mList.push_back(*iter);
}
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), model);
MWWorld::Ptr ptr = ref.getPtr();
state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(pos, orient);
createModel(state, ptr.getClass().getModel(ptr));
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
state.mSound = sndMgr->playManualSound3D(pos, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
mMagicBolts.push_back(state);
}
void ProjectileManager::launchProjectile(Ptr actor, Ptr projectile, const Ogre::Vector3 &pos,
const Ogre::Quaternion &orient, Ptr bow, float speed)
{
ProjectileState state;
state.mActorId = actor.getClass().getCreatureStats(actor).getActorId();
state.mBow = bow;
state.mVelocity = orient.yAxis() * speed;
state.mProjectileId = projectile.getCellRef().mRefID;
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().mRefID);
MWWorld::Ptr ptr = ref.getPtr();
state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(pos, orient);
createModel(state, ptr.getClass().getModel(ptr));
mProjectiles.push_back(state);
}
void ProjectileManager::update(float dt)
{
moveProjectiles(dt);
moveMagicBolts(dt);
}
void ProjectileManager::moveMagicBolts(float duration)
{
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
{
Ogre::Quaternion orient = it->mNode->getOrientation();
static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
.find("fTargetSpellMaxSpeed")->getFloat();
float speed = fTargetSpellMaxSpeed * it->mSpeed;
Ogre::Vector3 direction = orient.yAxis();
direction.normalise();
Ogre::Vector3 pos(it->mNode->getPosition());
Ogre::Vector3 newPos = pos + direction * duration * speed;
it->mSound->setPosition(newPos);
it->mNode->setPosition(newPos);
update(it->mObject, duration);
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
btVector3 from(pos.x, pos.y, pos.z);
btVector3 to(newPos.x, newPos.y, newPos.z);
std::vector<std::pair<float, std::string> > collisions = mPhysEngine.rayTest2(from, to);
bool hit=false;
for (std::vector<std::pair<float, std::string> >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt)
{
MWWorld::Ptr obstacle = MWBase::Environment::get().getWorld()->searchPtrViaHandle(cIt->second);
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId);
if (caster.isEmpty())
caster = obstacle;
if (obstacle.isEmpty())
{
// Terrain
}
else
{
MWMechanics::CastSpell cast(caster, obstacle);
cast.mHitPosition = pos;
cast.mId = it->mId;
cast.mSourceName = it->mSourceName;
cast.mStack = it->mStack;
cast.inflict(obstacle, caster, it->mEffects, ESM::RT_Target, false, true);
}
hit = true;
}
if (hit)
{
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId);
MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, it->mId, it->mSourceName);
MWBase::Environment::get().getSoundManager()->stopSound(it->mSound);
mSceneMgr->destroySceneNode(it->mNode);
it = mMagicBolts.erase(it);
continue;
}
else
++it;
}
}
void ProjectileManager::moveProjectiles(float duration)
{
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();)
{
// gravity constant - must be way lower than the gravity affecting actors, since we're not
// simulating aerodynamics at all
it->mVelocity -= Ogre::Vector3(0, 0, 627.2f * 0.1f) * duration;
Ogre::Vector3 pos(it->mNode->getPosition());
Ogre::Vector3 newPos = pos + it->mVelocity * duration;
Ogre::Quaternion orient = Ogre::Vector3::UNIT_Y.getRotationTo(it->mVelocity);
it->mNode->setOrientation(orient);
it->mNode->setPosition(newPos);
update(it->mObject, duration);
// Check for impact
// TODO: use a proper btRigidBody / btGhostObject?
btVector3 from(pos.x, pos.y, pos.z);
btVector3 to(newPos.x, newPos.y, newPos.z);
std::vector<std::pair<float, std::string> > collisions = mPhysEngine.rayTest2(from, to);
bool hit=false;
for (std::vector<std::pair<float, std::string> >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt)
{
MWWorld::Ptr obstacle = MWBase::Environment::get().getWorld()->searchPtrViaHandle(cIt->second);
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId);
// Arrow intersects with player immediately after shooting :/
if (obstacle == caster)
continue;
if (caster.isEmpty())
caster = obstacle;
if (obstacle.isEmpty())
{
// Terrain
}
else if (obstacle.getClass().isActor())
{
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mProjectileId);
MWMechanics::projectileHit(caster, obstacle, it->mBow, projectileRef.getPtr(), pos + (newPos - pos) * cIt->first);
}
hit = true;
}
if (hit)
{
mSceneMgr->destroySceneNode(it->mNode);
it = mProjectiles.erase(it);
continue;
}
else
++it;
}
}
void ProjectileManager::clear()
{
for (std::vector<ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{
mSceneMgr->destroySceneNode(it->mNode);
}
mProjectiles.clear();
for (std::vector<MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{
MWBase::Environment::get().getSoundManager()->stopSound(it->mSound);
mSceneMgr->destroySceneNode(it->mNode);
}
mMagicBolts.clear();
}
}

@ -0,0 +1,104 @@
#ifndef OPENMW_MWWORLD_PROJECTILEMANAGER_H
#define OPENMW_MWWORLD_PROJECTILEMANAGER_H
#include <string>
#include <OgreVector3.h>
#include <components/esm/effectlist.hpp>
#include <components/nifogre/ogrenifloader.hpp>
#include "../mwbase/soundmanager.hpp"
#include "ptr.hpp"
namespace OEngine
{
namespace Physic
{
class PhysicEngine;
}
}
namespace Ogre
{
class SceneManager;
}
namespace MWWorld
{
class ProjectileManager
{
public:
ProjectileManager (Ogre::SceneManager* sceneMgr,
OEngine::Physic::PhysicEngine& engine);
void launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId,
float speed, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& actor, const std::string& sourceName);
void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile,
const Ogre::Vector3& pos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed);
void update(float dt);
/// Removes all current projectiles. Should be called when switching to a new worldspace.
void clear();
private:
OEngine::Physic::PhysicEngine& mPhysEngine;
Ogre::SceneManager* mSceneMgr;
struct State
{
NifOgre::ObjectScenePtr mObject;
Ogre::SceneNode* mNode;
};
struct MagicBoltState : public State
{
// Id of spell or enchantment to apply when it hits
std::string mId;
// Actor who casted this projectile
int mActorId;
// Name of item to display as effect source in magic menu (in case we casted an enchantment)
std::string mSourceName;
ESM::EffectList mEffects;
float mSpeed;
bool mStack;
MWBase::SoundPtr mSound;
};
struct ProjectileState : public State
{
// Actor who shot this projectile
int mActorId;
// RefID of the projectile
std::string mProjectileId;
MWWorld::Ptr mBow; // bow or crossbow the projectile was fired from (may be empty)
Ogre::Vector3 mVelocity;
};
std::vector<MagicBoltState> mMagicBolts;
std::vector<ProjectileState> mProjectiles;
void moveProjectiles(float dt);
void moveMagicBolts(float dt);
void createModel (State& state, const std::string& model);
void update (NifOgre::ObjectScenePtr object, float duration);
};
}
#endif

@ -344,8 +344,6 @@ namespace MWWorld
// Sky system
MWBase::Environment::get().getWorld()->adjustSky();
mRendering.switchToExterior();
mCellChanged = true;
loadingListener->removeWallpaper();
@ -439,7 +437,6 @@ namespace MWWorld
mCurrentCell = cell;
// adjust fog
mRendering.switchToInterior();
mRendering.configureFog(*mCurrentCell);
// adjust player

@ -43,6 +43,7 @@
#include "containerstore.hpp"
#include "inventorystore.hpp"
#include "actionteleport.hpp"
#include "projectilemanager.hpp"
#include "contentloader.hpp"
#include "esmloader.hpp"
@ -136,6 +137,8 @@ namespace MWWorld
mPhysics = new PhysicsSystem(renderer);
mPhysEngine = mPhysics->getEngine();
mProjectileManager.reset(new ProjectileManager(renderer.getScene(), *mPhysEngine));
mRendering = new MWRender::RenderingManager(renderer, resDir, cacheDir, mPhysEngine,&mFallback);
mPhysEngine->setSceneManager(renderer.getScene());
@ -255,8 +258,6 @@ namespace MWWorld
mCells.clear();
mMagicBolts.clear();
mProjectiles.clear();
mDoorStates.clear();
mGodMode = false;
@ -807,6 +808,10 @@ namespace MWWorld
void World::changeToInteriorCell (const std::string& cellName, const ESM::Position& position)
{
// changed worldspace
mProjectileManager->clear();
mRendering->switchToInterior();
removeContainerScripts(getPlayerPtr());
mWorldScene->changeToInteriorCell(cellName, position);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
@ -814,6 +819,12 @@ namespace MWWorld
void World::changeToExteriorCell (const ESM::Position& position)
{
if (!getPlayerPtr().getCell()->getCell()->isExterior())
{
// changed worldspace
mProjectileManager->clear();
mRendering->switchToExterior();
}
removeContainerScripts(getPlayerPtr());
mWorldScene->changeToExteriorCell(position);
addContainerScripts(getPlayerPtr(), getPlayerPtr().getCell());
@ -949,8 +960,6 @@ namespace MWWorld
MWWorld::Ptr newPtr = MWWorld::Class::get(ptr)
.copyToCell(ptr, *newCell);
newPtr.getRefData().setBaseNode(0);
objectLeftActiveCell(ptr, newPtr);
}
else
{
@ -1172,8 +1181,7 @@ namespace MWWorld
{
processDoors(duration);
moveMagicBolts(duration);
moveProjectiles(duration);
mProjectileManager->update(duration);
const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration);
PtrVelocityList::const_iterator player(results.end());
@ -2254,306 +2262,14 @@ namespace MWWorld
void World::launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile,
const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed)
{
ProjectileState state;
state.mActorId = actor.getClass().getCreatureStats(actor).getActorId();
state.mBow = bow;
state.mVelocity = orient.yAxis() * speed;
MWWorld::ManualRef ref(getStore(), projectile.getCellRef().mRefID);
ESM::Position pos;
pos.pos[0] = worldPos.x;
pos.pos[1] = worldPos.y;
pos.pos[2] = worldPos.z;
// Do NOT copy actor rotation! actors use a different rotation order, and this will not produce the same facing direction.
Ogre::Matrix3 mat;
orient.ToRotationMatrix(mat);
Ogre::Radian xr,yr,zr;
mat.ToEulerAnglesXYZ(xr, yr, zr);
pos.rot[0] = -xr.valueRadians();
pos.rot[1] = -yr.valueRadians();
pos.rot[2] = -zr.valueRadians();
MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), actor.getCell(), pos, false);
ptr.getRefData().setCount(1);
mProjectiles[ptr] = state;
mProjectileManager->launchProjectile(actor, projectile, worldPos, orient, bow, speed);
}
void World::launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects,
void World::launchMagicBolt (const std::string& model, const std::string &sound, const std::string &spellId,
float speed, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& actor, const std::string& sourceName)
{
std::string projectileModel;
std::string sound;
float speed = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
iter!=effects.mList.end(); ++iter)
{
if (iter->mRange != ESM::RT_Target)
continue;
const ESM::MagicEffect *magicEffect = getStore().get<ESM::MagicEffect>().find (
iter->mEffectID);
projectileModel = magicEffect->mBolt;
if (projectileModel.empty())
projectileModel = "VFX_DefaultBolt";
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
if (!magicEffect->mBoltSound.empty())
sound = magicEffect->mBoltSound;
else
sound = schools[magicEffect->mData.mSchool] + " bolt";
speed = magicEffect->mData.mSpeed;
break;
}
if (projectileModel.empty())
return;
// Spawn at 0.75 * ActorHeight
float height = mPhysEngine->getCharacter(actor.getRefData().getHandle())->getHalfExtents().z * 2 * 0.75;
MWWorld::ManualRef ref(getStore(), projectileModel);
ESM::Position pos;
pos.pos[0] = actor.getRefData().getPosition().pos[0];
pos.pos[1] = actor.getRefData().getPosition().pos[1];
pos.pos[2] = actor.getRefData().getPosition().pos[2] + height;
// Do NOT copy rotation directly! actors use a different rotation order, and this will not produce the same facing direction.
Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) *
Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X);
Ogre::Matrix3 mat;
orient.ToRotationMatrix(mat);
Ogre::Radian xr,yr,zr;
mat.ToEulerAnglesXYZ(xr, yr, zr);
pos.rot[0] = -xr.valueRadians();
pos.rot[1] = -yr.valueRadians();
pos.rot[2] = -zr.valueRadians();
ref.getPtr().getCellRef().mPos = pos;
CellStore* cell = actor.getCell();
MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), cell, pos);
MagicBoltState state;
state.mSourceName = sourceName;
state.mId = id;
state.mActorId = actor.getClass().getCreatureStats(actor).getActorId();
state.mSpeed = speed;
state.mStack = stack;
// Only interested in "on target" effects
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
iter!=effects.mList.end(); ++iter)
{
if (iter->mRange == ESM::RT_Target)
state.mEffects.mList.push_back(*iter);
}
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
mMagicBolts[ptr] = state;
}
void World::moveProjectiles(float duration)
{
std::map<std::string, ProjectileState> moved;
for (std::map<MWWorld::Ptr, ProjectileState>::iterator it = mProjectiles.begin(); it != mProjectiles.end();)
{
if (!mWorldScene->isCellActive(*it->first.getCell()))
{
deleteObject(it->first);
mProjectiles.erase(it++);
continue;
}
MWWorld::Ptr ptr = it->first;
// gravity constant - must be way lower than the gravity affecting actors, since we're not
// simulating aerodynamics at all
it->second.mVelocity -= Ogre::Vector3(0, 0, 627.2f * 0.1f) * duration;
Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
Ogre::Vector3 newPos = pos + it->second.mVelocity * duration;
Ogre::Quaternion orient = Ogre::Vector3::UNIT_Y.getRotationTo(it->second.mVelocity);
Ogre::Matrix3 mat;
orient.ToRotationMatrix(mat);
Ogre::Radian xr,yr,zr;
mat.ToEulerAnglesXYZ(xr, yr, zr);
rotateObject(ptr, -xr.valueDegrees(), -yr.valueDegrees(), -zr.valueDegrees());
// Check for impact
btVector3 from(pos.x, pos.y, pos.z);
btVector3 to(newPos.x, newPos.y, newPos.z);
std::vector<std::pair<float, std::string> > collisions = mPhysEngine->rayTest2(from, to);
bool hit=false;
// HACK: query against the shape as well, since the ray does not take the volume into account
// really, this should be a convex cast, but the whole physics system needs a rewrite
std::vector<std::string> col2 = mPhysEngine->getCollisions(ptr.getRefData().getHandle());
for (std::vector<std::string>::iterator ci = col2.begin(); ci != col2.end(); ++ci)
collisions.push_back(std::make_pair(0.f,*ci));
for (std::vector<std::pair<float, std::string> >::iterator cIt = collisions.begin(); cIt != collisions.end() && !hit; ++cIt)
{
MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second);
if (obstacle == ptr)
continue;
MWWorld::Ptr caster = searchPtrViaActorId(it->second.mActorId);
// Arrow intersects with player immediately after shooting :/
if (obstacle == caster)
continue;
if (caster.isEmpty())
caster = obstacle;
if (obstacle.isEmpty())
{
// Terrain
}
else if (obstacle.getClass().isActor())
{
MWMechanics::projectileHit(caster, obstacle, it->second.mBow, ptr, pos + (newPos - pos) * cIt->first);
}
hit = true;
}
if (hit)
{
deleteObject(ptr);
mProjectiles.erase(it++);
continue;
}
std::string handle = ptr.getRefData().getHandle();
moveObject(ptr, newPos.x, newPos.y, newPos.z);
// HACK: Re-fetch Ptrs if necessary, since the cell might have changed
if (!ptr.getRefData().getCount())
{
moved[handle] = it->second;
mProjectiles.erase(it++);
}
else
++it;
}
// HACK: Re-fetch Ptrs if necessary, since the cell might have changed
for (std::map<std::string, ProjectileState>::iterator it = moved.begin(); it != moved.end(); ++it)
{
MWWorld::Ptr newPtr = searchPtrViaHandle(it->first);
if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted
continue;
mProjectiles[getPtrViaHandle(it->first)] = it->second;
}
}
void World::moveMagicBolts(float duration)
{
std::map<std::string, MagicBoltState> moved;
for (std::map<MWWorld::Ptr, MagicBoltState>::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();)
{
if (!mWorldScene->isCellActive(*it->first.getCell()))
{
deleteObject(it->first);
mMagicBolts.erase(it++);
continue;
}
MWWorld::Ptr ptr = it->first;
Ogre::Vector3 rot(ptr.getRefData().getPosition().rot);
Ogre::Quaternion orient = ptr.getRefData().getBaseNode()->getOrientation();
static float fTargetSpellMaxSpeed = getStore().get<ESM::GameSetting>().find("fTargetSpellMaxSpeed")->getFloat();
float speed = fTargetSpellMaxSpeed * it->second.mSpeed;
Ogre::Vector3 direction = orient.yAxis();
direction.normalise();
Ogre::Vector3 pos(ptr.getRefData().getPosition().pos);
Ogre::Vector3 newPos = pos + direction * duration * speed;
// Check for impact
btVector3 from(pos.x, pos.y, pos.z);
btVector3 to(newPos.x, newPos.y, newPos.z);
std::vector<std::pair<float, std::string> > collisions = mPhysEngine->rayTest2(from, to);
bool explode = false;
for (std::vector<std::pair<float, std::string> >::iterator cIt = collisions.begin(); cIt != collisions.end() && !explode; ++cIt)
{
MWWorld::Ptr obstacle = searchPtrViaHandle(cIt->second);
if (obstacle == ptr)
continue;
MWWorld::Ptr caster = searchPtrViaActorId(it->second.mActorId);
if (caster.isEmpty())
caster = obstacle;
if (obstacle.isEmpty())
{
// Terrain
}
else
{
MWMechanics::CastSpell cast(caster, obstacle);
cast.mHitPosition = pos;
cast.mId = it->second.mId;
cast.mSourceName = it->second.mSourceName;
cast.mStack = it->second.mStack;
cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false, true);
}
explode = true;
}
if (explode)
{
MWWorld::Ptr caster = searchPtrViaActorId(it->second.mActorId);
explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName);
deleteObject(ptr);
mMagicBolts.erase(it++);
continue;
}
std::string handle = ptr.getRefData().getHandle();
moveObject(ptr, newPos.x, newPos.y, newPos.z);
// HACK: Re-fetch Ptrs if necessary, since the cell might have changed
if (!ptr.getRefData().getCount())
{
moved[handle] = it->second;
mMagicBolts.erase(it++);
}
else
++it;
}
// HACK: Re-fetch Ptrs if necessary, since the cell might have changed
for (std::map<std::string, MagicBoltState>::iterator it = moved.begin(); it != moved.end(); ++it)
{
MWWorld::Ptr newPtr = searchPtrViaHandle(it->first);
if (newPtr.isEmpty()) // The projectile went into an inactive cell and was deleted
continue;
mMagicBolts[getPtrViaHandle(it->first)] = it->second;
}
}
void World::objectLeftActiveCell(Ptr object, Ptr movedPtr)
{
// For now, projectiles moved to an inactive cell are just deleted, because there's no reliable way to hold on to the meta information
if (mMagicBolts.find(object) != mMagicBolts.end())
deleteObject(movedPtr);
if (mProjectiles.find(object) != mProjectiles.end())
deleteObject(movedPtr);
mProjectileManager->launchMagicBolt(model, sound, spellId, speed, stack, effects, actor, sourceName);
}
const std::vector<std::string>& World::getContentFiles() const
@ -2935,7 +2651,7 @@ namespace MWWorld
mRendering->spawnEffect(model, texture, worldPosition);
}
void World::explodeSpell(const Vector3 &origin, const MWWorld::Ptr& object, const ESM::EffectList &effects, const Ptr &caster,
void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster,
const std::string& id, const std::string& sourceName)
{
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
@ -2960,12 +2676,13 @@ namespace MWWorld
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(!effect->mAreaSound.empty())
sndMgr->playSound3D(object, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
else
sndMgr->playSound3D(object, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
if(!effect->mAreaSound.empty())
sndMgr->playManualSound3D(origin, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
else
sndMgr->playManualSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack);
}
// Get the actors in range of the effect
std::vector<MWWorld::Ptr> objects;
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(

@ -3,6 +3,8 @@
#include "../mwrender/debugging.hpp"
#include <boost/shared_ptr.hpp>
#include "ptr.hpp"
#include "scene.hpp"
#include "esmstore.hpp"
@ -51,6 +53,7 @@ namespace MWWorld
{
class WeatherManager;
class Player;
class ProjectileManager;
/// \brief The game world and its visual representation
@ -74,6 +77,8 @@ namespace MWWorld
OEngine::Physic::PhysicEngine* mPhysEngine;
boost::shared_ptr<ProjectileManager> mProjectileManager;
bool mGodMode;
std::vector<std::string> mContentFiles;
@ -90,37 +95,6 @@ namespace MWWorld
std::map<MWWorld::Ptr, int> mDoorStates;
///< only holds doors that are currently moving. 1 = opening, 2 = closing
struct MagicBoltState
{
// Id of spell or enchantment to apply when it hits
std::string mId;
// Actor who casted this projectile
int mActorId;
// Name of item to display as effect source in magic menu (in case we casted an enchantment)
std::string mSourceName;
ESM::EffectList mEffects;
float mSpeed;
bool mStack;
};
struct ProjectileState
{
// Actor who shot this projectile
int mActorId;
MWWorld::Ptr mBow; // bow or crossbow the projectile was fired from
Ogre::Vector3 mVelocity;
};
std::map<MWWorld::Ptr, MagicBoltState> mMagicBolts;
std::map<MWWorld::Ptr, ProjectileState> mProjectiles;
std::string mStartCell;
void updateWeather(float duration);
@ -148,9 +122,6 @@ namespace MWWorld
void processDoors(float duration);
///< Run physics simulation and modify \a world accordingly.
void moveMagicBolts(float duration);
void moveProjectiles(float duration);
void doPhysics(float duration);
///< Run physics simulation and modify \a world accordingly.
@ -171,9 +142,6 @@ namespace MWWorld
bool mLevitationEnabled;
bool mGoToJail;
/// Called when \a object is moved to an inactive cell
void objectLeftActiveCell (MWWorld::Ptr object, MWWorld::Ptr movedPtr);
float feetToGameUnits(float feet);
public:
@ -572,7 +540,8 @@ namespace MWWorld
*/
virtual void castSpell (const MWWorld::Ptr& actor);
virtual void launchMagicBolt (const std::string& id, bool stack, const ESM::EffectList& effects,
virtual void launchMagicBolt (const std::string& model, const std::string& sound, const std::string& spellId,
float speed, bool stack, const ESM::EffectList& effects,
const MWWorld::Ptr& actor, const std::string& sourceName);
virtual void launchProjectile (MWWorld::Ptr actor, MWWorld::Ptr projectile,
const Ogre::Vector3& worldPos, const Ogre::Quaternion& orient, MWWorld::Ptr bow, float speed);
@ -612,7 +581,7 @@ namespace MWWorld
/// Spawn a blood effect for \a ptr at \a worldPosition
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition);
virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects,
virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects,
const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName);
};
}

Loading…
Cancel
Save