From 9b0e82a37f07c10fa27e1deb1decc7b7e2dd8578 Mon Sep 17 00:00:00 2001 From: scrawl Date: Wed, 13 Nov 2013 14:02:15 +0100 Subject: [PATCH] Projectile models are now spawned (no movement or impact yet). Refactored trap activation to apply range types properly. Handle ContinuousVFX for magic effects (note they aren't stopped yet when the effect ends) --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwbase/world.hpp | 3 + apps/openmw/mwclass/container.cpp | 8 +-- apps/openmw/mwclass/door.cpp | 8 +-- apps/openmw/mwclass/npc.cpp | 5 +- apps/openmw/mwmechanics/activespells.cpp | 6 +- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/magiceffects.hpp | 2 + apps/openmw/mwrender/animation.cpp | 69 +++++++++++++++++------ apps/openmw/mwrender/animation.hpp | 22 +++++++- apps/openmw/mwworld/actionapply.hpp | 1 - apps/openmw/mwworld/actiontrap.cpp | 28 +++++++++ apps/openmw/mwworld/actiontrap.hpp | 29 ++++++++++ apps/openmw/mwworld/inventorystore.cpp | 1 + apps/openmw/mwworld/worldimp.cpp | 72 ++++++++++++++++++++++++ apps/openmw/mwworld/worldimp.hpp | 19 +++++++ 16 files changed, 242 insertions(+), 35 deletions(-) create mode 100644 apps/openmw/mwworld/actiontrap.cpp create mode 100644 apps/openmw/mwworld/actiontrap.hpp 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); }; }