mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-25 11:26:37 +00:00 
			
		
		
		
	Lots of cleanup. Implemented Absorb and Resist. Implemented several instant effects. Added hand VFX.
This commit is contained in:
		
							parent
							
								
									7fd5f1df83
								
							
						
					
					
						commit
						0dc2e829dd
					
				
					 24 changed files with 801 additions and 587 deletions
				
			
		|  | @ -114,6 +114,11 @@ namespace MWBase | ||||||
|         /// references that are currently not in the scene should be ignored.
 |         /// references that are currently not in the scene should be ignored.
 | ||||||
| 
 | 
 | ||||||
|         virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; |         virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0; | ||||||
|  | 
 | ||||||
|  |         /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
 | ||||||
|  |         /// paused we may want to do it manually (after equipping permanent enchantment)
 | ||||||
|  |         virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0; | ||||||
|  | 
 | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ | ||||||
| #include "../mwmechanics/creaturestats.hpp" | #include "../mwmechanics/creaturestats.hpp" | ||||||
| #include "../mwmechanics/npcstats.hpp" | #include "../mwmechanics/npcstats.hpp" | ||||||
| #include "../mwmechanics/movement.hpp" | #include "../mwmechanics/movement.hpp" | ||||||
|  | #include "../mwmechanics/spellcasting.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../mwworld/ptr.hpp" | #include "../mwworld/ptr.hpp" | ||||||
| #include "../mwworld/actiontalk.hpp" | #include "../mwworld/actiontalk.hpp" | ||||||
|  | @ -467,12 +468,9 @@ namespace MWClass | ||||||
|                 else |                 else | ||||||
|                 { |                 { | ||||||
|                     weapon.getCellRef().mEnchantmentCharge -= castCost; |                     weapon.getCellRef().mEnchantmentCharge -= castCost; | ||||||
|                     // Touch
 | 
 | ||||||
|                     othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ptr, ESM::RT_Touch, weapon.getClass().getName(weapon)); |                     MWMechanics::CastSpell cast(ptr, victim); | ||||||
|                     // Self
 |                     cast.cast(weapon); | ||||||
|                     getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ptr, ESM::RT_Self, weapon.getClass().getName(weapon)); |  | ||||||
|                     // Target
 |  | ||||||
|                     MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon)); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -932,11 +930,8 @@ namespace MWClass | ||||||
|     bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, |     bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id, | ||||||
|         const MWWorld::Ptr& actor) const |         const MWWorld::Ptr& actor) const | ||||||
|     { |     { | ||||||
|         MWMechanics::CreatureStats& stats = getCreatureStats (ptr); |         MWMechanics::CastSpell cast(ptr, ptr); | ||||||
| 
 |         return cast.cast(id); | ||||||
|         /// \todo consider instant effects
 |  | ||||||
| 
 |  | ||||||
|         return stats.getActiveSpells().addSpell (id, actor, actor); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const |     void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const | ||||||
|  |  | ||||||
|  | @ -21,17 +21,17 @@ | ||||||
| namespace MWGui | namespace MWGui | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
|     void EffectSourceVisitor::visit (const ESM::ENAMstruct& enam, |     void EffectSourceVisitor::visit (MWMechanics::EffectKey key, | ||||||
|                                            const std::string& sourceName, float magnitude, float remainingTime) |                                            const std::string& sourceName, float magnitude, float remainingTime) | ||||||
|     { |     { | ||||||
|         MagicEffectInfo newEffectSource; |         MagicEffectInfo newEffectSource; | ||||||
|         newEffectSource.mKey = MWMechanics::EffectKey(enam); |         newEffectSource.mKey = key; | ||||||
|         newEffectSource.mMagnitude = magnitude; |         newEffectSource.mMagnitude = magnitude; | ||||||
|         newEffectSource.mPermanent = mIsPermanent; |         newEffectSource.mPermanent = mIsPermanent; | ||||||
|         newEffectSource.mRemainingTime = remainingTime; |         newEffectSource.mRemainingTime = remainingTime; | ||||||
|         newEffectSource.mSource = sourceName; |         newEffectSource.mSource = sourceName; | ||||||
| 
 | 
 | ||||||
|         mEffectSources[enam.mEffectID].push_back(newEffectSource); |         mEffectSources[key.mId].push_back(newEffectSource); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ namespace MWGui | ||||||
| 
 | 
 | ||||||
|         std::map <int, std::vector<MagicEffectInfo> > mEffectSources; |         std::map <int, std::vector<MagicEffectInfo> > mEffectSources; | ||||||
| 
 | 
 | ||||||
|         virtual void visit (const ESM::ENAMstruct& enam, |         virtual void visit (MWMechanics::EffectKey key, | ||||||
|                                  const std::string& sourceName, float magnitude, float remainingTime = -1); |                                  const std::string& sourceName, float magnitude, float remainingTime = -1); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,31 +1,7 @@ | ||||||
| 
 |  | ||||||
| #include "activespells.hpp" | #include "activespells.hpp" | ||||||
| 
 | 
 | ||||||
| #include <cstdlib> |  | ||||||
| 
 |  | ||||||
| #include <boost/algorithm/string.hpp> |  | ||||||
| 
 |  | ||||||
| #include <components/esm/loadalch.hpp> |  | ||||||
| #include <components/esm/loadspel.hpp> |  | ||||||
| #include <components/esm/loadingr.hpp> |  | ||||||
| #include <components/esm/loadmgef.hpp> |  | ||||||
| #include <components/esm/loadskil.hpp> |  | ||||||
| 
 |  | ||||||
| #include "../mwworld/esmstore.hpp" |  | ||||||
| 
 |  | ||||||
| #include "../mwbase/environment.hpp" | #include "../mwbase/environment.hpp" | ||||||
| #include "../mwbase/world.hpp" | #include "../mwbase/world.hpp" | ||||||
| #include "../mwbase/soundmanager.hpp" |  | ||||||
| #include "../mwbase/windowmanager.hpp" |  | ||||||
| 
 |  | ||||||
| #include "../mwrender/animation.hpp" |  | ||||||
| 
 |  | ||||||
| #include "../mwworld/class.hpp" |  | ||||||
| 
 |  | ||||||
| #include "../mwmechanics/spellcasting.hpp" |  | ||||||
| 
 |  | ||||||
| #include "creaturestats.hpp" |  | ||||||
| #include "npcstats.hpp" |  | ||||||
| 
 | 
 | ||||||
| namespace MWMechanics | namespace MWMechanics | ||||||
| { | { | ||||||
|  | @ -35,6 +11,7 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|         MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp(); |         MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp(); | ||||||
| 
 | 
 | ||||||
|  |         // Erase no longer active spells
 | ||||||
|         if (mLastUpdate!=now) |         if (mLastUpdate!=now) | ||||||
|         { |         { | ||||||
|             TContainer::iterator iter (mSpells.begin()); |             TContainer::iterator iter (mSpells.begin()); | ||||||
|  | @ -42,7 +19,6 @@ namespace MWMechanics | ||||||
|                 if (!timeToExpire (iter)) |                 if (!timeToExpire (iter)) | ||||||
|                 { |                 { | ||||||
|                     mSpells.erase (iter++); |                     mSpells.erase (iter++); | ||||||
|                     //onSpellExpired
 |  | ||||||
|                     rebuild = true; |                     rebuild = true; | ||||||
|                 } |                 } | ||||||
|                 else |                 else | ||||||
|  | @ -69,277 +45,28 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|         for (TIterator iter (begin()); iter!=end(); ++iter) |         for (TIterator iter (begin()); iter!=end(); ++iter) | ||||||
|         { |         { | ||||||
|             std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iter->first); |  | ||||||
| 
 |  | ||||||
|             const MWWorld::TimeStamp& start = iter->second.mTimeStamp; |             const MWWorld::TimeStamp& start = iter->second.mTimeStamp; | ||||||
| 
 | 
 | ||||||
|             int i = 0; |             const std::vector<Effect>& effects = iter->second.mEffects; | ||||||
|             for (std::vector<ESM::ENAMstruct>::const_iterator effectIter (effects.first.mList.begin()); | 
 | ||||||
|                 effectIter!=effects.first.mList.end(); ++effectIter, ++i) |             for (std::vector<Effect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt) | ||||||
|             { |             { | ||||||
|                 float random = iter->second.mRandom[i]; |                 int duration = effectIt->mDuration; | ||||||
|                 if (effectIter->mRange != iter->second.mRange) |  | ||||||
|                     continue; |  | ||||||
| 
 |  | ||||||
|                 if (effectIter->mDuration) |  | ||||||
|                 { |  | ||||||
|                     int duration = effectIter->mDuration; |  | ||||||
|                      |  | ||||||
|                     if (effects.second.first) |  | ||||||
|                         duration *= random; |  | ||||||
|                      |  | ||||||
|                 MWWorld::TimeStamp end = start; |                 MWWorld::TimeStamp end = start; | ||||||
|                 end += static_cast<double> (duration)* |                 end += static_cast<double> (duration)* | ||||||
|                     MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); |                     MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); | ||||||
| 
 | 
 | ||||||
|                 if (end>now) |                 if (end>now) | ||||||
|                     { |                     mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude)); | ||||||
|                         EffectParam param; |  | ||||||
|                          |  | ||||||
|                         if (effects.second.first) |  | ||||||
|                         { |  | ||||||
|                             const ESM::MagicEffect *magicEffect = |  | ||||||
|                                 MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( |  | ||||||
|                                 effectIter->mEffectID); |  | ||||||
|                                  |  | ||||||
|                             if (effectIter->mDuration==0) |  | ||||||
|                             { |  | ||||||
|                                 param.mMagnitude = |  | ||||||
|                                     static_cast<int> (random / (0.1 * magicEffect->mData.mBaseCost)); |  | ||||||
|                             } |  | ||||||
|                             else |  | ||||||
|                             { |  | ||||||
|                                 param.mMagnitude = |  | ||||||
|                                     static_cast<int> (0.05*random / (0.1 * magicEffect->mData.mBaseCost)); |  | ||||||
|             } |             } | ||||||
|         }     |         }     | ||||||
|                         else |  | ||||||
|                             param.mMagnitude = static_cast<int> ( |  | ||||||
|                                 (effectIter->mMagnMax-effectIter->mMagnMin)*random + effectIter->mMagnMin); |  | ||||||
|                         param.mMagnitude *= iter->second.mMultiplier[i]; |  | ||||||
| 
 |  | ||||||
|                         if (param.mMagnitude) |  | ||||||
|                             mEffects.add (*effectIter, param); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }     |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::pair<ESM::EffectList, std::pair<bool, bool> > ActiveSpells::getEffectList (const std::string& id) const |  | ||||||
|     { |  | ||||||
|         if (const ESM::Enchantment* enchantment = |  | ||||||
|             MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search (id)) |  | ||||||
|             return std::make_pair (enchantment->mEffects, std::make_pair(false, false)); |  | ||||||
| 
 |  | ||||||
|         if (const ESM::Spell *spell = |  | ||||||
|             MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id)) |  | ||||||
|             return std::make_pair (spell->mEffects, std::make_pair(false, false)); |  | ||||||
| 
 |  | ||||||
|         if (const ESM::Potion *potion = |  | ||||||
|             MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id)) |  | ||||||
|             return std::make_pair (potion->mEffects, std::make_pair(false, true)); |  | ||||||
| 
 |  | ||||||
|         if (const ESM::Ingredient *ingredient = |  | ||||||
|             MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id)) |  | ||||||
|         { |  | ||||||
|             const ESM::MagicEffect *magicEffect = |  | ||||||
|                 MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( |  | ||||||
|                 ingredient->mData.mEffectID[0]); |  | ||||||
|          |  | ||||||
|             ESM::ENAMstruct effect; |  | ||||||
|             effect.mEffectID = ingredient->mData.mEffectID[0]; |  | ||||||
|             effect.mSkill = ingredient->mData.mSkills[0]; |  | ||||||
|             effect.mAttribute = ingredient->mData.mAttributes[0]; |  | ||||||
|             effect.mRange = 0; |  | ||||||
|             effect.mArea = 0; |  | ||||||
|             effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1; |  | ||||||
|             effect.mMagnMin = 1; |  | ||||||
|             effect.mMagnMax = 1; |  | ||||||
|              |  | ||||||
|             std::pair<ESM::EffectList, std::pair<bool, bool> > result; |  | ||||||
|             result.second.second = true; |  | ||||||
|             result.second.first = true; |  | ||||||
| 
 |  | ||||||
|             result.first.mList.push_back (effect); |  | ||||||
| 
 |  | ||||||
|             return result; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         throw std::runtime_error ("ID " + id + " can not produce lasting effects"); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     std::string ActiveSpells::getSpellDisplayName (const std::string& id) const |  | ||||||
|     { |  | ||||||
|         if (const ESM::Spell *spell = |  | ||||||
|             MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id)) |  | ||||||
|             return spell->mName; |  | ||||||
| 
 |  | ||||||
|         if (const ESM::Potion *potion = |  | ||||||
|             MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id)) |  | ||||||
|             return potion->mName; |  | ||||||
| 
 |  | ||||||
|         if (const ESM::Ingredient *ingredient = |  | ||||||
|             MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id)) |  | ||||||
|             return ingredient->mName; |  | ||||||
| 
 |  | ||||||
|         throw std::runtime_error ("ID " + id + " has no display name"); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ActiveSpells::ActiveSpells() |     ActiveSpells::ActiveSpells() | ||||||
|     : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) |         : mSpellsChanged (false) | ||||||
|  |         , mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) | ||||||
|     {} |     {} | ||||||
| 
 | 
 | ||||||
|     bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range, const std::string& name, int effectIndex) |  | ||||||
|     { |  | ||||||
|         const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); |  | ||||||
| 
 |  | ||||||
|         std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (id); |  | ||||||
|         bool stacks = effects.second.second; |  | ||||||
| 
 |  | ||||||
|         bool found = false; |  | ||||||
| 
 |  | ||||||
|         for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin()); |  | ||||||
|             iter!=effects.first.mList.end(); ++iter) |  | ||||||
|         { |  | ||||||
|             if (iter->mRange != range) |  | ||||||
|                 continue; |  | ||||||
|             if (iter->mDuration) |  | ||||||
|             { |  | ||||||
|                 found = true; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // If none of the effects need to apply, no need to add the spell
 |  | ||||||
|         if (!found) |  | ||||||
|             return false; |  | ||||||
| 
 |  | ||||||
|         TContainer::iterator iter = mSpells.find (id); |  | ||||||
| 
 |  | ||||||
|         ActiveSpellParams params; |  | ||||||
|         for (unsigned int i=0; i<effects.first.mList.size(); ++i) |  | ||||||
|         { |  | ||||||
|             float random = static_cast<float> (std::rand()) / RAND_MAX; |  | ||||||
|             if (effects.second.first) |  | ||||||
|             { |  | ||||||
|                 // ingredient -> special treatment required.
 |  | ||||||
|                 const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); |  | ||||||
| 
 |  | ||||||
|                 float x = |  | ||||||
|                     (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + |  | ||||||
|                     0.2 * creatureStats.getAttribute (1).getModified() |  | ||||||
|                     + 0.1 * creatureStats.getAttribute (7).getModified()) |  | ||||||
|                     * creatureStats.getFatigueTerm(); |  | ||||||
|                 random *= 100; |  | ||||||
|                 random = random / std::min (x, 100.0f); |  | ||||||
|                 random *= 0.25 * x; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             params.mRandom.push_back(random); |  | ||||||
|         } |  | ||||||
|         params.mRange = range; |  | ||||||
|         params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); |  | ||||||
|         params.mName = name; |  | ||||||
|         params.mMultiplier.resize(effects.first.mList.size(), 1); |  | ||||||
| 
 |  | ||||||
|         /*
 |  | ||||||
|         for (int i=0; i<effects.first.mList.size(); ++i) |  | ||||||
|         { |  | ||||||
|             if (iter->mRange != range) |  | ||||||
|             { |  | ||||||
|                 params.mDisabled.push_back(true); |  | ||||||
|                 continue; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             bool disabled = false; |  | ||||||
| 
 |  | ||||||
|             int reflect = creatureStats.getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; |  | ||||||
|             int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
 |  | ||||||
|             if (roll < reflect) |  | ||||||
|                 disabled = true; |  | ||||||
|         } |  | ||||||
|         */ |  | ||||||
| 
 |  | ||||||
|         bool first=true; |  | ||||||
|         int i = 0; |  | ||||||
|         for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.first.mList.begin()); |  | ||||||
|             effectIt!=effects.first.mList.end(); ++effectIt, ++i) |  | ||||||
|         { |  | ||||||
|             if (effectIt->mRange != range) |  | ||||||
|                 continue; |  | ||||||
| 
 |  | ||||||
|             const ESM::MagicEffect *magicEffect = |  | ||||||
|                 MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( |  | ||||||
|                 effectIt->mEffectID); |  | ||||||
| 
 |  | ||||||
|             if (caster.getRefData().getHandle() == "player" && actor != caster |  | ||||||
|                     && magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) |  | ||||||
|                 MWBase::Environment::get().getWindowManager()->setEnemy(actor); |  | ||||||
| 
 |  | ||||||
|             // Try resisting effect in case its harmful
 |  | ||||||
|             const ESM::Spell *spell = |  | ||||||
|                         MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id); |  | ||||||
|             params.mMultiplier[i] = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, caster, spell); |  | ||||||
|             if (params.mMultiplier[i] == 0) |  | ||||||
|             { |  | ||||||
|                 if (actor.getRefData().getHandle() == "player") |  | ||||||
|                     MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); |  | ||||||
|                 else |  | ||||||
|                     MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // If fully resisted, don't play sounds or particles
 |  | ||||||
|             if (params.mMultiplier[i] == 0) |  | ||||||
|                 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.
 |  | ||||||
| 
 |  | ||||||
|             // Only the sound of the first effect plays
 |  | ||||||
|             if (first) |  | ||||||
|             { |  | ||||||
|                 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); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             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, ""); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             first = false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (iter==mSpells.end() || stacks) |  | ||||||
|             mSpells.insert (std::make_pair (id, params)); |  | ||||||
|         else |  | ||||||
|             iter->second = params; |  | ||||||
| 
 |  | ||||||
|         mSpellsChanged = true; |  | ||||||
| 
 |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void ActiveSpells::removeSpell (const std::string& id) |  | ||||||
|     { |  | ||||||
|         TContainer::iterator iter = mSpells.find (id); |  | ||||||
| 
 |  | ||||||
|         if (iter!=mSpells.end()) |  | ||||||
|         { |  | ||||||
|             mSpells.erase (iter); |  | ||||||
|             mSpellsChanged = true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     const MagicEffects& ActiveSpells::getMagicEffects() const |     const MagicEffects& ActiveSpells::getMagicEffects() const | ||||||
|     { |     { | ||||||
|         update(); |         update(); | ||||||
|  | @ -348,33 +75,27 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|     ActiveSpells::TIterator ActiveSpells::begin() const |     ActiveSpells::TIterator ActiveSpells::begin() const | ||||||
|     { |     { | ||||||
|         update(); |  | ||||||
|         return mSpells.begin(); |         return mSpells.begin(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ActiveSpells::TIterator ActiveSpells::end() const |     ActiveSpells::TIterator ActiveSpells::end() const | ||||||
|     { |     { | ||||||
|         update(); |  | ||||||
|         return mSpells.end(); |         return mSpells.end(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     double ActiveSpells::timeToExpire (const TIterator& iterator) const |     double ActiveSpells::timeToExpire (const TIterator& iterator) const | ||||||
|     { |     { | ||||||
|         std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iterator->first); |         const std::vector<Effect>& effects = iterator->second.mEffects; | ||||||
| 
 | 
 | ||||||
|         int duration = 0; |         int duration = 0; | ||||||
| 
 | 
 | ||||||
|         for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin()); |         for (std::vector<Effect>::const_iterator iter (effects.begin()); | ||||||
|             iter!=effects.first.mList.end(); ++iter) |             iter!=effects.end(); ++iter) | ||||||
|         { |         { | ||||||
|             if (iter->mDuration > duration) |             if (iter->mDuration > duration) | ||||||
|                 duration = iter->mDuration; |                 duration = iter->mDuration; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Scale duration by magnitude if needed
 |  | ||||||
|         if (effects.second.first && iterator->second.mRandom.size()) |  | ||||||
|             duration *= iterator->second.mRandom.front(); |  | ||||||
| 
 |  | ||||||
|         double scaledDuration = duration * |         double scaledDuration = duration * | ||||||
|               MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); |               MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60); | ||||||
| 
 | 
 | ||||||
|  | @ -405,48 +126,67 @@ namespace MWMechanics | ||||||
|         return mSpells; |         return mSpells; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector<Effect> effects, const std::string &displayName) | ||||||
|  |     { | ||||||
|  |         bool exists = false; | ||||||
|  |         for (TContainer::const_iterator it = begin(); it != end(); ++it) | ||||||
|  |         { | ||||||
|  |             if (id == it->first) | ||||||
|  |                 exists = true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         ActiveSpellParams params; | ||||||
|  |         params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp(); | ||||||
|  |         params.mEffects = effects; | ||||||
|  |         params.mDisplayName = displayName; | ||||||
|  | 
 | ||||||
|  |         if (!exists || stack) | ||||||
|  |             mSpells.insert (std::make_pair(id, params)); | ||||||
|  |         else | ||||||
|  |             mSpells.find(id)->second = params; | ||||||
|  | 
 | ||||||
|  |         mSpellsChanged = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const |     void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const | ||||||
|     { |     { | ||||||
|         for (TContainer::const_iterator it = begin(); it != end(); ++it) |         for (TContainer::const_iterator it = begin(); it != end(); ++it) | ||||||
|         { |         { | ||||||
|             const ESM::EffectList& list = getEffectList(it->first).first; |  | ||||||
| 
 |  | ||||||
|             float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); |             float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); | ||||||
| 
 | 
 | ||||||
|             int i=0; |             for (std::vector<Effect>::const_iterator effectIt = it->second.mEffects.begin(); | ||||||
|             for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin(); |                  effectIt != it->second.mEffects.end(); ++effectIt) | ||||||
|                  effectIt != list.mList.end(); ++effectIt, ++i) |  | ||||||
|             { |             { | ||||||
|                 if (effectIt->mRange != it->second.mRange) |                 std::string name = it->second.mDisplayName; | ||||||
|                     continue; |  | ||||||
| 
 |  | ||||||
|                 std::string name; |  | ||||||
|                 if (it->second.mName.empty()) |  | ||||||
|                     name = getSpellDisplayName(it->first); |  | ||||||
|                 else |  | ||||||
|                     name = it->second.mName; |  | ||||||
| 
 | 
 | ||||||
|                 float remainingTime = effectIt->mDuration + |                 float remainingTime = effectIt->mDuration + | ||||||
|                         (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; |                         (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; | ||||||
|                 float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second.mRandom[i]; |                 float magnitude = effectIt->mMagnitude; | ||||||
| 
 | 
 | ||||||
|                 // hack for ingredients
 |  | ||||||
|                 if (MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (it->first)) |  | ||||||
|                 { |  | ||||||
|                     const ESM::MagicEffect *magicEffect = |  | ||||||
|                         MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( |  | ||||||
|                         effectIt->mEffectID); |  | ||||||
| 
 |  | ||||||
|                     remainingTime = effectIt->mDuration * it->second.mRandom[i] + |  | ||||||
|                             (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; |  | ||||||
| 
 |  | ||||||
|                     magnitude = static_cast<int> (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 magnitude *= it->second.mMultiplier[i]; |  | ||||||
|                 if (magnitude) |                 if (magnitude) | ||||||
|                     visitor.visit(*effectIt, name, magnitude, remainingTime); |                     visitor.visit(effectIt->mKey, name, magnitude, remainingTime); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     void ActiveSpells::purgeAll() | ||||||
|  |     { | ||||||
|  |         mSpells.clear(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void ActiveSpells::purgeEffect(short effectId) | ||||||
|  |     { | ||||||
|  |         for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) | ||||||
|  |         { | ||||||
|  |             for (std::vector<Effect>::iterator effectIt = it->second.mEffects.begin(); | ||||||
|  |                  effectIt != it->second.mEffects.end();) | ||||||
|  |             { | ||||||
|  |                 if (effectIt->mKey.mId == effectId) | ||||||
|  |                     effectIt = it->second.mEffects.erase(effectIt); | ||||||
|  |                 else | ||||||
|  |                     effectIt++; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -11,39 +11,8 @@ | ||||||
| 
 | 
 | ||||||
| #include <components/esm/defs.hpp> | #include <components/esm/defs.hpp> | ||||||
| 
 | 
 | ||||||
| namespace ESM |  | ||||||
| { |  | ||||||
|     struct Spell; |  | ||||||
|     struct EffectList; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| namespace MWWorld |  | ||||||
| { |  | ||||||
|     class Ptr; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| namespace MWMechanics | namespace MWMechanics | ||||||
| { | { | ||||||
|     struct ActiveSpellParams |  | ||||||
|     { |  | ||||||
|         // Only apply effects of this range type
 |  | ||||||
|         ESM::RangeType mRange; |  | ||||||
| 
 |  | ||||||
|         // When the spell was added
 |  | ||||||
|         MWWorld::TimeStamp mTimeStamp; |  | ||||||
| 
 |  | ||||||
|         // Random factor for each effect
 |  | ||||||
|         std::vector<float> mRandom; |  | ||||||
| 
 |  | ||||||
|         // Effect magnitude multiplier. Use 0 to completely disable the effect
 |  | ||||||
|         // (if it was resisted, reflected or absorbed). Use (0,1) for partially resisted.
 |  | ||||||
|         std::vector<float> mMultiplier; |  | ||||||
| 
 |  | ||||||
|         // Display name, we need this for enchantments, which don't have a name - so you need to supply the
 |  | ||||||
|         // name of the item with the enchantment to addSpell
 |  | ||||||
|         std::string mName; |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     /// \brief Lasting spell effects
 |     /// \brief Lasting spell effects
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// \note The name of this class is slightly misleading, since it also handels lasting potion
 |     /// \note The name of this class is slightly misleading, since it also handels lasting potion
 | ||||||
|  | @ -52,6 +21,23 @@ namespace MWMechanics | ||||||
|     { |     { | ||||||
|         public: |         public: | ||||||
| 
 | 
 | ||||||
|  |             // Parameters of an effect concerning lasting effects.
 | ||||||
|  |             // Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc.
 | ||||||
|  |             // It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster.
 | ||||||
|  |             struct Effect | ||||||
|  |             { | ||||||
|  |                 float mMagnitude; | ||||||
|  |                 EffectKey mKey; | ||||||
|  |                 float mDuration; | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             struct ActiveSpellParams | ||||||
|  |             { | ||||||
|  |                 std::vector<Effect> mEffects; | ||||||
|  |                 MWWorld::TimeStamp mTimeStamp; | ||||||
|  |                 std::string mDisplayName; | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|             typedef std::multimap<std::string, ActiveSpellParams > TContainer; |             typedef std::multimap<std::string, ActiveSpellParams > TContainer; | ||||||
|             typedef TContainer::const_iterator TIterator; |             typedef TContainer::const_iterator TIterator; | ||||||
| 
 | 
 | ||||||
|  | @ -66,9 +52,6 @@ namespace MWMechanics | ||||||
|              |              | ||||||
|             void rebuildEffects() const; |             void rebuildEffects() const; | ||||||
| 
 | 
 | ||||||
|             std::pair<ESM::EffectList, std::pair<bool, bool> > getEffectList (const std::string& id) const; |  | ||||||
|             ///< @return (EffectList, (isIngredient, stacks))
 |  | ||||||
| 
 |  | ||||||
|             double timeToExpire (const TIterator& iterator) const; |             double timeToExpire (const TIterator& iterator) const; | ||||||
|             ///< Returns time (in in-game hours) until the spell pointed to by \a iterator
 |             ///< Returns time (in in-game hours) until the spell pointed to by \a iterator
 | ||||||
|             /// expires.
 |             /// expires.
 | ||||||
|  | @ -79,25 +62,25 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|             TIterator end() const; |             TIterator end() const; | ||||||
| 
 | 
 | ||||||
|             std::string getSpellDisplayName (const std::string& id) const; |  | ||||||
| 
 |  | ||||||
|         public: |         public: | ||||||
| 
 | 
 | ||||||
|             ActiveSpells(); |             ActiveSpells(); | ||||||
| 
 | 
 | ||||||
|             bool addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); |             /// Add lasting effects
 | ||||||
|             ///< Overwrites an existing spell with the same ID. If the spell does not have any
 |  | ||||||
|             /// non-instant effects, it is ignored.
 |  | ||||||
|             /// @param id
 |  | ||||||
|             /// @param actor actor to add the spell to
 |  | ||||||
|             /// @param caster actor who casted the spell
 |  | ||||||
|             /// @param range Only effects with range type \a range will be applied
 |  | ||||||
|             /// @param name Display name for enchantments, since they don't have a name in their record
 |  | ||||||
|             /// @param effectIndex Only apply one specific effect - useful for reflecting spells, since each effect is reflected individually
 |  | ||||||
|             ///
 |             ///
 | ||||||
|             /// \return Has the spell been added?
 |             /// \brief addSpell
 | ||||||
|  |             /// \param id ID for stacking purposes.
 | ||||||
|  |             /// \param stack If false, the spell is not added if one with the same ID exists already.
 | ||||||
|  |             /// \param effects
 | ||||||
|  |             /// \param displayName Name for display in magic menu.
 | ||||||
|  |             ///
 | ||||||
|  |             void addSpell (const std::string& id, bool stack, std::vector<Effect> effects, const std::string& displayName); | ||||||
| 
 | 
 | ||||||
|             void removeSpell (const std::string& id); |             /// Remove all active effects with this id
 | ||||||
|  |             void purgeEffect (short effectId); | ||||||
|  | 
 | ||||||
|  |             /// Remove all active effects
 | ||||||
|  |             void purgeAll (); | ||||||
| 
 | 
 | ||||||
|             bool isSpellActive (std::string id) const; |             bool isSpellActive (std::string id) const; | ||||||
|             ///< case insensitive
 |             ///< case insensitive
 | ||||||
|  |  | ||||||
|  | @ -53,6 +53,10 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|             Actors(); |             Actors(); | ||||||
| 
 | 
 | ||||||
|  |             /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
 | ||||||
|  |             /// paused we may want to do it manually (after equipping permanent enchantment)
 | ||||||
|  |             void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); } | ||||||
|  | 
 | ||||||
|             void addActor (const MWWorld::Ptr& ptr); |             void addActor (const MWWorld::Ptr& ptr); | ||||||
|             ///< Register an actor for stats management
 |             ///< Register an actor for stats management
 | ||||||
|             ///
 |             ///
 | ||||||
|  |  | ||||||
|  | @ -516,6 +516,12 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun | ||||||
|                     const ESM::Static* castStatic = store.get<ESM::Static>().find (effect->mCasting); |                     const ESM::Static* castStatic = store.get<ESM::Static>().find (effect->mCasting); | ||||||
|                     mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); |                     mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex); | ||||||
| 
 | 
 | ||||||
|  |                     castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Hands"); | ||||||
|  |                     //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle);
 | ||||||
|  |                     //mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle);
 | ||||||
|  |                     mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle); | ||||||
|  |                     mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle); | ||||||
|  | 
 | ||||||
|                     switch(effectentry.mRange) |                     switch(effectentry.mRange) | ||||||
|                     { |                     { | ||||||
|                         case 0: mAttackType = "self"; break; |                         case 0: mAttackType = "self"; break; | ||||||
|  |  | ||||||
|  | @ -12,13 +12,6 @@ namespace ESM | ||||||
| 
 | 
 | ||||||
| namespace MWMechanics | namespace MWMechanics | ||||||
| { | { | ||||||
|     // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display
 |  | ||||||
|     struct EffectSourceVisitor |  | ||||||
|     { |  | ||||||
|         virtual void visit (const ESM::ENAMstruct& enam, |  | ||||||
|                                  const std::string& sourceName, float magnitude, float remainingTime = -1) = 0; |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     struct EffectKey |     struct EffectKey | ||||||
|     { |     { | ||||||
|         int mId; |         int mId; | ||||||
|  | @ -59,6 +52,13 @@ namespace MWMechanics | ||||||
|         return param -= right; |         return param -= right; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display
 | ||||||
|  |     struct EffectSourceVisitor | ||||||
|  |     { | ||||||
|  |         virtual void visit (MWMechanics::EffectKey key, | ||||||
|  |                                  const std::string& sourceName, float magnitude, float remainingTime = -1) = 0; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     /// \brief Effects currently affecting a NPC or creature
 |     /// \brief Effects currently affecting a NPC or creature
 | ||||||
|     class MagicEffects |     class MagicEffects | ||||||
|     { |     { | ||||||
|  |  | ||||||
|  | @ -679,4 +679,9 @@ namespace MWMechanics | ||||||
|             return false; |             return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr) | ||||||
|  |     { | ||||||
|  |         mActors.updateMagicEffects(ptr); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -100,6 +100,11 @@ namespace MWMechanics | ||||||
|         virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); |         virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); | ||||||
|         virtual void skipAnimation(const MWWorld::Ptr& ptr); |         virtual void skipAnimation(const MWWorld::Ptr& ptr); | ||||||
|         virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); |         virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName); | ||||||
|  | 
 | ||||||
|  |             /// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
 | ||||||
|  |             /// paused we may want to do it manually (after equipping permanent enchantment)
 | ||||||
|  |             virtual void updateMagicEffects (const MWWorld::Ptr& ptr); | ||||||
|  | 
 | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										473
									
								
								apps/openmw/mwmechanics/spellcasting.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										473
									
								
								apps/openmw/mwmechanics/spellcasting.cpp
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,473 @@ | ||||||
|  | #include "spellcasting.hpp" | ||||||
|  | 
 | ||||||
|  | #include <boost/format.hpp> | ||||||
|  | 
 | ||||||
|  | #include "../mwbase/windowmanager.hpp" | ||||||
|  | #include "../mwbase/soundmanager.hpp" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #include "../mwworld/containerstore.hpp" | ||||||
|  | 
 | ||||||
|  | #include "../mwrender/animation.hpp" | ||||||
|  | 
 | ||||||
|  | namespace MWMechanics | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  |     CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target) | ||||||
|  |         : mCaster(caster) | ||||||
|  |         , mTarget(target) | ||||||
|  |         , mStack(false) | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, | ||||||
|  |                             const ESM::EffectList &effects, ESM::RangeType range, bool reflected) | ||||||
|  |     { | ||||||
|  |         // If none of the effects need to apply, we can early-out
 | ||||||
|  |         bool found = false; | ||||||
|  |         for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin()); | ||||||
|  |             iter!=effects.mList.end(); ++iter) | ||||||
|  |         { | ||||||
|  |             if (iter->mRange != range) | ||||||
|  |                 continue; | ||||||
|  |             found = true; | ||||||
|  |         } | ||||||
|  |         if (!found) | ||||||
|  |             return; | ||||||
|  | 
 | ||||||
|  |         ESM::EffectList reflectedEffects; | ||||||
|  |         std::vector<ActiveSpells::Effect> appliedLastingEffects; | ||||||
|  |         bool firstAppliedEffect = true; | ||||||
|  | 
 | ||||||
|  |         for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin()); | ||||||
|  |             effectIt!=effects.mList.end(); ++effectIt) | ||||||
|  |         { | ||||||
|  |             if (effectIt->mRange != range) | ||||||
|  |                 continue; | ||||||
|  | 
 | ||||||
|  |             const ESM::MagicEffect *magicEffect = | ||||||
|  |                 MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( | ||||||
|  |                 effectIt->mEffectID); | ||||||
|  | 
 | ||||||
|  |             float magnitudeMult = 1; | ||||||
|  |             if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor()) | ||||||
|  |             { | ||||||
|  |                 // If player is attempting to cast a harmful spell, show the target's HP bar
 | ||||||
|  |                 if (caster.getRefData().getHandle() == "player" && target != caster) | ||||||
|  |                     MWBase::Environment::get().getWindowManager()->setEnemy(target); | ||||||
|  | 
 | ||||||
|  |                 // Try absorbing if it's a spell
 | ||||||
|  |                 // NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure
 | ||||||
|  |                 // if that is worth replicating.
 | ||||||
|  |                 if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId)) | ||||||
|  |                 { | ||||||
|  |                     int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude; | ||||||
|  |                     int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
 | ||||||
|  |                     bool isAbsorbed = (roll < absorb); | ||||||
|  |                     if (isAbsorbed) | ||||||
|  |                     { | ||||||
|  |                         const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb"); | ||||||
|  |                         MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( | ||||||
|  |                                     "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, ""); | ||||||
|  |                         // Magicka is increased by cost of spell
 | ||||||
|  |                         DynamicStat<float> magicka = target.getClass().getCreatureStats(target).getMagicka(); | ||||||
|  |                         magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost); | ||||||
|  |                         target.getClass().getCreatureStats(target).setMagicka(magicka); | ||||||
|  |                         magnitudeMult = 0; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // Try reflecting
 | ||||||
|  |                 if (!reflected && magnitudeMult > 0) | ||||||
|  |                 { | ||||||
|  |                     int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude; | ||||||
|  |                     int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
 | ||||||
|  |                     bool isReflected = (roll < reflect); | ||||||
|  |                     if (isReflected) | ||||||
|  |                     { | ||||||
|  |                         const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect"); | ||||||
|  |                         MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect( | ||||||
|  |                                     "meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, ""); | ||||||
|  |                         reflectedEffects.mList.push_back(*effectIt); | ||||||
|  |                         magnitudeMult = 0; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // Try resisting
 | ||||||
|  |                 if (magnitudeMult > 0 && caster.getClass().isActor()) | ||||||
|  |                 { | ||||||
|  |                     const ESM::Spell *spell = | ||||||
|  |                             MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId); | ||||||
|  |                     magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell); | ||||||
|  |                     if (magnitudeMult == 0) | ||||||
|  |                     { | ||||||
|  |                         // Fully resisted, show message
 | ||||||
|  |                         if (target.getRefData().getHandle() == "player") | ||||||
|  |                             MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}"); | ||||||
|  |                         else | ||||||
|  |                             MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             if (magnitudeMult > 0) | ||||||
|  |             { | ||||||
|  |                 float random = std::rand() / static_cast<float>(RAND_MAX); | ||||||
|  |                 float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; | ||||||
|  |                 magnitude *= magnitudeMult; | ||||||
|  | 
 | ||||||
|  |                 if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) | ||||||
|  |                 { | ||||||
|  |                     ActiveSpells::Effect effect; | ||||||
|  |                     effect.mKey = MWMechanics::EffectKey(*effectIt); | ||||||
|  |                     effect.mDuration = effectIt->mDuration; | ||||||
|  |                     effect.mMagnitude = magnitude; | ||||||
|  | 
 | ||||||
|  |                     appliedLastingEffects.push_back(effect); | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                     applyInstantEffect(mTarget, effectIt->mEffectID, magnitude); | ||||||
|  | 
 | ||||||
|  |                 if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) | ||||||
|  |                 { | ||||||
|  |                     // Play sound, only for the first effect
 | ||||||
|  |                     if (firstAppliedEffect) | ||||||
|  |                     { | ||||||
|  |                         static const std::string schools[] = { | ||||||
|  |                             "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" | ||||||
|  |                         }; | ||||||
|  | 
 | ||||||
|  |                         MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); | ||||||
|  |                         if(!magicEffect->mHitSound.empty()) | ||||||
|  |                             sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f); | ||||||
|  |                         else | ||||||
|  |                             sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f); | ||||||
|  |                         firstAppliedEffect = false; | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     // Add VFX
 | ||||||
|  |                     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; | ||||||
|  |                         // Note: in case of non actor, a free effect should be fine as well
 | ||||||
|  |                         MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target); | ||||||
|  |                         if (anim) | ||||||
|  |                             anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (reflectedEffects.mList.size()) | ||||||
|  |             inflict(caster, target, reflectedEffects, range, true); | ||||||
|  | 
 | ||||||
|  |         if (appliedLastingEffects.size()) | ||||||
|  |             target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, short effectId, float magnitude) | ||||||
|  |     { | ||||||
|  |         if (!target.getClass().isActor()) | ||||||
|  |         { | ||||||
|  |             if (effectId == ESM::MagicEffect::Lock) | ||||||
|  |             { | ||||||
|  |                 if (target.getCellRef().mLockLevel < magnitude) | ||||||
|  |                     target.getCellRef().mLockLevel = magnitude; | ||||||
|  |             } | ||||||
|  |             else if (effectId == ESM::MagicEffect::Open) | ||||||
|  |             { | ||||||
|  |                 // TODO: This is a crime
 | ||||||
|  |                 if (target.getCellRef().mLockLevel <= magnitude) | ||||||
|  |                 { | ||||||
|  |                     if (target.getCellRef().mLockLevel > 0) | ||||||
|  |                         MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f); | ||||||
|  |                     target.getCellRef().mLockLevel = 0; | ||||||
|  |                 } | ||||||
|  |                 else | ||||||
|  |                     MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             if (effectId == ESM::MagicEffect::CurePoison) | ||||||
|  |                 target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison); | ||||||
|  |             else if (effectId == ESM::MagicEffect::CureParalyzation) | ||||||
|  |                 target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze); | ||||||
|  |             else if (effectId == ESM::MagicEffect::CureCommonDisease) | ||||||
|  |                 target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease(); | ||||||
|  |             else if (effectId == ESM::MagicEffect::CureBlightDisease) | ||||||
|  |                 target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease(); | ||||||
|  |             else if (effectId == ESM::MagicEffect::CureCorprusDisease) | ||||||
|  |                 target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease(); | ||||||
|  |             else if (effectId == ESM::MagicEffect::Dispel) | ||||||
|  |                 target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(); | ||||||
|  |             else if (effectId == ESM::MagicEffect::RemoveCurse) | ||||||
|  |                 target.getClass().getCreatureStats(target).getSpells().purgeCurses(); | ||||||
|  | 
 | ||||||
|  |             else if (effectId == ESM::MagicEffect::DivineIntervention) | ||||||
|  |             { | ||||||
|  |                 // We need to be able to get the world location of an interior cell before implementing this
 | ||||||
|  |                 // or alternatively, the last known exterior location of the player, which is how vanilla does it.
 | ||||||
|  |             } | ||||||
|  |             else if (effectId == ESM::MagicEffect::AlmsiviIntervention) | ||||||
|  |             { | ||||||
|  |                 // Same as above
 | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             else if (effectId == ESM::MagicEffect::Mark) | ||||||
|  |             { | ||||||
|  |                 // TODO
 | ||||||
|  |             } | ||||||
|  |             else if (effectId == ESM::MagicEffect::Recall) | ||||||
|  |             { | ||||||
|  |                 // TODO
 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool CastSpell::cast(const std::string &id) | ||||||
|  |     { | ||||||
|  |         if (const ESM::Spell *spell = | ||||||
|  |             MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id)) | ||||||
|  |             return cast(spell); | ||||||
|  | 
 | ||||||
|  |         if (const ESM::Potion *potion = | ||||||
|  |             MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id)) | ||||||
|  |             return cast(potion); | ||||||
|  | 
 | ||||||
|  |         if (const ESM::Ingredient *ingredient = | ||||||
|  |             MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id)) | ||||||
|  |             return cast(ingredient); | ||||||
|  | 
 | ||||||
|  |         throw std::runtime_error("ID type cannot be casted"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool CastSpell::cast(const MWWorld::Ptr &item) | ||||||
|  |     { | ||||||
|  |         std::string enchantmentName = item.getClass().getEnchantment(item); | ||||||
|  |         if (enchantmentName.empty()) | ||||||
|  |             throw std::runtime_error("can't cast an item without an enchantment"); | ||||||
|  | 
 | ||||||
|  |         mSourceName = item.getClass().getName(item); | ||||||
|  |         mId = item.getCellRef().mRefID; | ||||||
|  | 
 | ||||||
|  |         const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName); | ||||||
|  | 
 | ||||||
|  |         mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce); | ||||||
|  | 
 | ||||||
|  |         if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) | ||||||
|  |         { | ||||||
|  |             // Check if there's enough charge left
 | ||||||
|  |             const float enchantCost = enchantment->mData.mCost; | ||||||
|  |             MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster); | ||||||
|  |             int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); | ||||||
|  |             const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); | ||||||
|  | 
 | ||||||
|  |             if (item.getCellRef().mEnchantmentCharge == -1) | ||||||
|  |                 item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; | ||||||
|  | 
 | ||||||
|  |             if (mCaster.getRefData().getHandle() == "player" && item.getCellRef().mEnchantmentCharge < castCost) | ||||||
|  |             { | ||||||
|  |                 // TODO: Should there be a sound here?
 | ||||||
|  |                 MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Reduce charge
 | ||||||
|  |             item.getCellRef().mEnchantmentCharge -= castCost; | ||||||
|  |         } | ||||||
|  |         if (enchantment->mData.mType == ESM::Enchantment::CastOnce) | ||||||
|  |             item.getContainerStore()->remove(item, 1, mCaster); | ||||||
|  |         else | ||||||
|  |         { | ||||||
|  |             if (mCaster.getRefData().getHandle() == "player") | ||||||
|  |                 MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); | ||||||
|  | 
 | ||||||
|  |         if (!mTarget.isEmpty()) | ||||||
|  |         { | ||||||
|  |             if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) | ||||||
|  |                 inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         MWBase::Environment::get().getWorld()->launchProjectile(mId, enchantment->mEffects, mCaster, mSourceName); | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool CastSpell::cast(const ESM::Potion* potion) | ||||||
|  |     { | ||||||
|  |         mSourceName = potion->mName; | ||||||
|  |         mId = potion->mId; | ||||||
|  |         mStack = true; | ||||||
|  | 
 | ||||||
|  |         inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self); | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool CastSpell::cast(const ESM::Spell* spell) | ||||||
|  |     { | ||||||
|  |         mSourceName = spell->mName; | ||||||
|  |         mId = spell->mId; | ||||||
|  |         mStack = false; | ||||||
|  | 
 | ||||||
|  |         const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); | ||||||
|  | 
 | ||||||
|  |         int school = 0; | ||||||
|  | 
 | ||||||
|  |         if (mCaster.getClass().isActor()) | ||||||
|  |         { | ||||||
|  |             school = getSpellSchool(spell, mCaster); | ||||||
|  | 
 | ||||||
|  |             CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster); | ||||||
|  | 
 | ||||||
|  |             // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss)
 | ||||||
|  |             static const float fFatigueSpellBase = store.get<ESM::GameSetting>().find("fFatigueSpellBase")->getFloat(); | ||||||
|  |             static const float fFatigueSpellMult = store.get<ESM::GameSetting>().find("fFatigueSpellMult")->getFloat(); | ||||||
|  |             DynamicStat<float> fatigue = stats.getFatigue(); | ||||||
|  |             const float normalizedEncumbrance = mCaster.getClass().getEncumbrance(mCaster) / mCaster.getClass().getCapacity(mCaster); | ||||||
|  |             float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); | ||||||
|  |             fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss)); | ||||||
|  |             stats.setFatigue(fatigue); | ||||||
|  | 
 | ||||||
|  |             // Check mana
 | ||||||
|  |             bool fail = false; | ||||||
|  |             DynamicStat<float> magicka = stats.getMagicka(); | ||||||
|  |             if (magicka.getCurrent() < spell->mData.mCost) | ||||||
|  |             { | ||||||
|  |                 MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); | ||||||
|  |                 fail = true; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Reduce mana
 | ||||||
|  |             if (!fail) | ||||||
|  |             { | ||||||
|  |                 magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); | ||||||
|  |                 stats.setMagicka(magicka); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // If this is a power, check if it was already used in last 24h
 | ||||||
|  |             if (!fail && spell->mData.mType & ESM::Spell::ST_Power) | ||||||
|  |             { | ||||||
|  |                 if (stats.canUsePower(spell->mId)) | ||||||
|  |                     stats.usePower(spell->mId); | ||||||
|  |                 else | ||||||
|  |                 { | ||||||
|  |                     MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}"); | ||||||
|  |                     fail = true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Check success
 | ||||||
|  |             int successChance = getSpellSuccessChance(spell, mCaster); | ||||||
|  |             int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
 | ||||||
|  |             if (!fail && roll >= successChance) | ||||||
|  |             { | ||||||
|  |                 MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); | ||||||
|  |                 fail = true; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (fail) | ||||||
|  |             { | ||||||
|  |                 // Failure sound
 | ||||||
|  |                 static const std::string schools[] = { | ||||||
|  |                     "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" | ||||||
|  |                 }; | ||||||
|  | 
 | ||||||
|  |                 MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); | ||||||
|  |                 sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (mCaster.getRefData().getHandle() == "player" && spell->mData.mType == ESM::Spell::ST_Spell) | ||||||
|  |             mCaster.getClass().skillUsageSucceeded(mCaster, | ||||||
|  |                 spellSchoolToSkill(school), 0); | ||||||
|  | 
 | ||||||
|  |         inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self); | ||||||
|  | 
 | ||||||
|  |         if (!mTarget.isEmpty()) | ||||||
|  |         { | ||||||
|  |             if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead()) | ||||||
|  |             { | ||||||
|  |                 inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         MWBase::Environment::get().getWorld()->launchProjectile(mId, spell->mEffects, mCaster, mSourceName); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     bool CastSpell::cast (const ESM::Ingredient* ingredient) | ||||||
|  |     { | ||||||
|  |         mId = ingredient->mId; | ||||||
|  |         mStack = true; | ||||||
|  |         mSourceName = ingredient->mName; | ||||||
|  | 
 | ||||||
|  |         ESM::ENAMstruct effect; | ||||||
|  |         effect.mEffectID = ingredient->mData.mEffectID[0]; | ||||||
|  |         effect.mSkill = ingredient->mData.mSkills[0]; | ||||||
|  |         effect.mAttribute = ingredient->mData.mAttributes[0]; | ||||||
|  |         effect.mRange = ESM::RT_Self; | ||||||
|  |         effect.mArea = 0; | ||||||
|  | 
 | ||||||
|  |         const ESM::MagicEffect *magicEffect = | ||||||
|  |             MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find ( | ||||||
|  |             effect.mEffectID); | ||||||
|  | 
 | ||||||
|  |         const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster); | ||||||
|  |         const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster); | ||||||
|  | 
 | ||||||
|  |         float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + | ||||||
|  |                     0.2 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified() | ||||||
|  |                     + 0.1 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified()) | ||||||
|  |                     * creatureStats.getFatigueTerm(); | ||||||
|  | 
 | ||||||
|  |         int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
 | ||||||
|  |         if (roll > x) | ||||||
|  |         { | ||||||
|  |             // "X has no effect on you"
 | ||||||
|  |             std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("#{sNotifyMessage50}")->getString(); | ||||||
|  |             message = boost::str(boost::format(message) % ingredient->mName); | ||||||
|  | 
 | ||||||
|  |             MWBase::Environment::get().getWindowManager()->messageBox(message); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         float magnitude = 0; | ||||||
|  |         float y = roll / std::min(x, 100.f); | ||||||
|  |         y *= 0.25 * x; | ||||||
|  |         if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) | ||||||
|  |             effect.mDuration = int(y); | ||||||
|  |         else | ||||||
|  |             effect.mDuration = 1; | ||||||
|  |         if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) | ||||||
|  |         { | ||||||
|  |             if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) | ||||||
|  |                 magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost)); | ||||||
|  |             else | ||||||
|  |                 magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost)); | ||||||
|  |             magnitude = std::max(1.f, magnitude); | ||||||
|  |         } | ||||||
|  |         else | ||||||
|  |             magnitude = 1; | ||||||
|  | 
 | ||||||
|  |         effect.mMagnMax = magnitude; | ||||||
|  |         effect.mMagnMin = magnitude; | ||||||
|  | 
 | ||||||
|  |         ESM::EffectList effects; | ||||||
|  |         effects.mList.push_back(effect); | ||||||
|  | 
 | ||||||
|  |         inflict(mCaster, mCaster, effects, ESM::RT_Self); | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -44,12 +44,6 @@ namespace MWMechanics | ||||||
|         if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude) |         if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude) | ||||||
|             return 0; |             return 0; | ||||||
| 
 | 
 | ||||||
|         if (spell->mData.mType != ESM::Spell::ST_Spell) |  | ||||||
|             return 100; |  | ||||||
| 
 |  | ||||||
|         if (spell->mData.mFlags & ESM::Spell::F_Always) |  | ||||||
|             return 100; |  | ||||||
| 
 |  | ||||||
|         float y = FLT_MAX; |         float y = FLT_MAX; | ||||||
|         float lowestSkill = 0; |         float lowestSkill = 0; | ||||||
| 
 | 
 | ||||||
|  | @ -79,8 +73,14 @@ namespace MWMechanics | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if (spell->mData.mType != ESM::Spell::ST_Spell) | ||||||
|  |             return 100; | ||||||
|  | 
 | ||||||
|  |         if (spell->mData.mFlags & ESM::Spell::F_Always) | ||||||
|  |             return 100; | ||||||
| 
 | 
 | ||||||
|         int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude; |         int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude; | ||||||
|  | 
 | ||||||
|         int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); |         int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); | ||||||
|         int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); |         int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); | ||||||
| 
 | 
 | ||||||
|  | @ -98,7 +98,6 @@ namespace MWMechanics | ||||||
|         return getSpellSuccessChance(spell, actor, effectiveSchool); |         return getSpellSuccessChance(spell, actor, effectiveSchool); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// @note this only works for ST_Spell
 |  | ||||||
|     inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) |     inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor) | ||||||
|     { |     { | ||||||
|         int school = 0; |         int school = 0; | ||||||
|  | @ -106,7 +105,6 @@ namespace MWMechanics | ||||||
|         return school; |         return school; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// @note this only works for ST_Spell
 |  | ||||||
|     inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) |     inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor) | ||||||
|     { |     { | ||||||
|         int school = 0; |         int school = 0; | ||||||
|  | @ -180,6 +178,34 @@ namespace MWMechanics | ||||||
|             return -(resistance-100) / 100.f; |             return -(resistance-100) / 100.f; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     class CastSpell | ||||||
|  |     { | ||||||
|  |     private: | ||||||
|  |         MWWorld::Ptr mCaster; | ||||||
|  |         MWWorld::Ptr mTarget; | ||||||
|  | 
 | ||||||
|  |         bool mStack; | ||||||
|  |         std::string mId; // ID of spell, potion, item etc
 | ||||||
|  |         std::string mSourceName; // Display name for spell, potion, etc
 | ||||||
|  | 
 | ||||||
|  |     public: | ||||||
|  |         CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); | ||||||
|  | 
 | ||||||
|  |         bool cast (const ESM::Spell* spell); | ||||||
|  |         bool cast (const MWWorld::Ptr& item); | ||||||
|  |         bool cast (const ESM::Ingredient* ingredient); | ||||||
|  |         bool cast (const ESM::Potion* potion); | ||||||
|  | 
 | ||||||
|  |         /// @note Auto detects if spell, ingredient or potion
 | ||||||
|  |         bool cast (const std::string& id); | ||||||
|  | 
 | ||||||
|  |         void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, | ||||||
|  |                       const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false); | ||||||
|  | 
 | ||||||
|  |         void applyInstantEffect (const MWWorld::Ptr& target, short effectId, float magnitude); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -118,6 +118,62 @@ namespace MWMechanics | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     void Spells::purgeCommonDisease() | ||||||
|  |     { | ||||||
|  |         for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) | ||||||
|  |         { | ||||||
|  |             const ESM::Spell *spell = | ||||||
|  |                 MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first); | ||||||
|  | 
 | ||||||
|  |             if (spell->mData.mType & ESM::Spell::ST_Disease) | ||||||
|  |                 mSpells.erase(iter++); | ||||||
|  |             else | ||||||
|  |                 iter++; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void Spells::purgeBlightDisease() | ||||||
|  |     { | ||||||
|  |         for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) | ||||||
|  |         { | ||||||
|  |             const ESM::Spell *spell = | ||||||
|  |                 MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first); | ||||||
|  | 
 | ||||||
|  |             if (spell->mData.mType & ESM::Spell::ST_Blight) | ||||||
|  |                 mSpells.erase(iter++); | ||||||
|  |             else | ||||||
|  |                 iter++; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void Spells::purgeCorprusDisease() | ||||||
|  |     { | ||||||
|  |         for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) | ||||||
|  |         { | ||||||
|  |             const ESM::Spell *spell = | ||||||
|  |                 MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first); | ||||||
|  | 
 | ||||||
|  |             if (Misc::StringUtils::ciEqual(spell->mId, "corprus")) | ||||||
|  |                 mSpells.erase(iter++); | ||||||
|  |             else | ||||||
|  |                 iter++; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void Spells::purgeCurses() | ||||||
|  |     { | ||||||
|  |         for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) | ||||||
|  |         { | ||||||
|  |             const ESM::Spell *spell = | ||||||
|  |                 MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first); | ||||||
|  | 
 | ||||||
|  |             if (spell->mData.mType == ESM::Spell::ST_Curse) | ||||||
|  |                 mSpells.erase(iter++); | ||||||
|  |             else | ||||||
|  |                 iter++; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     void Spells::visitEffectSources(EffectSourceVisitor &visitor) const |     void Spells::visitEffectSources(EffectSourceVisitor &visitor) const | ||||||
|     { |     { | ||||||
|         for (TIterator it = begin(); it != end(); ++it) |         for (TIterator it = begin(); it != end(); ++it) | ||||||
|  | @ -136,7 +192,7 @@ namespace MWMechanics | ||||||
|                  effectIt != list.mList.end(); ++effectIt, ++i) |                  effectIt != list.mList.end(); ++effectIt, ++i) | ||||||
|             { |             { | ||||||
|                 float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i]; |                 float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i]; | ||||||
|                 visitor.visit(*effectIt, spell->mName, magnitude); |                 visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, magnitude); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -35,6 +35,11 @@ namespace MWMechanics | ||||||
| 
 | 
 | ||||||
|         public: |         public: | ||||||
| 
 | 
 | ||||||
|  |             void purgeCommonDisease(); | ||||||
|  |             void purgeBlightDisease(); | ||||||
|  |             void purgeCorprusDisease(); | ||||||
|  |             void purgeCurses(); | ||||||
|  | 
 | ||||||
|             TIterator begin() const; |             TIterator begin() const; | ||||||
| 
 | 
 | ||||||
|             TIterator end() const; |             TIterator end() const; | ||||||
|  |  | ||||||
|  | @ -994,16 +994,27 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj) | ||||||
|     mSkelBase->detachObjectFromBone(obj); |     mSkelBase->detachObjectFromBone(obj); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename) | void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture) | ||||||
| { | { | ||||||
|     // Early out if we already have this effect
 |     // Early out if we already have this effect
 | ||||||
|     for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it) |     for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it) | ||||||
|         if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) |         if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename) | ||||||
|             return; |             return; | ||||||
| 
 | 
 | ||||||
|  |     // fix texture extension to .dds
 | ||||||
|  |     if (texture.size() > 4) | ||||||
|  |     { | ||||||
|  |         texture[texture.size()-3] = 'd'; | ||||||
|  |         texture[texture.size()-2] = 'd'; | ||||||
|  |         texture[texture.size()-1] = 's'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     EffectParams params; |     EffectParams params; | ||||||
|     params.mModelName = model; |     params.mModelName = model; | ||||||
|  |     if (bonename.empty()) | ||||||
|         params.mObjects = NifOgre::Loader::createObjects(mInsert, model); |         params.mObjects = NifOgre::Loader::createObjects(mInsert, model); | ||||||
|  |     else | ||||||
|  |         params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); | ||||||
|     params.mLoop = loop; |     params.mLoop = loop; | ||||||
|     params.mEffectId = effectId; |     params.mEffectId = effectId; | ||||||
|     params.mBoneName = bonename; |     params.mBoneName = bonename; | ||||||
|  | @ -1013,6 +1024,35 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con | ||||||
|         if(params.mObjects.mControllers[i].getSource().isNull()) |         if(params.mObjects.mControllers[i].getSource().isNull()) | ||||||
|             params.mObjects.mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationValue> (new EffectAnimationValue())); |             params.mObjects.mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationValue> (new EffectAnimationValue())); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     if (!texture.empty()) | ||||||
|  |     { | ||||||
|  |         for(size_t i = 0;i < params.mObjects.mParticles.size(); ++i) | ||||||
|  |         { | ||||||
|  |             Ogre::ParticleSystem* partSys = params.mObjects.mParticles[i]; | ||||||
|  |             Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName()); | ||||||
|  |             static int count = 0; | ||||||
|  |             Ogre::String materialName = "openmw/" + Ogre::StringConverter::toString(count++); | ||||||
|  |             // TODO: destroy when effect is removed
 | ||||||
|  |             Ogre::MaterialPtr newMat = mat->clone(materialName); | ||||||
|  |             partSys->setMaterialName(materialName); | ||||||
|  | 
 | ||||||
|  |             for (int t=0; t<newMat->getNumTechniques(); ++t) | ||||||
|  |             { | ||||||
|  |                 Ogre::Technique* tech = newMat->getTechnique(t); | ||||||
|  |                 for (int p=0; p<tech->getNumPasses(); ++p) | ||||||
|  |                 { | ||||||
|  |                     Ogre::Pass* pass = tech->getPass(p); | ||||||
|  |                     for (int tex=0; tex<pass->getNumTextureUnitStates(); ++tex) | ||||||
|  |                     { | ||||||
|  |                         Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex); | ||||||
|  |                         tus->setTextureName("textures\\" + texture); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     mEffects.push_back(params); |     mEffects.push_back(params); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -206,9 +206,10 @@ public: | ||||||
|      * @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true, |      * @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. |      *              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 |      * @param bonename Bone to attach to, or empty string to use the scene node instead | ||||||
|  |      * @param texture override the texture specified in the model's materials | ||||||
|      * @note Will not add an effect twice. |      * @note Will not add an effect twice. | ||||||
|      */ |      */ | ||||||
|     void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = ""); |     void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = ""); | ||||||
|     void removeEffect (int effectId); |     void removeEffect (int effectId); | ||||||
|     void getLoopingEffects (std::vector<int>& out); |     void getLoopingEffects (std::vector<int>& out); | ||||||
| private: | private: | ||||||
|  |  | ||||||
|  | @ -281,3 +281,11 @@ void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) | ||||||
|     node->addChild(cur.getRefData().getBaseNode()); |     node->addChild(cur.getRefData().getBaseNode()); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | ObjectAnimation* Objects::getAnimation(const MWWorld::Ptr &ptr) | ||||||
|  | { | ||||||
|  |     PtrAnimationMap::const_iterator iter = mObjects.find(ptr); | ||||||
|  |     if(iter != mObjects.end()) | ||||||
|  |         return iter->second; | ||||||
|  |     return NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | @ -41,6 +41,8 @@ public: | ||||||
|     ~Objects(){} |     ~Objects(){} | ||||||
|     void insertModel(const MWWorld::Ptr& ptr, const std::string &model); |     void insertModel(const MWWorld::Ptr& ptr, const std::string &model); | ||||||
| 
 | 
 | ||||||
|  |     ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); | ||||||
|  | 
 | ||||||
|     void enableLights(); |     void enableLights(); | ||||||
|     void disableLights(); |     void disableLights(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -975,8 +975,13 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend | ||||||
| Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) | Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr) | ||||||
| { | { | ||||||
|     Animation *anim = mActors.getAnimation(ptr); |     Animation *anim = mActors.getAnimation(ptr); | ||||||
|  | 
 | ||||||
|     if(!anim && ptr.getRefData().getHandle() == "player") |     if(!anim && ptr.getRefData().getHandle() == "player") | ||||||
|         anim = mPlayerAnimation; |         anim = mPlayerAnimation; | ||||||
|  | 
 | ||||||
|  |     if (!anim) | ||||||
|  |         anim = mObjects.getAnimation(ptr); | ||||||
|  | 
 | ||||||
|     return anim; |     return anim; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,17 +3,11 @@ | ||||||
| 
 | 
 | ||||||
| #include <cstdlib> | #include <cstdlib> | ||||||
| 
 | 
 | ||||||
| #include <components/esm/loadskil.hpp> |  | ||||||
| 
 |  | ||||||
| #include "../mwbase/environment.hpp" | #include "../mwbase/environment.hpp" | ||||||
| #include "../mwbase/world.hpp" | #include "../mwbase/world.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../mwmechanics/creaturestats.hpp" |  | ||||||
| #include "../mwmechanics/npcstats.hpp" |  | ||||||
| 
 |  | ||||||
| #include "../mwworld/containerstore.hpp" | #include "../mwworld/containerstore.hpp" | ||||||
| 
 | 
 | ||||||
| #include "esmstore.hpp" |  | ||||||
| #include "class.hpp" | #include "class.hpp" | ||||||
| 
 | 
 | ||||||
| namespace MWWorld | namespace MWWorld | ||||||
|  | @ -23,28 +17,12 @@ namespace MWWorld | ||||||
|         // remove used item (assume the item is present in inventory)
 |         // remove used item (assume the item is present in inventory)
 | ||||||
|         getTarget().getContainerStore()->remove(getTarget(), 1, actor); |         getTarget().getContainerStore()->remove(getTarget(), 1, actor); | ||||||
| 
 | 
 | ||||||
|         // check for success
 |  | ||||||
|         const MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); |  | ||||||
|         MWMechanics::NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); |  | ||||||
|      |  | ||||||
|         float x = |  | ||||||
|             (npcStats.getSkill (ESM::Skill::Alchemy).getModified() + |  | ||||||
|             0.2 * creatureStats.getAttribute (1).getModified() |  | ||||||
|             + 0.1 * creatureStats.getAttribute (7).getModified()) |  | ||||||
|             * creatureStats.getFatigueTerm(); |  | ||||||
| 
 |  | ||||||
|         if (x>=100*static_cast<float> (std::rand()) / RAND_MAX) |  | ||||||
|         { |  | ||||||
|         // apply to actor
 |         // apply to actor
 | ||||||
|         std::string id = Class::get (getTarget()).getId (getTarget()); |         std::string id = Class::get (getTarget()).getId (getTarget()); | ||||||
|              |              | ||||||
|             Class::get (actor).apply (actor, id, actor); |         if (Class::get (actor).apply (actor, id, actor)) | ||||||
|             // we ignore the result here. Skill increases no matter if the ingredient did something or not.      
 |  | ||||||
|          |  | ||||||
|             // increase skill
 |  | ||||||
|             Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); |             Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1); | ||||||
|     }     |     }     | ||||||
|     }     |  | ||||||
| 
 | 
 | ||||||
|     ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {} |     ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,26 +1,14 @@ | ||||||
| #include "actiontrap.hpp" | #include "actiontrap.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../mwworld/class.hpp" | #include "../mwmechanics/spellcasting.hpp" | ||||||
| 
 |  | ||||||
| #include "../mwmechanics/activespells.hpp" |  | ||||||
| #include "../mwmechanics/creaturestats.hpp" |  | ||||||
| 
 |  | ||||||
| #include "../mwbase/world.hpp" |  | ||||||
| #include "../mwbase/environment.hpp" |  | ||||||
| 
 | 
 | ||||||
| namespace MWWorld | namespace MWWorld | ||||||
| { | { | ||||||
| 
 | 
 | ||||||
|     void ActionTrap::executeImp(const Ptr &actor) |     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
 |         MWMechanics::CastSpell cast(mTrapSource, actor); | ||||||
|         // make it lock itself when activated for example.
 |         cast.cast(mSpellId); | ||||||
| 
 |  | ||||||
|         actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, actor, ESM::RT_Touch); |  | ||||||
| 
 |  | ||||||
|         const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(mSpellId); |  | ||||||
| 
 |  | ||||||
|         MWBase::Environment::get().getWorld()->launchProjectile(mSpellId, spell->mEffects, mTrapSource, spell->mName); |  | ||||||
| 
 | 
 | ||||||
|         mTrapSource.getCellRef().mTrap = ""; |         mTrapSource.getCellRef().mTrap = ""; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ | ||||||
| #include "../mwbase/environment.hpp" | #include "../mwbase/environment.hpp" | ||||||
| #include "../mwbase/world.hpp" | #include "../mwbase/world.hpp" | ||||||
| #include "../mwbase/windowmanager.hpp" | #include "../mwbase/windowmanager.hpp" | ||||||
|  | #include "../mwbase/mechanicsmanager.hpp" | ||||||
| 
 | 
 | ||||||
| #include "../mwmechanics/npcstats.hpp" | #include "../mwmechanics/npcstats.hpp" | ||||||
| #include "../mwmechanics/spellcasting.hpp" | #include "../mwmechanics/spellcasting.hpp" | ||||||
|  | @ -339,6 +340,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) | ||||||
|                 if (params[i].mMultiplier == 0) |                 if (params[i].mMultiplier == 0) | ||||||
|                     continue; |                     continue; | ||||||
| 
 | 
 | ||||||
|  |                 float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; | ||||||
|  |                 magnitude *= params[i].mMultiplier; | ||||||
|  | 
 | ||||||
|                 if (!existed) |                 if (!existed) | ||||||
|                 { |                 { | ||||||
|                     // During first auto equip, we don't play any sounds.
 |                     // During first auto equip, we don't play any sounds.
 | ||||||
|  | @ -346,10 +350,13 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) | ||||||
|                     // the items should appear as if they'd always been equipped.
 |                     // the items should appear as if they'd always been equipped.
 | ||||||
|                     mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip, |                     mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip, | ||||||
|                                                         !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); |                                                         !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); | ||||||
|  | 
 | ||||||
|  |                     // Apply instant effects
 | ||||||
|  |                     MWMechanics::CastSpell cast(actor, actor); | ||||||
|  |                     if (magnitude) | ||||||
|  |                         cast.applyInstantEffect(actor, effectIt->mEffectID, magnitude); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; |  | ||||||
|                 magnitude *= params[i].mMultiplier; |  | ||||||
|                 if (magnitude) |                 if (magnitude) | ||||||
|                     mMagicEffects.add (*effectIt, magnitude); |                     mMagicEffects.add (*effectIt, magnitude); | ||||||
|             } |             } | ||||||
|  | @ -376,6 +383,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) | ||||||
|             ++it; |             ++it; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping
 | ||||||
|  |     MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor); | ||||||
|  | 
 | ||||||
|     mFirstAutoEquip = false; |     mFirstAutoEquip = false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -442,6 +452,13 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor | ||||||
|             autoEquip(actor); |             autoEquip(actor); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() | ||||||
|  |             && *mSelectedEnchantItem == item && actor.getRefData().getHandle() == "player") | ||||||
|  |     { | ||||||
|  |         mSelectedEnchantItem = end(); | ||||||
|  |         MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return retCount; |     return retCount; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -554,7 +571,7 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito | ||||||
|             const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; |             const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; | ||||||
|             float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; |             float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; | ||||||
|             magnitude *= params.mMultiplier; |             magnitude *= params.mMultiplier; | ||||||
|             visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude); |             visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), magnitude); | ||||||
| 
 | 
 | ||||||
|             ++i; |             ++i; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -2029,156 +2029,28 @@ namespace MWWorld | ||||||
|     void World::castSpell(const Ptr &actor) |     void World::castSpell(const Ptr &actor) | ||||||
|     { |     { | ||||||
|         MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); |         MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); | ||||||
|  |         InventoryStore& inv = actor.getClass().getInventoryStore(actor); | ||||||
|  | 
 | ||||||
|  |         // Unset casting flag, otherwise pressing the mouse button down would continue casting every frame if using an enchantment
 | ||||||
|  |         // (which casts instantly without an animation)
 | ||||||
|         stats.setAttackingOrSpell(false); |         stats.setAttackingOrSpell(false); | ||||||
| 
 | 
 | ||||||
|         ESM::EffectList effects; |         MWWorld::Ptr target = getFacedObject(); | ||||||
| 
 | 
 | ||||||
|         std::string selectedSpell = stats.getSpells().getSelectedSpell(); |         std::string selectedSpell = stats.getSpells().getSelectedSpell(); | ||||||
|         std::string sourceName; | 
 | ||||||
|  |         MWMechanics::CastSpell cast(actor, target); | ||||||
|  | 
 | ||||||
|         if (!selectedSpell.empty()) |         if (!selectedSpell.empty()) | ||||||
|         { |         { | ||||||
|             const ESM::Spell* spell = getStore().get<ESM::Spell>().search(selectedSpell); |             const ESM::Spell* spell = getStore().get<ESM::Spell>().search(selectedSpell); | ||||||
| 
 | 
 | ||||||
|             // Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss)
 |             cast.cast(spell); | ||||||
|             static const float fFatigueSpellBase = getStore().get<ESM::GameSetting>().find("fFatigueSpellBase")->getFloat(); |         } | ||||||
|             static const float fFatigueSpellMult = getStore().get<ESM::GameSetting>().find("fFatigueSpellMult")->getFloat(); |         else if (inv.getSelectedEnchantItem() != inv.end()) | ||||||
|             MWMechanics::DynamicStat<float> fatigue = stats.getFatigue(); |  | ||||||
|             const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor); |  | ||||||
|             float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); |  | ||||||
|             fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss)); |  | ||||||
|             stats.setFatigue(fatigue); |  | ||||||
| 
 |  | ||||||
|             // Check mana
 |  | ||||||
|             bool fail = false; |  | ||||||
|             MWMechanics::DynamicStat<float> magicka = stats.getMagicka(); |  | ||||||
|             if (magicka.getCurrent() < spell->mData.mCost) |  | ||||||
|         { |         { | ||||||
|                 MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}"); |             cast.cast(*inv.getSelectedEnchantItem()); | ||||||
|                 fail = true; |  | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|             // Reduce mana
 |  | ||||||
|             if (!fail) |  | ||||||
|             { |  | ||||||
|                 magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); |  | ||||||
|                 stats.setMagicka(magicka); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // If this is a power, check if it was already used in last 24h
 |  | ||||||
|             if (!fail && spell->mData.mType & ESM::Spell::ST_Power) |  | ||||||
|             { |  | ||||||
|                 if (stats.canUsePower(selectedSpell)) |  | ||||||
|                     stats.usePower(selectedSpell); |  | ||||||
|                 else |  | ||||||
|                 { |  | ||||||
|                     MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}"); |  | ||||||
|                     fail = true; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             // Check success
 |  | ||||||
|             int successChance = MWMechanics::getSpellSuccessChance(selectedSpell, actor); |  | ||||||
|             int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
 |  | ||||||
|             if (!fail && roll >= successChance) |  | ||||||
|             { |  | ||||||
|                 MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}"); |  | ||||||
|                 fail = true; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (fail) |  | ||||||
|             { |  | ||||||
|                 // Failure sound
 |  | ||||||
|                 for (std::vector<ESM::ENAMstruct>::const_iterator iter (spell->mEffects.mList.begin()); |  | ||||||
|                     iter!=spell->mEffects.mList.end(); ++iter) |  | ||||||
|                 { |  | ||||||
|                     const ESM::MagicEffect *magicEffect = getStore().get<ESM::MagicEffect>().find ( |  | ||||||
|                         iter->mEffectID); |  | ||||||
| 
 |  | ||||||
|                     static const std::string schools[] = { |  | ||||||
|                         "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" |  | ||||||
|                     }; |  | ||||||
| 
 |  | ||||||
|                     MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); |  | ||||||
|                     sndMgr->playSound3D(actor, "Spell Failure " + schools[magicEffect->mData.mSchool], 1.0f, 1.0f); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (actor == getPlayer().getPlayer() && spell->mData.mType == ESM::Spell::ST_Spell) |  | ||||||
|                 actor.getClass().skillUsageSucceeded(actor, |  | ||||||
|                     MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0); |  | ||||||
| 
 |  | ||||||
|             effects = spell->mEffects; |  | ||||||
|         } |  | ||||||
|         InventoryStore& inv = actor.getClass().getInventoryStore(actor); |  | ||||||
|         if (selectedSpell.empty() && inv.getSelectedEnchantItem() != inv.end()) |  | ||||||
|         { |  | ||||||
|             MWWorld::Ptr item = *inv.getSelectedEnchantItem(); |  | ||||||
|             selectedSpell = item.getClass().getEnchantment(item); |  | ||||||
|             const ESM::Enchantment* enchantment = getStore().get<ESM::Enchantment>().search (selectedSpell); |  | ||||||
| 
 |  | ||||||
|             if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) |  | ||||||
|             { |  | ||||||
|                 // Check if there's enough charge left
 |  | ||||||
|                 const float enchantCost = enchantment->mData.mCost; |  | ||||||
|                 MWMechanics::NpcStats &stats = MWWorld::Class::get(actor).getNpcStats(actor); |  | ||||||
|                 int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); |  | ||||||
|                 const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); |  | ||||||
| 
 |  | ||||||
|                 if (item.getCellRef().mEnchantmentCharge == -1) |  | ||||||
|                     item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; |  | ||||||
| 
 |  | ||||||
|                 if (item.getCellRef().mEnchantmentCharge < castCost) |  | ||||||
|                 { |  | ||||||
|                     MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 // Reduce charge
 |  | ||||||
|                 item.getCellRef().mEnchantmentCharge -= castCost; |  | ||||||
|             } |  | ||||||
|             if (enchantment->mData.mType == ESM::Enchantment::CastOnce) |  | ||||||
|             { |  | ||||||
|                 if (!item.getContainerStore()->remove(item, 1, actor)) |  | ||||||
|                 { |  | ||||||
|                     // Item was used up
 |  | ||||||
|                     MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); |  | ||||||
|                     inv.setSelectedEnchantItem(inv.end()); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|                 MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
 |  | ||||||
| 
 |  | ||||||
|             sourceName = item.getClass().getName(item); |  | ||||||
| 
 |  | ||||||
|             effects = enchantment->mEffects; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // Now apply the spell!
 |  | ||||||
| 
 |  | ||||||
|         // Apply Self portion
 |  | ||||||
|         actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, actor, ESM::RT_Self, sourceName); |  | ||||||
| 
 |  | ||||||
|         // Apply Touch portion
 |  | ||||||
|         // TODO: Distance is probably incorrect, and should it be hardcoded?
 |  | ||||||
|         std::pair<MWWorld::Ptr, Ogre::Vector3> contact = getHitContact(actor, 100); |  | ||||||
|         if (!contact.first.isEmpty()) |  | ||||||
|         { |  | ||||||
|             if (contact.first.getClass().isActor()) |  | ||||||
|             { |  | ||||||
|                 if (!contact.first.getClass().getCreatureStats(contact.first).isDead()) |  | ||||||
|                     contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|             { |  | ||||||
|                 // We hit a non-actor, e.g. a door. Only instant effects are relevant.
 |  | ||||||
|                 //  inflictSpellOnNonActor(contact.first, selectedSpell, ESM::RT_Touch);
 |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         launchProjectile(selectedSpell, effects, actor, sourceName); |  | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     void World::launchProjectile (const std::string& id, const ESM::EffectList& effects, |     void World::launchProjectile (const std::string& id, const ESM::EffectList& effects, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue