diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index da51f7e8f..a18e88f5f 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -21,6 +21,20 @@ namespace MWGui { + void EffectSourceVisitor::visit (const ESM::ENAMstruct& enam, + const std::string& sourceName, float magnitude, float remainingTime) + { + MagicEffectInfo newEffectSource; + newEffectSource.mKey = MWMechanics::EffectKey(enam); + newEffectSource.mMagnitude = magnitude; + newEffectSource.mPermanent = mIsPermanent; + newEffectSource.mRemainingTime = remainingTime; + newEffectSource.mSource = sourceName; + + mEffectSources[enam.mEffectID].push_back(newEffectSource); + } + + void SpellIcons::updateWidgets(MyGUI::Widget *parent, bool adjustSize) { // TODO: Tracking add/remove/expire would be better than force updating every frame @@ -28,125 +42,20 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayer().getPlayer(); const MWMechanics::CreatureStats& stats = MWWorld::Class::get(player).getCreatureStats(player); - std::map > effects; - // add permanent item enchantments + EffectSourceVisitor visitor; + + // permanent item enchantments & permanent spells + visitor.mIsPermanent = true; MWWorld::InventoryStore& store = MWWorld::Class::get(player).getInventoryStore(player); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - MWWorld::ContainerStoreIterator it = store.getSlot(slot); - if (it == store.end()) - continue; - std::string enchantment = MWWorld::Class::get(*it).getEnchantment(*it); - if (enchantment.empty()) - continue; - const ESM::Enchantment* enchant = MWBase::Environment::get().getWorld()->getStore().get().find(enchantment); - if (enchant->mData.mType != ESM::Enchantment::ConstantEffect) - continue; + store.visitEffectSources(visitor); + stats.getSpells().visitEffectSources(visitor); - const ESM::EffectList& list = enchant->mEffects; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt) - { - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); + // now add lasting effects + visitor.mIsPermanent = false; + stats.getActiveSpells().visitEffectSources(visitor); - MagicEffectInfo effectInfo; - effectInfo.mSource = MWWorld::Class::get(*it).getName(*it); - effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effectInfo.mKey.mArg = effectIt->mSkill; - else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effectInfo.mKey.mArg = effectIt->mAttribute; - // just using the min magnitude here, permanent enchantments with a random magnitude just wouldn't make any sense - effectInfo.mMagnitude = effectIt->mMagnMin; - effectInfo.mPermanent = true; - effects[effectIt->mEffectID].push_back (effectInfo); - } - } - - // add permanent spells - const MWMechanics::Spells& spells = stats.getSpells(); - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); - - // these are the spell types that are permanently in effect - if (!(spell->mData.mType == ESM::Spell::ST_Ability) - && !(spell->mData.mType == ESM::Spell::ST_Disease) - && !(spell->mData.mType == ESM::Spell::ST_Curse) - && !(spell->mData.mType == ESM::Spell::ST_Blight)) - continue; - const ESM::EffectList& list = spell->mEffects; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt) - { - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); - MagicEffectInfo effectInfo; - effectInfo.mSource = getSpellDisplayName (it->first); - effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effectInfo.mKey.mArg = effectIt->mSkill; - else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effectInfo.mKey.mArg = effectIt->mAttribute; - // just using the min magnitude here, permanent spells with a random magnitude just wouldn't make any sense - effectInfo.mMagnitude = effectIt->mMagnMin; - effectInfo.mPermanent = true; - - effects[effectIt->mEffectID].push_back (effectInfo); - } - } - - // add lasting effect spells/potions etc - - // TODO: Move this to ActiveSpells - const MWMechanics::ActiveSpells::TContainer& activeSpells = stats.getActiveSpells().getActiveSpells(); - for (MWMechanics::ActiveSpells::TContainer::const_iterator it = activeSpells.begin(); - it != activeSpells.end(); ++it) - { - const ESM::EffectList& list = getSpellEffectList(it->first); - - float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); - - int i=0; - for (std::vector::const_iterator effectIt = list.mList.begin(); - effectIt != list.mList.end(); ++effectIt, ++i) - { - if (effectIt->mRange != it->second.mRange) - continue; - - float randomFactor = it->second.mRandom[i]; - - const ESM::MagicEffect* magicEffect = - MWBase::Environment::get().getWorld ()->getStore ().get().find(effectIt->mEffectID); - - MagicEffectInfo effectInfo; - if (!it->second.mName.empty()) - effectInfo.mSource = it->second.mName; - else - effectInfo.mSource = getSpellDisplayName(it->first); - effectInfo.mKey = MWMechanics::EffectKey (effectIt->mEffectID); - if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effectInfo.mKey.mArg = effectIt->mSkill; - else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effectInfo.mKey.mArg = effectIt->mAttribute; - effectInfo.mMagnitude = effectIt->mMagnMin + (effectIt->mMagnMax-effectIt->mMagnMin) * randomFactor; - effectInfo.mRemainingTime = effectIt->mDuration + - (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - - // ingredients need special casing for their magnitude / duration - if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) - { - effectInfo.mRemainingTime = effectIt->mDuration * randomFactor + - (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; - - effectInfo.mMagnitude = static_cast (0.05*randomFactor / (0.1 * magicEffect->mData.mBaseCost)); - } - - effects[effectIt->mEffectID].push_back (effectInfo); - } - } + std::map >& effects = visitor.mEffectSources; int w=2; @@ -280,59 +189,4 @@ namespace MWGui } } - - std::string SpellIcons::getSpellDisplayName (const std::string& id) - { - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return spell->mName; - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return potion->mName; - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return ingredient->mName; - - throw std::runtime_error ("ID " + id + " has no display name"); - } - - ESM::EffectList SpellIcons::getSpellEffectList (const std::string& id) - { - if (const ESM::Enchantment* enchantment = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return enchantment->mEffects; - - if (const ESM::Spell *spell = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return spell->mEffects; - - if (const ESM::Potion *potion = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - return potion->mEffects; - - if (const ESM::Ingredient *ingredient = - MWBase::Environment::get().getWorld()->getStore().get().search (id)) - { - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().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; - ESM::EffectList result; - result.mList.push_back (effect); - return result; - } - throw std::runtime_error("ID " + id + " does not have effects"); - } - } diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index 818d67b5b..bae108a1d 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -2,6 +2,7 @@ #define MWGUI_SPELLICONS_H #include +#include #include "../mwmechanics/magiceffects.hpp" @@ -34,14 +35,23 @@ namespace MWGui bool mPermanent; // the effect is permanent }; + class EffectSourceVisitor : public MWMechanics::EffectSourceVisitor + { + public: + bool mIsPermanent; + + std::map > mEffectSources; + + virtual void visit (const ESM::ENAMstruct& enam, + const std::string& sourceName, float magnitude, float remainingTime = -1); + }; + class SpellIcons { public: void updateWidgets(MyGUI::Widget* parent, bool adjustSize); private: - std::string getSpellDisplayName (const std::string& id); - ESM::EffectList getSpellEffectList (const std::string& id); std::map mWidgetMap; }; diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 88fa57a8f..d96a5f351 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -164,12 +164,31 @@ namespace MWMechanics 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().search (id)) + return spell->mName; + + if (const ESM::Potion *potion = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return potion->mName; + + if (const ESM::Ingredient *ingredient = + MWBase::Environment::get().getWorld()->getStore().get().search (id)) + return ingredient->mName; + + throw std::runtime_error ("ID " + id + " has no display name"); + } + ActiveSpells::ActiveSpells() : mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp()) {} - bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name) + bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range, const std::string& name, int effectIndex) { + const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); + std::pair > effects = getEffectList (id); bool stacks = effects.second.second; @@ -195,7 +214,6 @@ namespace MWMechanics if (effects.second.first) { // ingredient -> special treatment required. - const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor); float x = @@ -220,6 +238,26 @@ namespace MWMechanics else iter->second = params; + + + /* + for (int i=0; imRange != 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 (RAND_MAX) + 1) * 100; // [0, 99] + if (roll < reflect) + disabled = true; + } + */ + // Play sounds & particles bool first=true; for (std::vector::const_iterator iter (effects.first.mList.begin()); @@ -338,4 +376,47 @@ namespace MWMechanics { return mSpells; } + + void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const + { + for (TContainer::const_iterator it = begin(); it != end(); ++it) + { + const ESM::EffectList& list = getEffectList(it->first).first; + + float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor(); + + int i=0; + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt, ++i) + { + if (effectIt->mRange != it->second.mRange) + continue; + + std::string name; + if (it->second.mName.empty()) + name = getSpellDisplayName(it->first); + else + name = it->second.mName; + + float remainingTime = effectIt->mDuration + + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second.mRandom[i]; + + // hack for ingredients + if (MWBase::Environment::get().getWorld()->getStore().get().search (it->first)) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + remainingTime = effectIt->mDuration * it->second.mRandom[i] + + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; + + magnitude = static_cast (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); + } + + visitor.visit(*effectIt, name, magnitude, remainingTime); + } + } + } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index e3f882b9a..2cf30da94 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -35,6 +35,10 @@ namespace MWMechanics // Random factor for each effect std::vector 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 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; @@ -65,17 +69,30 @@ namespace MWMechanics std::pair > getEffectList (const std::string& id) const; ///< @return (EffectList, (isIngredient, stacks)) + double timeToExpire (const TIterator& iterator) const; + ///< Returns time (in in-game hours) until the spell pointed to by \a iterator + /// expires. + + const TContainer& getActiveSpells() const; + + TIterator begin() const; + + TIterator end() const; + + std::string getSpellDisplayName (const std::string& id) const; + public: ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = ""); + bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); ///< 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 /// @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? @@ -86,15 +103,8 @@ namespace MWMechanics const MagicEffects& getMagicEffects() const; - const TContainer& getActiveSpells() const; + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; - TIterator begin() const; - - TIterator end() const; - - double timeToExpire (const TIterator& iterator) const; - ///< Returns time (in in-game hours) until the spell pointed to by \a iterator - /// expires. }; } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 212ef312d..9cdc27514 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -2,6 +2,7 @@ #define GAME_MWMECHANICS_MAGICEFFECTS_H #include +#include namespace ESM { @@ -11,6 +12,13 @@ namespace ESM 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 { int mId; @@ -29,11 +37,12 @@ namespace MWMechanics struct EffectParam { - int mMagnitude; + // Note usually this would be int, but applying partial resistance might introduce decimal point. + float mMagnitude; EffectParam(); - EffectParam(int magnitude) : mMagnitude(magnitude) {} + EffectParam(float magnitude) : mMagnitude(magnitude) {} EffectParam& operator+= (const EffectParam& param); diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index df1f6a318..a5a5677d1 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -117,4 +117,27 @@ namespace MWMechanics return false; } + + void Spells::visitEffectSources(EffectSourceVisitor &visitor) const + { + for (TIterator it = begin(); it != end(); ++it) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(it->first); + + // these are the spell types that are permanently in effect + if (!(spell->mData.mType == ESM::Spell::ST_Ability) + && !(spell->mData.mType == ESM::Spell::ST_Disease) + && !(spell->mData.mType == ESM::Spell::ST_Curse) + && !(spell->mData.mType == ESM::Spell::ST_Blight)) + continue; + const ESM::EffectList& list = spell->mEffects; + int i=0; + for (std::vector::const_iterator effectIt = list.mList.begin(); + effectIt != list.mList.end(); ++effectIt, ++i) + { + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i]; + visitor.visit(*effectIt, spell->mName, magnitude); + } + } + } } diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index ccac96190..79b7a782d 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -6,6 +6,8 @@ #include "../mwworld/ptr.hpp" +#include "magiceffects.hpp" + namespace ESM { struct Spell; @@ -59,6 +61,8 @@ namespace MWMechanics bool hasCommonDisease() const; bool hasBlightDisease() const; + + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor) const; }; } diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellsuccess.hpp index 68b89752f..fc6af3c55 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellsuccess.hpp @@ -63,7 +63,7 @@ namespace MWMechanics x *= 0.1 * magicEffect->mData.mBaseCost; x *= 0.5 * (it->mMagnMin + it->mMagnMax); x *= it->mArea * 0.05 * magicEffect->mData.mBaseCost; - if (it->mRange == ESM::RT_Target) + if (magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) x *= 1.5; static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get().find( "fEffectCostMult")->getFloat(); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 6bc2bfc12..bc7e48af1 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -60,7 +60,7 @@ const NpcAnimation::PartBoneMap NpcAnimation::sPartList = createPartListMap(); NpcAnimation::~NpcAnimation() { if (!mListenerDisabled) - mPtr.getClass().getInventoryStore(mPtr).setListener(NULL); + mPtr.getClass().getInventoryStore(mPtr).setListener(NULL, mPtr); Ogre::SceneManager *sceneMgr = mInsert->getCreator(); for(size_t i = 0;i < ESM::PRT_Count;i++) @@ -85,7 +85,7 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v } if (!disableListener) - mPtr.getClass().getInventoryStore(mPtr).setListener(this); + mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr); updateNpcBase(); } diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index c2b1f084d..bec059389 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -123,7 +123,7 @@ void MWWorld::InventoryStore::equip (int slot, const ContainerStoreIterator& ite flagAsModified(); fireEquipmentChangedEvent(); - updateMagicEffects(); + updateMagicEffects(actor); } void MWWorld::InventoryStore::unequipAll(const MWWorld::Ptr& actor) @@ -150,10 +150,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) return mSlots[slot]; } -void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) +void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { - const MWMechanics::NpcStats& stats = MWWorld::Class::get(npc).getNpcStats(npc); - MWWorld::InventoryStore& invStore = MWWorld::Class::get(npc).getInventoryStore(npc); + const MWMechanics::NpcStats& stats = MWWorld::Class::get(actor).getNpcStats(actor); + MWWorld::InventoryStore& invStore = MWWorld::Class::get(actor).getInventoryStore(actor); TSlots slots_; initSlots (slots_); @@ -211,15 +211,15 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) } } - switch(MWWorld::Class::get (test).canBeEquipped (test, npc).first) + switch(MWWorld::Class::get (test).canBeEquipped (test, actor).first) { case 0: continue; case 2: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, npc); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, actor); break; case 3: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, npc); + invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); break; } @@ -255,7 +255,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& npc) { mSlots.swap (slots_); fireEquipmentChangedEvent(); - updateMagicEffects(); + updateMagicEffects(actor); flagAsModified(); } } @@ -265,7 +265,7 @@ const MWMechanics::MagicEffects& MWWorld::InventoryStore::getMagicEffects() cons return mMagicEffects; } -void MWWorld::InventoryStore::updateMagicEffects() +void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) { // To avoid excessive updates during auto-equip if (!mUpdatesEnabled) @@ -293,19 +293,41 @@ void MWWorld::InventoryStore::updateMagicEffects() continue; // Roll some dice, one for each effect - std::vector random; - random.resize(enchantment.mEffects.mList.size()); - for (unsigned int i=0; i (std::rand()) / RAND_MAX; + std::vector params; + params.resize(enchantment.mEffects.mList.size()); + for (unsigned int i=0; i (std::rand()) / RAND_MAX; bool existed = (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) != mPermanentMagicEffectMagnitudes.end()); if (!existed) { + // Try resisting each effect + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectIt->mEffectID); + + //const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + float resisted = 0; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + { + + } + params[i].mMultiplier = (100.f - resisted) / 100.f; + + ++i; + } + + // Note that using the RefID as a key here is not entirely correct. // Consider equipping the same item twice (e.g. a ring) // However, permanent enchantments with a random magnitude are kind of an exploit anyway, // so it doesn't really matter if both items will get the same magnitude. *Extreme* edge case. - mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = random; + mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID] = params; } int i=0; @@ -316,6 +338,10 @@ void MWWorld::InventoryStore::updateMagicEffects() MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); + // Fully resisted? + if (params[i].mMultiplier == 0) + continue; + if (!existed) { // During first auto equip, we don't play any sounds. @@ -325,7 +351,10 @@ void MWWorld::InventoryStore::updateMagicEffects() !mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin()); } - mMagicEffects.add (*effectIt, random[i]); + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; + magnitude *= params[i].mMultiplier; + if (magnitude) + mMagicEffects.add (*effectIt, magnitude); ++i; } } @@ -467,7 +496,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, c } fireEquipmentChangedEvent(); - updateMagicEffects(); + updateMagicEffects(actor); return retval; } @@ -487,10 +516,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItem(const MWWor throw std::runtime_error ("attempt to unequip an item that is not currently equipped"); } -void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener) +void MWWorld::InventoryStore::setListener(InventoryStoreListener *listener, const Ptr& actor) { mListener = listener; - updateMagicEffects(); + updateMagicEffects(actor); } void MWWorld::InventoryStore::fireEquipmentChangedEvent() @@ -500,3 +529,36 @@ void MWWorld::InventoryStore::fireEquipmentChangedEvent() if (mListener) mListener->equipmentChanged(); } + +void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisitor &visitor) +{ + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) + { + if (*iter==end()) + continue; + + std::string enchantmentId = MWWorld::Class::get (**iter).getEnchantment (**iter); + if (enchantmentId.empty()) + continue; + + const ESM::Enchantment& enchantment = + *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); + + if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) + continue; + + if (mPermanentMagicEffectMagnitudes.find((**iter).getCellRef().mRefID) == mPermanentMagicEffectMagnitudes.end()) + continue; + + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + { + float random = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i].mRandom; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; + visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude); + + ++i; + } + } +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 099523a9c..f53ce8efb 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -74,7 +74,15 @@ namespace MWWorld // Vanilla allows permanent effects with a random magnitude, so it needs to be stored here. // We also need this to only play sounds and particle effects when the item is equipped, rather than on every update. - typedef std::map > TEffectMagnitudes; + struct EffectParams + { + // Modifier to scale between min and max magnitude + float mRandom; + // Multiplier for when an effect was fully or partially resisted + float mMultiplier; + }; + + typedef std::map > TEffectMagnitudes; TEffectMagnitudes mPermanentMagicEffectMagnitudes; typedef std::vector TSlots; @@ -88,7 +96,7 @@ namespace MWWorld void initSlots (TSlots& slots_); - void updateMagicEffects(); + void updateMagicEffects(const Ptr& actor); void fireEquipmentChangedEvent(); @@ -127,7 +135,7 @@ namespace MWWorld void unequipAll(const MWWorld::Ptr& actor); ///< Unequip all currently equipped items. - void autoEquip (const MWWorld::Ptr& npc); + void autoEquip (const MWWorld::Ptr& actor); ///< Auto equip items according to stats and item value. const MWMechanics::MagicEffects& getMagicEffects() const; @@ -160,8 +168,10 @@ namespace MWWorld /// (it can be re-stacked so its count may be different than when it /// was equipped). - void setListener (InventoryStoreListener* listener); + void setListener (InventoryStoreListener* listener, const Ptr& actor); ///< Set a listener for various events, see \a InventoryStoreListener + + void visitEffectSources (MWMechanics::EffectSourceVisitor& visitor); }; }