diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 42fd11a71..04bd89f95 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -58,7 +58,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 + contentloader esmloader omwloader actiontrap ) add_openmw_dir (mwclass diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index ddf841744..ad3130ac5 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -415,6 +415,9 @@ namespace MWBase virtual void castSpell (const MWWorld::Ptr& actor) = 0; virtual void updateAnimParts(const MWWorld::Ptr& ptr) = 0; + + virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName) = 0; }; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 783eabff6..55b15a7d2 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -13,8 +13,8 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/cellstore.hpp" -#include "../mwworld/actionapply.hpp" #include "../mwworld/actionopen.hpp" +#include "../mwworld/actiontrap.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/player.hpp" #include "../mwworld/inventorystore.hpp" @@ -147,11 +147,9 @@ namespace MWClass } else { - // Trap activation goes here - std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl; - boost::shared_ptr action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap)); + // Activate trap + boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr)); action->setSound(trapActivationSound); - ptr.getCellRef().mTrap = ""; return action; } } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 3a0e4d0ba..3adb4f6fc 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -12,12 +12,12 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/failedaction.hpp" -#include "../mwworld/actionapply.hpp" #include "../mwworld/actionteleport.hpp" #include "../mwworld/actiondoor.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/actiontrap.hpp" #include "../mwgui/tooltips.hpp" @@ -109,12 +109,8 @@ namespace MWClass if(!ptr.getCellRef().mTrap.empty()) { // Trap activation - std::cout << "Activated trap: " << ptr.getCellRef().mTrap << std::endl; - - boost::shared_ptr action(new MWWorld::ActionApply(actor, ptr.getCellRef().mTrap)); + boost::shared_ptr action(new MWWorld::ActionTrap(actor, ptr.getCellRef().mTrap, ptr)); action->setSound(trapActivationSound); - ptr.getCellRef().mTrap = ""; - return action; } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 492157993..786299580 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -467,9 +467,12 @@ namespace MWClass else { weapon.getCellRef().mEnchantmentCharge -= castCost; + // Touch othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ESM::RT_Touch, weapon.getClass().getName(weapon)); + // Self getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); - // TODO: RT_Target + // Target + MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon)); } } } diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 9e0ddb92b..3bf10e08f 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -39,6 +39,7 @@ namespace MWMechanics if (!timeToExpire (iter)) { mSpells.erase (iter++); + //onSpellExpired rebuild = true; } else @@ -227,6 +228,8 @@ namespace MWMechanics if (iter->mRange != range) continue; + // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. + const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( iter->mEffectID); @@ -248,7 +251,8 @@ namespace MWMechanics if (!magicEffect->mHit.empty()) { const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find (magicEffect->mHit); - MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, ""); + bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx; + MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, loop, ""); } first = false; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index fe1254b43..929e01237 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -513,7 +513,7 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun effect = store.get().find(effectentry.mEffectID); const ESM::Static* castStatic = store.get().find (effect->mCasting); - mAnimation->addEffect("meshes\\" + castStatic->mModel, ""); + mAnimation->addEffect("meshes\\" + castStatic->mModel); switch(effectentry.mRange) { diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index b80b5a863..d2559d301 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -16,6 +16,8 @@ namespace MWMechanics int mId; int mArg; // skill or ability + // TODO: Add caster here for Absorb effects? + EffectKey(); EffectKey (int id, int arg = -1) : mId (id), mArg (arg) {} diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 6ef51945e..3435df393 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -87,8 +87,8 @@ Animation::Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node) Animation::~Animation() { - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) - destroyObjectList(mInsert->getCreator(), *it); + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + destroyObjectList(mInsert->getCreator(), it->mObjects); mAnimSources.clear(); @@ -929,22 +929,35 @@ Ogre::Vector3 Animation::runAnimation(float duration) } - for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ) { - for(size_t i = 0; i < it->mControllers.size() ;i++) + NifOgre::ObjectList& objects = it->mObjects; + for(size_t i = 0; i < objects.mControllers.size() ;i++) { - static_cast (it->mControllers[i].getSource().get())->addTime(duration); + static_cast (objects.mControllers[i].getSource().get())->addTime(duration); - it->mControllers[i].update(); + objects.mControllers[i].update(); } - if (it->mControllers[0].getSource()->getValue() >= it->mMaxControllerLength) + if (objects.mControllers[0].getSource()->getValue() >= objects.mMaxControllerLength) { - destroyObjectList(mInsert->getCreator(), *it); - it = mEffects.erase(it); + if (it->mLoop) + { + // Start from the beginning again; carry over the remainder + float remainder = objects.mControllers[0].getSource()->getValue() - objects.mMaxControllerLength; + for(size_t i = 0; i < objects.mControllers.size() ;i++) + { + static_cast (objects.mControllers[i].getSource().get())->resetTime(remainder); + } + } + else + { + destroyObjectList(mInsert->getCreator(), it->mObjects); + it = mEffects.erase(it); + continue; + } } - else - ++it; + ++it; } return movement; @@ -1011,15 +1024,37 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) mSkelBase->detachObjectFromBone(obj); } -void Animation::addEffect(const std::string &model, const std::string &bonename) +void Animation::addEffect(const std::string &model, bool loop, const std::string &bonename) { - NifOgre::ObjectList list = NifOgre::Loader::createObjects(mInsert, model); - for(size_t i = 0;i < list.mControllers.size();i++) + // Early out if we already have this effect + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + if (it->mModelName == model) + return; + + EffectParams params; + params.mModelName = model; + params.mObjects = NifOgre::Loader::createObjects(mInsert, model); + params.mLoop = loop; + + for(size_t i = 0;i < params.mObjects.mControllers.size();i++) { - if(list.mControllers[i].getSource().isNull()) - list.mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); + if(params.mObjects.mControllers[i].getSource().isNull()) + params.mObjects.mControllers[i].setSource(Ogre::SharedPtr (new EffectAnimationValue())); + } + mEffects.push_back(params); +} + +void Animation::removeEffect(const std::string &model) +{ + for (std::vector::iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + if (it->mModelName == model) + { + destroyObjectList(mInsert->getCreator(), it->mObjects); + mEffects.erase(it); + return; + } } - mEffects.push_back(list); } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 6653b342f..b983a5944 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -59,6 +59,7 @@ protected: public: EffectAnimationValue() : mTime(0) { } void addTime(float time) { mTime += time; } + void resetTime(float value) { mTime = value; } virtual Ogre::Real getValue() const; virtual void setValue(Ogre::Real value); @@ -108,7 +109,14 @@ protected: typedef std::map ObjectAttachMap; - std::vector mEffects; + struct EffectParams + { + std::string mModelName; // Just here so we don't add the same effect twice + NifOgre::ObjectList mObjects; + bool mLoop; + }; + + std::vector mEffects; MWWorld::Ptr mPtr; Camera *mCamera; @@ -189,7 +197,17 @@ public: Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node); virtual ~Animation(); - void addEffect (const std::string& model, const std::string& bonename = ""); + /** + * @brief Add an effect mesh attached to a bone or the insert scene node + * @param model + * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, + * you need to remove it manually using removeEffect when the effect should end. + * @param bonename Bone to attach to, or empty string to use the scene node instead + * @note Will not add an effect twice. + */ + void addEffect (const std::string& model, bool loop = false, const std::string& bonename = ""); + + void removeEffect (const std::string& model); void updatePtr(const MWWorld::Ptr &ptr); diff --git a/apps/openmw/mwworld/actionapply.hpp b/apps/openmw/mwworld/actionapply.hpp index 3353ae0ee..669a19067 100644 --- a/apps/openmw/mwworld/actionapply.hpp +++ b/apps/openmw/mwworld/actionapply.hpp @@ -1,4 +1,3 @@ - #ifndef GAME_MWWORLD_ACTIONAPPLY_H #define GAME_MWWORLD_ACTIONAPPLY_H diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp new file mode 100644 index 000000000..13b2fd269 --- /dev/null +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -0,0 +1,28 @@ +#include "actiontrap.hpp" + +#include "../mwworld/class.hpp" + +#include "../mwmechanics/activespells.hpp" +#include "../mwmechanics/creaturestats.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +namespace MWWorld +{ + + void ActionTrap::executeImp(const Ptr &actor) + { + // TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could + // make it lock itself when activated for example. + + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, ESM::RT_Touch); + + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); + + MWBase::Environment::get().getWorld()->launchProjectile(mSpellId, spell->mEffects, mTrapSource, spell->mName); + + mTrapSource.getCellRef().mTrap = ""; + } + +} diff --git a/apps/openmw/mwworld/actiontrap.hpp b/apps/openmw/mwworld/actiontrap.hpp new file mode 100644 index 000000000..4c2f4139f --- /dev/null +++ b/apps/openmw/mwworld/actiontrap.hpp @@ -0,0 +1,29 @@ +#ifndef GAME_MWWORLD_ACTIONTRAP_H +#define GAME_MWWORLD_ACTIONTRAP_H + +#include + +#include "action.hpp" +#include "ptr.hpp" + +namespace MWWorld +{ + class ActionTrap : public Action + { + std::string mSpellId; + MWWorld::Ptr mTrapSource; + + virtual void executeImp (const Ptr& actor); + + public: + + /// @param spellId + /// @param actor Actor that activated the trap + /// @param trapSource + ActionTrap (const Ptr& actor, const std::string& spellId, const Ptr& trapSource) + : Action(false, actor), mSpellId(spellId), mTrapSource(trapSource) {} + }; +} + + +#endif diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index a5711acfc..c847a6574 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -254,6 +254,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() { + // TODO: Add VFX; update when needed instead of update on request if (!mMagicEffectsUpToDate) { mMagicEffects = MWMechanics::MagicEffects(); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e343546c2..dd29c33a9 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1122,6 +1122,8 @@ namespace MWWorld processDoors(duration); + moveProjectiles(duration); + const PtrVelocityList &results = mPhysics->applyQueuedMovement(duration); PtrVelocityList::const_iterator player(results.end()); for(PtrVelocityList::const_iterator iter(results.begin());iter != results.end();iter++) @@ -2163,8 +2165,78 @@ namespace MWWorld } } + launchProjectile(selectedSpell, effects, actor, sourceName); + } + void World::launchProjectile (const std::string& id, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName) + { + std::string projectileModel; + std::string sound; + for (std::vector::const_iterator iter (effects.mList.begin()); + iter!=effects.mList.end(); ++iter) + { + if (iter->mRange != ESM::RT_Target) + continue; + + const ESM::MagicEffect *magicEffect = getStore().get().find ( + iter->mEffectID); + + projectileModel = magicEffect->mBolt; + + 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"; + + break; + } + if (projectileModel.empty()) + return; + + 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]; + pos.rot[0] = actor.getRefData().getPosition().rot[0]; + pos.rot[1] = actor.getRefData().getPosition().rot[1]; + pos.rot[2] = actor.getRefData().getPosition().rot[2]; + ref.getPtr().getCellRef().mPos = pos; + MWWorld::Ptr ptr = copyObjectToCell(ref.getPtr(), *actor.getCell(), pos); + + ProjectileState state; + state.mSourceName = sourceName; + state.mId = id; + state.mActorHandle = actor.getRefData().getHandle(); + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(ptr, sound, 1.0f, 1.0f); + + mProjectiles[ptr] = state; + } + + void World::moveProjectiles(float duration) + { + for (std::map::iterator it = mProjectiles.begin(); it != mProjectiles.end();) + { + if (!mWorldScene->isCellActive(*it->first.getCell())) + { + mProjectiles.erase(it++); + continue; + } + // TODO: Move + //moveObject(it->first, newPos.x, newPos.y, newPos.z); + ++it; + } + } + + void World::updateAnimParts(const Ptr& actor) { mRendering->updateAnimParts(actor); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 602d8d5e7..456d1491f 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -87,6 +87,20 @@ namespace MWWorld std::map mDoorStates; ///< only holds doors that are currently moving. 0 means closing, 1 opening + struct ProjectileState + { + // Id of spell or enchantment to apply when it hits + std::string mId; + + // Actor who casted this projectile + std::string mActorHandle; + + // Name of item to display as effect source in magic menu (in case we casted an enchantment) + std::string mSourceName; + }; + + std::map mProjectiles; + int getDaysPerMonth (int month) const; void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust); @@ -112,6 +126,8 @@ namespace MWWorld void processDoors(float duration); ///< Run physics simulation and modify \a world accordingly. + void moveProjectiles(float duration); + void doPhysics(float duration); ///< Run physics simulation and modify \a world accordingly. @@ -475,6 +491,9 @@ namespace MWWorld virtual void castSpell (const MWWorld::Ptr& actor); virtual void updateAnimParts(const MWWorld::Ptr& ptr); + + virtual void launchProjectile (const std::string& id, const ESM::EffectList& effects, + const MWWorld::Ptr& actor, const std::string& sourceName); }; }