diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp
index 6cb0dc55a..3d52ce8e6 100644
--- a/apps/openmw/mwmechanics/actors.cpp
+++ b/apps/openmw/mwmechanics/actors.cpp
@@ -374,6 +374,13 @@ namespace MWMechanics
 
         if(!paused)
         {
+            // Note: we need to do this before any of the animations are updated.
+            // Reaching the text keys may trigger Hit / Spellcast (and as such, particles),
+            // so updating VFX immediately after that would just remove the particle effects instantly.
+            // There needs to be a magic effect update in between.
+            for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
+                iter->second->updateContinuousVfx();
+
             for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter)
                 iter->second->update(duration);
         }
diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp
index 0a13f7c8f..94aceba38 100644
--- a/apps/openmw/mwmechanics/character.cpp
+++ b/apps/openmw/mwmechanics/character.cpp
@@ -716,8 +716,6 @@ void CharacterController::update(float duration)
     const MWWorld::Class &cls = MWWorld::Class::get(mPtr);
     Ogre::Vector3 movement(0.0f);
 
-    updateContinuousVfx();
-
     if(!cls.isActor())
     {
         if(mAnimQueue.size() > 1)
diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp
index c82b29b47..0b55534a6 100644
--- a/apps/openmw/mwmechanics/character.hpp
+++ b/apps/openmw/mwmechanics/character.hpp
@@ -173,12 +173,13 @@ class CharacterController
 
     bool updateNpcState(bool onground, bool inwater, bool isrunning, bool sneak);
 
-    void updateContinuousVfx();
-
 public:
     CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim);
     virtual ~CharacterController();
 
+    // Be careful when to call this, see comment in Actors
+    void updateContinuousVfx();
+
     void updatePtr(const MWWorld::Ptr &ptr);
 
     void update(float duration);
diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp
index caf3c6481..df1f6a318 100644
--- a/apps/openmw/mwmechanics/spells.cpp
+++ b/apps/openmw/mwmechanics/spells.cpp
@@ -32,7 +32,7 @@ namespace MWMechanics
 
             std::vector<float> random;
             random.resize(spell->mEffects.mList.size());
-            for (int i=0; i<random.size();++i)
+            for (unsigned int i=0; i<random.size();++i)
                 random[i] = static_cast<float> (std::rand()) / RAND_MAX;
             mSpells.insert (std::make_pair (spellId, random));
         }
@@ -51,6 +51,8 @@ namespace MWMechanics
 
     MagicEffects Spells::getMagicEffects() const
     {
+        // TODO: These are recalculated every frame, no need to do that
+
         MagicEffects effects;
 
         for (TIterator iter = mSpells.begin(); iter!=mSpells.end(); ++iter)
diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp
index d92912fa9..cc92e6248 100644
--- a/apps/openmw/mwrender/animation.cpp
+++ b/apps/openmw/mwrender/animation.cpp
@@ -998,7 +998,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con
 {
     // Early out if we already have this effect
     for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
-        if (it->mEffectId == effectId)
+        if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename)
             return;
 
     EffectParams params;
@@ -1006,6 +1006,7 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con
     params.mObjects = NifOgre::Loader::createObjects(mInsert, model);
     params.mLoop = loop;
     params.mEffectId = effectId;
+    params.mBoneName = bonename;
 
     for(size_t i = 0;i < params.mObjects.mControllers.size();i++)
     {
diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp
index 11adf0c58..b1572b6a1 100644
--- a/apps/openmw/mwrender/animation.hpp
+++ b/apps/openmw/mwrender/animation.hpp
@@ -115,6 +115,7 @@ protected:
         NifOgre::ObjectList mObjects;
         int mEffectId;
         bool mLoop;
+        std::string mBoneName;
     };
 
     std::vector<EffectParams> mEffects;
diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp
index 7a5e59f36..d032b2613 100644
--- a/apps/openmw/mwworld/inventorystore.cpp
+++ b/apps/openmw/mwworld/inventorystore.cpp
@@ -308,25 +308,29 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
                     // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case.
                     mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random;
 
-                    // Only the sound of the first effect plays
-                    if (effectIt == enchantment.mEffects.mList.begin())
+                    // TODO: What do we do if no animation yet?
+                    if (MWBase::Environment::get().getWorld()->getAnimation(actor))
                     {
-                        static const std::string schools[] = {
-                            "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
-                        };
+                        // Only the sound of the first effect plays
+                        if (effectIt == enchantment.mEffects.mList.begin())
+                        {
+                            static const std::string schools[] = {
+                                "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
+                            };
 
-                        MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
-                        if(!magicEffect->mHitSound.empty())
-                            sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f);
-                        else
-                            sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
-                    }
+                            MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
+                            if(!magicEffect->mHitSound.empty())
+                                sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f);
+                            else
+                                sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
+                        }
 
-                    if (!magicEffect->mHit.empty())
-                    {
-                        const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
-                        bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
-                        MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
+                        if (!magicEffect->mHit.empty())
+                        {
+                            const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
+                            bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
+                            MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
+                        }
                     }
                 }