You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw-tes3mp/apps/openmw/mwworld/projectilemanager.cpp

428 lines
16 KiB
C++

#include "projectilemanager.hpp"
#include <OgreSceneManager.h>
#include <OgreSceneNode.h>
#include <libs/openengine/bullet/physic.hpp>
#include <components/esm/projectilestate.hpp>
#include "../mwworld/manualref.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.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 "../mwrender/animation.hpp"
#include "../mwrender/renderconst.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()));
}
MWRender::Animation::setRenderProperties(state.mObject, MWRender::RV_Misc,
MWRender::RQG_Main, MWRender::RQG_Alpha, 0.f, false, NULL);
}
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 &caster, const std::string &sourceName,
const Ogre::Vector3& fallbackDirection)
{
float height = 0;
if (OEngine::Physic::PhysicActor* actor = mPhysEngine.getCharacter(caster.getRefData().getHandle()))
height = actor->getHalfExtents().z * 2 * 0.75; // Spawn at 0.75 * ActorHeight
Ogre::Vector3 pos(caster.getRefData().getPosition().pos);
pos.z += height;
if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible
return;
Ogre::Quaternion orient;
if (caster.getClass().isActor())
orient = Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) *
Ogre::Quaternion(Ogre::Radian(caster.getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X);
else
orient = Ogre::Vector3::UNIT_Y.getRotationTo(fallbackDirection);
MagicBoltState state;
state.mSourceName = sourceName;
state.mId = model;
state.mSpellId = spellId;
state.mCasterHandle = caster.getRefData().getHandle();
if (caster.getClass().isActor())
state.mActorId = caster.getClass().getCreatureStats(caster).getActorId();
else
state.mActorId = -1;
state.mSpeed = speed;
state.mStack = stack;
state.mSoundId = sound;
// 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.mBowId = bow.getCellRef().getRefId();
state.mVelocity = orient.yAxis() * speed;
state.mId = projectile.getCellRef().getRefId();
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), projectile.getCellRef().getRefId());
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;
if (it->mSound.get())
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, OEngine::Physic::CollisionType_Projectile);
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()->searchPtrViaHandle(it->mCasterHandle);
if (caster.isEmpty())
caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId);
if (!obstacle.isEmpty() && obstacle == caster)
continue;
if (caster.isEmpty())
caster = obstacle;
if (obstacle.isEmpty())
{
// Terrain
}
else
{
MWMechanics::CastSpell cast(caster, obstacle);
cast.mHitPosition = pos;
cast.mId = it->mSpellId;
cast.mSourceName = it->mSourceName;
cast.mStack = it->mStack;
cast.inflict(obstacle, caster, it->mEffects, ESM::RT_Target, false, true);
}
hit = true;
}
// Explodes when hitting water
if (MWBase::Environment::get().getWorld()->isUnderwater(MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), newPos))
hit = true;
if (hit)
{
MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId);
MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, it->mSpellId, 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, OEngine::Physic::CollisionType_Projectile);
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;
MWWorld::ManualRef projectileRef(MWBase::Environment::get().getWorld()->getStore(), it->mId);
// Try to get a Ptr to the bow that was used. It might no longer exist.
MWWorld::Ptr bow = projectileRef.getPtr();
if (!caster.isEmpty())
{
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))
bow = *invIt;
}
if (caster.isEmpty())
caster = obstacle;
MWMechanics::projectileHit(caster, obstacle, bow, 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();
}
void ProjectileManager::write(ESM::ESMWriter &writer, Loading::Listener &progress) const
{
for (std::vector<ProjectileState>::const_iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it)
{
writer.startRecord(ESM::REC_PROJ);
ESM::ProjectileState state;
state.mId = it->mId;
state.mPosition = it->mNode->getPosition();
state.mOrientation = it->mNode->getOrientation();
state.mActorId = it->mActorId;
state.mBowId = it->mBowId;
state.mVelocity = it->mVelocity;
state.save(writer);
writer.endRecord(ESM::REC_PROJ);
progress.increaseProgress();
}
for (std::vector<MagicBoltState>::const_iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it)
{
writer.startRecord(ESM::REC_MPRJ);
ESM::MagicBoltState state;
state.mId = it->mId;
state.mPosition = it->mNode->getPosition();
state.mOrientation = it->mNode->getOrientation();
state.mActorId = it->mActorId;
state.mSpellId = it->mSpellId;
state.mEffects = it->mEffects;
state.mSound = it->mSoundId;
state.mSourceName = it->mSourceName;
state.mSpeed = it->mSpeed;
state.mStack = it->mStack;
state.save(writer);
writer.endRecord(ESM::REC_MPRJ);
progress.increaseProgress();
}
}
bool ProjectileManager::readRecord(ESM::ESMReader &reader, int32_t type)
{
if (type == ESM::REC_PROJ)
{
ESM::ProjectileState esm;
esm.load(reader);
ProjectileState state;
state.mActorId = esm.mActorId;
state.mBowId = esm.mBowId;
state.mVelocity = esm.mVelocity;
state.mId = esm.mId;
std::string model;
try
{
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
}
catch(...)
{
return true;
}
state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(esm.mPosition, esm.mOrientation);
createModel(state, model);
mProjectiles.push_back(state);
return true;
}
else if (type == ESM::REC_MPRJ)
{
ESM::MagicBoltState esm;
esm.load(reader);
MagicBoltState state;
state.mSourceName = esm.mSourceName;
state.mId = esm.mId;
state.mSpellId = esm.mSpellId;
state.mActorId = esm.mActorId;
state.mSpeed = esm.mSpeed;
state.mStack = esm.mStack;
state.mEffects = esm.mEffects;
std::string model;
try
{
MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), esm.mId);
MWWorld::Ptr ptr = ref.getPtr();
model = ptr.getClass().getModel(ptr);
}
catch(...)
{
return true;
}
state.mNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(esm.mPosition, esm.mOrientation);
createModel(state, model);
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
state.mSound = sndMgr->playManualSound3D(esm.mPosition, esm.mSound, 1.0f, 1.0f,
MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop);
state.mSoundId = esm.mSound;
mMagicBolts.push_back(state);
return true;
}
return false;
}
int ProjectileManager::countSavedGameRecords() const
{
return mMagicBolts.size() + mProjectiles.size();
}
}