#include "projectilemanager.hpp" #include #include #include #include #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.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 (new MWRender::EffectAnimationTime())); } MWRender::Animation::setRenderProperties(state.mObject, MWRender::RV_Effects, 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(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.75f; // 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::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::iterator it = mMagicBolts.begin(); it != mMagicBolts.end();) { Ogre::Quaternion orient = it->mNode->getOrientation(); static float fTargetSpellMaxSpeed = MWBase::Environment::get().getWorld()->getStore().get() .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 > collisions = mPhysEngine.rayTest2(from, to, OEngine::Physic::CollisionType_Projectile); bool hit=false; for (std::vector >::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, ESM::RT_Target, 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::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 > collisions = mPhysEngine.rayTest2(from, to, OEngine::Physic::CollisionType_Projectile); bool hit=false; for (std::vector >::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::iterator it = mProjectiles.begin(); it != mProjectiles.end(); ++it) { mSceneMgr->destroySceneNode(it->mNode); } mProjectiles.clear(); for (std::vector::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::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); } for (std::vector::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); } } bool ProjectileManager::readRecord(ESM::ESMReader &reader, uint32_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(); } }