From c5f1bbcc5fb41151dd7ea8117b519ad0e849a449 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 19:43:25 +0100 Subject: [PATCH 1/7] Add functions to get the effect affecting resistance and weakness for another effect --- components/esm/loadmgef.cpp | 94 +++++++++++++++++++++++++++++++++++++ components/esm/loadmgef.hpp | 6 +++ 2 files changed, 100 insertions(+) diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index 1a90f5b09c..46e2a8555d 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -81,6 +81,100 @@ void MagicEffect::save(ESMWriter &esm) const esm.writeHNOString("DESC", mDescription); } +short MagicEffect::getResistanceEffect(short effect) +{ + // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute + + // + std::map effects; + effects[DisintegrateArmor] = Sanctuary; + effects[DisintegrateWeapon] = Sanctuary; + + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = ResistMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = ResistMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = ResistMagicka; + + effects[Burden] = ResistMagicka; + effects[Charm] = ResistMagicka; + effects[Silence] = ResistMagicka; + effects[Blind] = ResistMagicka; + effects[Sound] = ResistMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid] = ResistMagicka; + effects[FrenzyHumanoid] = ResistMagicka; + effects[DemoralizeHumanoid] = ResistMagicka; + effects[RallyHumanoid] = ResistMagicka; + } + + effects[TurnUndead] = ResistMagicka; + + effects[FireDamage] = ResistFire; + effects[FrostDamage] = ResistFrost; + effects[ShockDamage] = ResistShock; + effects[Vampirism] = ResistCommonDisease; + effects[Corprus] = ResistCorprusDisease; + effects[Poison] = ResistPoison; + effects[Paralyze] = ResistParalysis; + + if (effects.find(effect) != effects.end()) + return effects[effect]; + else + return -1; +} + +short MagicEffect::getWeaknessEffect(short effect) +{ + std::map effects; + effects[DisintegrateArmor] = Sanctuary; + effects[DisintegrateWeapon] = Sanctuary; + + for (int i=0; i<5; ++i) + effects[DrainAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[DamageAttribute+i] = WeaknessToMagicka; + for (int i=0; i<5; ++i) + effects[AbsorbAttribute+i] = WeaknessToMagicka; + for (int i=0; i<10; ++i) + effects[WeaknessToFire+i] = WeaknessToMagicka; + + effects[Burden] = WeaknessToMagicka; + effects[Charm] = WeaknessToMagicka; + effects[Silence] = WeaknessToMagicka; + effects[Blind] = WeaknessToMagicka; + effects[Sound] = WeaknessToMagicka; + + for (int i=0; i<2; ++i) + { + effects[CalmHumanoid] = WeaknessToMagicka; + effects[FrenzyHumanoid] = WeaknessToMagicka; + effects[DemoralizeHumanoid] = WeaknessToMagicka; + effects[RallyHumanoid] = WeaknessToMagicka; + } + + effects[TurnUndead] = WeaknessToMagicka; + + effects[FireDamage] = WeaknessToFire; + effects[FrostDamage] = WeaknessToFrost; + effects[ShockDamage] = WeaknessToShock; + effects[Vampirism] = WeaknessToCommonDisease; + effects[Corprus] = WeaknessToCorprusDisease; + effects[Poison] = WeaknessToPoison; + + // Weakness to magicka or -1 ? + effects[Paralyze] = WeaknessToMagicka; + + if (effects.find(effect) != effects.end()) + return effects[effect]; + else + return -1; +} static std::map genNameMap() { diff --git a/components/esm/loadmgef.hpp b/components/esm/loadmgef.hpp index b1047e94af..cc9cc180ee 100644 --- a/components/esm/loadmgef.hpp +++ b/components/esm/loadmgef.hpp @@ -58,6 +58,12 @@ struct MagicEffect static const std::string &effectIdToString(short effectID); static short effectStringToId(const std::string &effect); + + /// Returns the effect that provides resistance against \a effect (or -1 if there's none) + static short getResistanceEffect(short effect); + /// Returns the effect that induces weakness against \a effect (or -1 if there's none) + static short getWeaknessEffect(short effect); + MagnitudeDisplayType getMagnitudeDisplayType() const; From c73217627e1a7533bf181a2731673ae77ea24247 Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 15 Nov 2013 20:29:47 +0100 Subject: [PATCH 2/7] Move code for listing effect sources to the spell management classes --- apps/openmw/mwgui/spellicons.cpp | 194 +++-------------------- apps/openmw/mwgui/spellicons.hpp | 14 +- apps/openmw/mwmechanics/activespells.cpp | 85 +++++++++- apps/openmw/mwmechanics/activespells.hpp | 28 ++-- apps/openmw/mwmechanics/magiceffects.hpp | 13 +- apps/openmw/mwmechanics/spells.cpp | 23 +++ apps/openmw/mwmechanics/spells.hpp | 4 + apps/openmw/mwmechanics/spellsuccess.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 +- apps/openmw/mwworld/inventorystore.cpp | 98 +++++++++--- apps/openmw/mwworld/inventorystore.hpp | 18 ++- 11 files changed, 273 insertions(+), 210 deletions(-) diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index da51f7e8f1..a18e88f5fd 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 818d67b5bb..bae108a1da 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 88fa57a8f6..d96a5f3510 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 e3f882b9aa..2cf30da94b 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 212ef312dd..9cdc27514e 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 df1f6a3182..a5a5677d12 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 ccac96190b..79b7a782d0 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 68b89752f8..fc6af3c552 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 6bc2bfc12a..bc7e48af1b 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 c2b1f084d5..bec0593891 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 099523a9c8..f53ce8efb9 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); }; } From aa4b2d9504f642d691e26b63a696d199edf0059d Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 01:19:39 +0100 Subject: [PATCH 3/7] Fix uninitialized mSkill/mAttribute for spellmaker spells --- apps/openmw/mwgui/spellcreationdialog.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index dc86fd825f..e0b808b283 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -9,7 +9,7 @@ #include "../mwworld/player.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "tooltips.hpp" #include "class.hpp" @@ -89,6 +89,8 @@ namespace MWGui mEffect.mMagnMax = 1; mEffect.mDuration = 1; mEffect.mArea = 0; + mEffect.mSkill = -1; + mEffect.mAttribute = -1; eventEffectAdded(mEffect); onRangeButtonClicked(mRangeButton); From d49b6f19ffef8bd9ed6fe118233285458592b8dc Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 02:11:11 +0100 Subject: [PATCH 4/7] Don't advance acrobatics skill for NPCs --- apps/openmw/mwmechanics/character.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 76bbafb22b..240695a1cb 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -782,7 +782,7 @@ void CharacterController::update(float duration) if(!onground && !flying && !inwater) { - // The player is in the air (either getting up —ascending part of jump— or falling). + // In the air (either getting up —ascending part of jump— or falling). if (world->isSlowFalling(mPtr)) { @@ -817,8 +817,7 @@ void CharacterController::update(float duration) } else if(vec.z > 0.0f && mJumpState == JumpState_None) { - // The player has started a jump. - + // Started a jump. float z = cls.getJump(mPtr); if(vec.x == 0 && vec.y == 0) vec = Ogre::Vector3(0.0f, 0.0f, z); @@ -829,7 +828,8 @@ void CharacterController::update(float duration) } // advance acrobatics - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 0); // decrease fatigue const MWWorld::Store &gmst = world->getStore().get(); @@ -843,8 +843,6 @@ void CharacterController::update(float duration) } else if(mJumpState == JumpState_Falling) { - // The player is landing. - forcestateupdate = true; mJumpState = JumpState_Landing; vec.z = 0.0f; @@ -861,7 +859,8 @@ void CharacterController::update(float duration) cls.getCreatureStats(mPtr).setHealth(health); // report acrobatics progression - cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); + if (mPtr.getRefData().getHandle() == "player") + cls.skillUsageSucceeded(mPtr, ESM::Skill::Acrobatics, 1); const float acrobaticsSkill = cls.getNpcStats(mPtr).getSkill(ESM::Skill::Acrobatics).getModified(); if (healthLost > (acrobaticsSkill * fatigueTerm)) From b1a29eb27eafdc2db1bf7ffb416ac42aea25357f Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 02:34:43 +0100 Subject: [PATCH 5/7] Implement Resist & Weakness effects --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwclass/npc.cpp | 6 +- apps/openmw/mwgui/quickkeysmenu.cpp | 2 +- apps/openmw/mwgui/spellwindow.cpp | 2 +- apps/openmw/mwmechanics/activespells.cpp | 104 +++++++++++------- apps/openmw/mwmechanics/activespells.hpp | 7 +- .../{spellsuccess.hpp => spellcasting.hpp} | 66 +++++++++++ apps/openmw/mwworld/actiontrap.cpp | 2 +- apps/openmw/mwworld/inventorystore.cpp | 36 +++--- apps/openmw/mwworld/worldimp.cpp | 6 +- 10 files changed, 159 insertions(+), 74 deletions(-) rename apps/openmw/mwmechanics/{spellsuccess.hpp => spellcasting.hpp} (64%) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 04bd89f959..cc4a9d6de3 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -69,7 +69,7 @@ add_openmw_dir (mwclass add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence alchemy aiwander aitravel aifollow - aiescort aiactivate repair enchanting pathfinding security spellsuccess + aiescort aiactivate repair enchanting pathfinding security spellsuccess spellcasting ) add_openmw_dir (mwbase diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 35a6d17a3b..fd24dd93fc 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -468,9 +468,9 @@ namespace MWClass { weapon.getCellRef().mEnchantmentCharge -= castCost; // Touch - othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ESM::RT_Touch, weapon.getClass().getName(weapon)); + othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ptr, ESM::RT_Touch, weapon.getClass().getName(weapon)); // Self - getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ESM::RT_Self, weapon.getClass().getName(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)); } @@ -936,7 +936,7 @@ namespace MWClass /// \todo consider instant effects - return stats.getActiveSpells().addSpell (id, actor); + return stats.getActiveSpells().addSpell (id, actor, actor); } void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 5f749d3d3d..503bf7c114 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -5,7 +5,7 @@ #include "../mwworld/player.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwgui/inventorywindow.hpp" #include "../mwgui/bookwindow.hpp" #include "../mwgui/scrollwindow.hpp" diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 61ac2c7b29..03bb106310 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -9,7 +9,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/actionequip.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "spellicons.hpp" #include "inventorywindow.hpp" diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index d96a5f3510..3a9321f398 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -16,11 +16,14 @@ #include "../mwbase/environment.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" @@ -74,7 +77,7 @@ namespace MWMechanics for (std::vector::const_iterator effectIter (effects.first.mList.begin()); effectIter!=effects.first.mList.end(); ++effectIter, ++i) { - float magnitude = iter->second.mRandom[i]; + float random = iter->second.mRandom[i]; if (effectIter->mRange != iter->second.mRange) continue; @@ -83,7 +86,7 @@ namespace MWMechanics int duration = effectIter->mDuration; if (effects.second.first) - duration *= magnitude; + duration *= random; MWWorld::TimeStamp end = start; end += static_cast (duration)* @@ -102,19 +105,21 @@ namespace MWMechanics if (effectIter->mDuration==0) { param.mMagnitude = - static_cast (magnitude / (0.1 * magicEffect->mData.mBaseCost)); + static_cast (random / (0.1 * magicEffect->mData.mBaseCost)); } else { param.mMagnitude = - static_cast (0.05*magnitude / (0.1 * magicEffect->mData.mBaseCost)); + static_cast (0.05*random / (0.1 * magicEffect->mData.mBaseCost)); } } else param.mMagnitude = static_cast ( - (effectIter->mMagnMax-effectIter->mMagnMin)*magnitude + effectIter->mMagnMin); - - mEffects.add (*effectIter, param); + (effectIter->mMagnMax-effectIter->mMagnMin)*random + effectIter->mMagnMin); + param.mMagnitude *= iter->second.mMultiplier[i]; + + if (param.mMagnitude) + mEffects.add (*effectIter, param); } } } @@ -185,7 +190,7 @@ namespace MWMechanics : 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, int effectIndex) + 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); @@ -197,6 +202,8 @@ namespace MWMechanics for (std::vector::const_iterator iter (effects.first.mList.begin()); iter!=effects.first.mList.end(); ++iter) { + if (iter->mRange != range) + continue; if (iter->mDuration) { found = true; @@ -204,41 +211,37 @@ namespace MWMechanics } } + // If none of the effects need to apply, no need to add the spell if (!found) return false; TContainer::iterator iter = mSpells.find (id); - float random = static_cast (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; - } - ActiveSpellParams params; for (unsigned int i=0; i (std::rand()) / RAND_MAX); + { + float random = static_cast (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; - - if (iter==mSpells.end() || stacks) - mSpells.insert (std::make_pair (id, params)); - else - iter->second = params; - - + params.mMultiplier.resize(effects.first.mList.size(), 1); /* for (int i=0; i::const_iterator iter (effects.first.mList.begin()); - iter!=effects.first.mList.end(); ++iter) + int i = 0; + for (std::vector::const_iterator effectIt (effects.first.mList.begin()); + effectIt!=effects.first.mList.end(); ++effectIt, ++i) { - if (iter->mRange != range) + if (effectIt->mRange != range) + continue; + + // Try resisting effect in case its harmful + const ESM::Spell *spell = + MWBase::Environment::get().getWorld()->getStore().get().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. const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( - iter->mEffectID); + effectIt->mEffectID); // Only the sound of the first effect plays if (first) @@ -296,6 +315,11 @@ namespace MWMechanics first = false; } + if (iter==mSpells.end() || stacks) + mSpells.insert (std::make_pair (id, params)); + else + iter->second = params; + mSpellsChanged = true; return true; @@ -415,7 +439,9 @@ namespace MWMechanics magnitude = static_cast (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); } - visitor.visit(*effectIt, name, magnitude, remainingTime); + magnitude *= it->second.mMultiplier[i]; + if (magnitude) + visitor.visit(*effectIt, name, magnitude, remainingTime); } } } diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 2cf30da94b..b5c302afe1 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -37,7 +37,7 @@ namespace MWMechanics // 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; + 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 @@ -85,11 +85,12 @@ namespace MWMechanics ActiveSpells(); - bool addSpell (const std::string& id, const MWWorld::Ptr& actor, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1); + 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); ///< 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 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 diff --git a/apps/openmw/mwmechanics/spellsuccess.hpp b/apps/openmw/mwmechanics/spellcasting.hpp similarity index 64% rename from apps/openmw/mwmechanics/spellsuccess.hpp rename to apps/openmw/mwmechanics/spellcasting.hpp index fc6af3c552..3bbb4d0c53 100644 --- a/apps/openmw/mwmechanics/spellsuccess.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -114,6 +114,72 @@ namespace MWMechanics return school; } + /// @return >=100 for fully resisted. can also return negative value for damage amplification. + inline float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL) + { + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().find ( + effectId); + + const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + + float resisted = 0; + if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful) + { + + short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); + short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); + + float resistance = 0; + if (resistanceEffect != -1) + resistance += stats.getMagicEffects().get(resistanceEffect).mMagnitude; + if (weaknessEffect != -1) + resistance -= stats.getMagicEffects().get(weaknessEffect).mMagnitude; + + + float willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); + float luck = stats.getAttribute(ESM::Attribute::Luck).getModified(); + float x = (willpower + 0.1 * luck) * stats.getFatigueTerm(); + + // This makes spells that are easy to cast harder to resist and vice versa + if (spell != NULL) + { + float castChance = getSpellSuccessChance(spell, caster); + if (castChance > 0) + x *= 50 / castChance; + } + + float roll = static_cast(std::rand()) / RAND_MAX * 100; + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + roll -= resistance; + + if (x <= roll) + x = 0; + else + { + if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude) + x = 100; + else + x = roll / std::min(x, 100.f); + } + + x = std::min(x + resistance, 100.f); + + resisted = x; + } + + return resisted; + } + + inline float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL) + { + float resistance = getEffectResistance(effectId, actor, caster, spell); + if (resistance >= 0) + return 1 - resistance / 100.f; + else + return -(resistance-100) / 100.f; + } + } #endif diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index 13b2fd2694..80da290727 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -16,7 +16,7 @@ namespace MWWorld // TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could // make it lock itself when activated for example. - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, ESM::RT_Touch); + actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, actor, ESM::RT_Touch); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(mSpellId); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index bec0593891..9e18691254 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -11,6 +11,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/npcstats.hpp" +#include "../mwmechanics/spellcasting.hpp" + #include "esmstore.hpp" #include "class.hpp" @@ -292,47 +294,37 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) continue; - // Roll some dice, one for each effect 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) { + // Roll some dice, one for each effect + params.resize(enchantment.mEffects.mList.size()); + for (unsigned int i=0; i (std::rand()) / RAND_MAX; + // 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; - + params[i].mMultiplier = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, actor); ++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] = params; } + else + params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID]; int i=0; for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); - effectIt!=enchantment.mEffects.mList.end(); ++effectIt) + effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( @@ -355,7 +347,6 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) magnitude *= params[i].mMultiplier; if (magnitude) mMagicEffects.add (*effectIt, magnitude); - ++i; } } } @@ -554,8 +545,9 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito 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; + const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i]; + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; + magnitude *= params.mMultiplier; visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude); ++i; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index eca2ebb79f..e6c4c1ef08 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -26,7 +26,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/movement.hpp" #include "../mwmechanics/npcstats.hpp" -#include "../mwmechanics/spellsuccess.hpp" +#include "../mwmechanics/spellcasting.hpp" #include "../mwrender/sky.hpp" @@ -2158,7 +2158,7 @@ namespace MWWorld // Now apply the spell! // Apply Self portion - actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, ESM::RT_Self, sourceName); + 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? @@ -2166,7 +2166,7 @@ namespace MWWorld if (!contact.first.isEmpty()) { if (contact.first.getClass().isActor()) - contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, ESM::RT_Touch, sourceName); + 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. From bb43ec9b35b3f45d55de492834b5d45f550890f5 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 03:16:21 +0100 Subject: [PATCH 6/7] Implement damage tick effects --- apps/openmw/mwmechanics/activespells.cpp | 12 ++++++++---- apps/openmw/mwmechanics/actors.cpp | 18 ++++++++++++++++-- apps/openmw/mwmechanics/actors.hpp | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 3a9321f398..89543476b0 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -269,6 +269,14 @@ namespace MWMechanics if (effectIt->mRange != range) continue; + const ESM::MagicEffect *magicEffect = + MWBase::Environment::get().getWorld()->getStore().get().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().search (id); @@ -287,10 +295,6 @@ namespace MWMechanics // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. - const ESM::MagicEffect *magicEffect = - MWBase::Environment::get().getWorld()->getStore().get().find ( - effectIt->mEffectID); - // Only the sound of the first effect plays if (first) { diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3d52ce8e69..576c830da7 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -29,7 +29,7 @@ namespace MWMechanics // magic effects adjustMagicEffects (ptr); calculateDynamicStats (ptr); - calculateCreatureStatModifiers (ptr); + calculateCreatureStatModifiers (ptr, duration); if(!MWBase::Environment::get().getWindowManager()->isGuiMode()) { @@ -145,7 +145,7 @@ namespace MWMechanics stats.setFatigue (fatigue); } - void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr) + void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) { CreatureStats &creatureStats = MWWorld::Class::get(ptr).getCreatureStats(ptr); const MagicEffects &effects = creatureStats.getMagicEffects(); @@ -167,10 +167,24 @@ namespace MWMechanics stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); + float damage = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth)).mMagnitude; + stat.setCurrent(stat.getCurrent() - damage * duration); + creatureStats.setDynamic(i, stat); } + // Apply damage ticks + int damageEffects[] = { + ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, ESM::MagicEffect::Poison + }; + for (unsigned int i=0; i health = creatureStats.getHealth(); + health.setCurrent(health.getCurrent() - magnitude * duration); + creatureStats.setHealth(health); + } } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index eeef22635c..251c38ec0e 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -40,7 +40,7 @@ namespace MWMechanics void calculateDynamicStats (const MWWorld::Ptr& ptr); - void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr); + void calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration); void calculateNpcStatModifiers (const MWWorld::Ptr& ptr); void calculateRestoration (const MWWorld::Ptr& ptr, float duration); From 7474e87edce448361d6f48eb4013bac85c7a0c02 Mon Sep 17 00:00:00 2001 From: scrawl Date: Sat, 16 Nov 2013 05:06:54 +0100 Subject: [PATCH 7/7] Implement RestoreHealth/Magicka/Fatigue --- apps/openmw/mwmechanics/activespells.hpp | 2 +- apps/openmw/mwmechanics/actors.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index b5c302afe1..56d9413c39 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -57,7 +57,7 @@ namespace MWMechanics private: - mutable TContainer mSpells; // spellId, (time of casting, relative magnitude) + mutable TContainer mSpells; mutable MagicEffects mEffects; mutable bool mSpellsChanged; mutable MWWorld::TimeStamp mLastUpdate; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 576c830da7..2873816f73 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -167,8 +167,9 @@ namespace MWMechanics stat.setModifier(effects.get(EffectKey(ESM::MagicEffect::FortifyHealth+i)).mMagnitude - effects.get(EffectKey(ESM::MagicEffect::DrainHealth+i)).mMagnitude); - float damage = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth)).mMagnitude; - stat.setCurrent(stat.getCurrent() - damage * duration); + float currentDiff = creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::RestoreHealth+i)).mMagnitude + - creatureStats.getMagicEffects().get(EffectKey(ESM::MagicEffect::DamageHealth+i)).mMagnitude; + stat.setCurrent(stat.getCurrent() + currentDiff * duration); creatureStats.setDynamic(i, stat); }