diff --git a/CHANGELOG.md b/CHANGELOG.md index 05d3126c77..319c635d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies + Bug #7428: AutoCalc flag is not used to calculate enchantment costs Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6447: Add LOD support to Object Paging diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index 48fb236fc6..f50db3cb6b 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -12,6 +12,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwmechanics/spellutil.hpp" + #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -199,8 +201,8 @@ namespace MWGui break; line.mCharge->setVisible(true); - line.mCharge->setValue( - static_cast(line.mItemPtr.getCellRef().getEnchantmentCharge()), ench->mData.mCharge); + line.mCharge->setValue(static_cast(line.mItemPtr.getCellRef().getEnchantmentCharge()), + MWMechanics::getEnchantmentCharge(*ench)); break; } } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index d56bd11973..81074d7fcf 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -24,6 +24,7 @@ #include "../mwworld/nullaction.hpp" #include "../mwmechanics/alchemy.hpp" +#include "../mwmechanics/spellutil.hpp" namespace { @@ -112,8 +113,8 @@ namespace if (ench->mData.mType == ESM::Enchantment::ConstantEffect) leftChargePercent = 101; else - leftChargePercent = static_cast( - left.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); + leftChargePercent + = static_cast(left.mBase.getCellRef().getNormalizedEnchantmentCharge(*ench) * 100); } } @@ -126,8 +127,8 @@ namespace if (ench->mData.mType == ESM::Enchantment::ConstantEffect) rightChargePercent = 101; else - rightChargePercent = static_cast( - right.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); + rightChargePercent + = static_cast(right.mBase.getCellRef().getNormalizedEnchantmentCharge(*ench) * 100); } } @@ -304,8 +305,8 @@ namespace MWGui return false; } - if (base.getCellRef().getEnchantmentCharge() >= ench->mData.mCharge - || base.getCellRef().getEnchantmentCharge() == -1) + if (base.getCellRef().getEnchantmentCharge() == -1 + || base.getCellRef().getEnchantmentCharge() >= MWMechanics::getEnchantmentCharge(*ench)) return false; } diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index dafa009eef..a995865ca6 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -153,13 +153,12 @@ namespace MWGui && item.getClass().canBeEquipped(item, mActor).first == 0) continue; - int castCost - = MWMechanics::getEffectiveEnchantmentCastCost(static_cast(enchant->mData.mCost), mActor); + int castCost = MWMechanics::getEffectiveEnchantmentCastCost(*enchant, mActor); std::string cost = std::to_string(castCost); int currentCharge = int(item.getCellRef().getEnchantmentCharge()); if (currentCharge == -1) - currentCharge = enchant->mData.mCharge; + currentCharge = MWMechanics::getEnchantmentCharge(*enchant); std::string charge = std::to_string(currentCharge); newSpell.mCostColumn = cost + "/" + charge; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 265ccd8ec7..6867b9114d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -539,7 +539,7 @@ namespace MWGui if (enchant->mData.mType == ESM::Enchantment::WhenStrikes || enchant->mData.mType == ESM::Enchantment::WhenUsed) { - int maxCharge = enchant->mData.mCharge; + const int maxCharge = MWMechanics::getEnchantmentCharge(*enchant); int charge = (info.remainingEnchantCharge == -1) ? maxCharge : info.remainingEnchantCharge; const int chargeWidth = 204; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 4f2979b72b..fab7191a32 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1384,8 +1384,7 @@ namespace MWGui mSelectedSpell = ESM::RefId(); const ESM::Enchantment* ench = mStore->get().find(item.getClass().getEnchantment(item)); - int chargePercent - = static_cast(item.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100); + int chargePercent = static_cast(item.getCellRef().getNormalizedEnchantmentCharge(*ench) * 100); mHud->setSelectedEnchantItem(item, chargePercent); mSpellWindow->setTitle(item.getClass().getName(item)); } diff --git a/apps/openmw/mwmechanics/recharge.cpp b/apps/openmw/mwmechanics/recharge.cpp index b9a6a3ca76..fc8a0e8a72 100644 --- a/apps/openmw/mwmechanics/recharge.cpp +++ b/apps/openmw/mwmechanics/recharge.cpp @@ -15,6 +15,7 @@ #include "actorutil.hpp" #include "creaturestats.hpp" +#include "spellutil.hpp" namespace MWMechanics { @@ -68,8 +69,9 @@ namespace MWMechanics const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get().find( item.getClass().getEnchantment(item)); - item.getCellRef().setEnchantmentCharge(std::min( - item.getCellRef().getEnchantmentCharge() + restored, static_cast(enchantment->mData.mCharge))); + const int maxCharge = MWMechanics::getEnchantmentCharge(*enchantment); + item.getCellRef().setEnchantmentCharge( + std::min(item.getCellRef().getEnchantmentCharge() + restored, static_cast(maxCharge))); MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("Enchant Success")); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 195bda5ddf..59c2a1686d 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -319,10 +319,11 @@ namespace MWMechanics if (!godmode && (type == ESM::Enchantment::WhenUsed || (!isProjectile && type == ESM::Enchantment::WhenStrikes))) { - int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), mCaster); + int castCost = getEffectiveEnchantmentCastCost(*enchantment, mCaster); if (item.getCellRef().getEnchantmentCharge() == -1) - item.getCellRef().setEnchantmentCharge(static_cast(enchantment->mData.mCharge)); + item.getCellRef().setEnchantmentCharge( + static_cast(MWMechanics::getEnchantmentCharge(*enchantment))); if (item.getCellRef().getEnchantmentCharge() < castCost) { diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 2302a6d70d..b0120d91fb 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -296,8 +296,7 @@ namespace { const ESM::Enchantment* enchantment = esmStore.get().search(spellId); if (enchantment) - spellCost = MWMechanics::getEffectiveEnchantmentCastCost( - static_cast(enchantment->mData.mCost), caster); + spellCost = MWMechanics::getEffectiveEnchantmentCastCost(*enchantment, caster); } // Magicka is increased by the cost of the spell diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index fbcfa9a9c1..e2c0dad55f 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -171,7 +171,7 @@ namespace MWMechanics if (actor.getClass().isNpc() && !store.isEquipped(ptr)) return 0.f; - int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); + int castCost = getEffectiveEnchantmentCastCost(*enchantment, actor); if (ptr.getCellRef().getEnchantmentCharge() != -1 && ptr.getCellRef().getEnchantmentCharge() < castCost) return 0.f; diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index a03b7577d3..7f032bd181 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -2,6 +2,7 @@ #include +#include #include #include "../mwbase/environment.hpp" @@ -15,6 +16,27 @@ namespace MWMechanics { + namespace + { + float getTotalCost(const ESM::EffectList& list, const EffectCostMethod method = EffectCostMethod::GameSpell) + { + float cost = 0; + + for (const ESM::ENAMstruct& effect : list.mList) + { + float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect, nullptr, method)); + + // This is applied to the whole spell cost for each effect when + // creating spells, but is only applied on the effect itself in TES:CS. + if (effect.mRange == ESM::RT_Target) + effectCost *= 1.5; + + cost += effectCost; + } + return cost; + } + } + ESM::RefId spellSchoolToSkill(int school) { static const std::array schoolSkillArray{ @@ -39,6 +61,11 @@ namespace MWMechanics bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce; int minMagn = hasMagnitude ? effect.mMagnMin : 1; int maxMagn = hasMagnitude ? effect.mMagnMax : 1; + if (method != EffectCostMethod::GameEnchantment) + { + minMagn = std::max(1, minMagn); + maxMagn = std::max(1, maxMagn); + } int duration = hasDuration ? effect.mDuration : 1; if (!appliedOnce) duration = std::max(1, duration); @@ -52,7 +79,7 @@ namespace MWMechanics minArea = 1; } - float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); + float x = 0.5 * (minMagn + maxMagn); x *= 0.1 * magicEffect->mData.mBaseCost; x *= durationOffset + duration; x += 0.05 * std::max(minArea, effect.mArea) * magicEffect->mData.mBaseCost; @@ -65,19 +92,7 @@ namespace MWMechanics if (!(spell.mData.mFlags & ESM::Spell::F_Autocalc)) return spell.mData.mCost; - float cost = 0; - - for (const ESM::ENAMstruct& effect : spell.mEffects.mList) - { - float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect)); - - // This is applied to the whole spell cost for each effect when - // creating spells, but is only applied on the effect itself in TES:CS. - if (effect.mRange == ESM::RT_Target) - effectCost *= 1.5; - - cost += effectCost; - } + float cost = getTotalCost(spell.mEffects); return std::round(cost); } @@ -94,6 +109,50 @@ namespace MWMechanics return static_cast((result < 1) ? 1 : result); } + int getEffectiveEnchantmentCastCost(const ESM::Enchantment& enchantment, const MWWorld::Ptr& actor) + { + float castCost; + if (enchantment.mData.mFlags & ESM::Enchantment::Autocalc) + castCost = getTotalCost(enchantment.mEffects, EffectCostMethod::GameEnchantment); + else + castCost = static_cast(enchantment.mData.mCost); + return getEffectiveEnchantmentCastCost(castCost, actor); + } + + int getEnchantmentCharge(const ESM::Enchantment& enchantment) + { + if (enchantment.mData.mFlags & ESM::Enchantment::Autocalc) + { + int charge + = static_cast(std::round(getTotalCost(enchantment.mEffects, EffectCostMethod::GameEnchantment))); + const auto& store = MWBase::Environment::get().getESMStore()->get(); + switch (enchantment.mData.mType) + { + case ESM::Enchantment::CastOnce: + { + static const int iMagicItemChargeOnce = store.find("iMagicItemChargeOnce")->mValue.getInteger(); + return charge * iMagicItemChargeOnce; + } + case ESM::Enchantment::WhenStrikes: + { + static const int iMagicItemChargeStrike = store.find("iMagicItemChargeStrike")->mValue.getInteger(); + return charge * iMagicItemChargeStrike; + } + case ESM::Enchantment::WhenUsed: + { + static const int iMagicItemChargeUse = store.find("iMagicItemChargeUse")->mValue.getInteger(); + return charge * iMagicItemChargeUse; + } + case ESM::Enchantment::ConstantEffect: + { + static const int iMagicItemChargeConst = store.find("iMagicItemChargeConst")->mValue.getInteger(); + return charge * iMagicItemChargeConst; + } + } + } + return enchantment.mData.mCharge; + } + float calcSpellBaseSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool) { // Morrowind for some reason uses a formula slightly different from magicka cost calculation diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index 8db37b9adf..6b346b5c34 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -6,6 +6,7 @@ namespace ESM { struct ENAMstruct; + struct Enchantment; struct MagicEffect; struct Spell; } @@ -23,6 +24,7 @@ namespace MWMechanics { GameSpell, PlayerSpell, + GameEnchantment, }; float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr, @@ -30,6 +32,8 @@ namespace MWMechanics int calcSpellCost(const ESM::Spell& spell); int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr& actor); + int getEffectiveEnchantmentCastCost(const ESM::Enchantment& enchantment, const MWWorld::Ptr& actor); + int getEnchantmentCharge(const ESM::Enchantment& enchantment); /** * @param spell spell to cast diff --git a/apps/openmw/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index c123a94056..e0584afcd4 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -108,7 +108,7 @@ namespace MWMechanics const ESM::Enchantment* enchantment = world->getStore().get().find(weapon->mEnchant); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { - int castCost = getEffectiveEnchantmentCastCost(static_cast(enchantment->mData.mCost), actor); + int castCost = getEffectiveEnchantmentCastCost(*enchantment, actor); float charge = item.getCellRef().getEnchantmentCharge(); if (charge == -1 || charge >= castCost || weapclass == ESM::WeaponType::Thrown diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index 3f85f9c9ee..bb204e0515 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -10,6 +10,7 @@ #include #include +#include #include namespace MWWorld @@ -129,8 +130,9 @@ namespace MWWorld mCellRef.mVariant); } - float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const + float CellRef::getNormalizedEnchantmentCharge(const ESM::Enchantment& enchantment) const { + const int maxCharge = MWMechanics::getEnchantmentCharge(enchantment); if (maxCharge == 0) { return 0; diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 56e3f094ba..b470644bf7 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -9,6 +9,7 @@ namespace ESM { + struct Enchantment; struct ObjectState; } @@ -87,7 +88,7 @@ namespace MWWorld float getEnchantmentCharge() const; // Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment). - float getNormalizedEnchantmentCharge(int maxCharge) const; + float getNormalizedEnchantmentCharge(const ESM::Enchantment& enchantment) const; void setEnchantmentCharge(float charge); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index bd19ad6683..c6a03244be 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -55,6 +55,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/recharge.hpp" +#include "../mwmechanics/spellutil.hpp" #include "class.hpp" #include "containerstore.hpp" @@ -1248,7 +1249,8 @@ namespace MWWorld if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - mRechargingItems.emplace_back(ptr.getBase(), static_cast(enchantment->mData.mCharge)); + mRechargingItems.emplace_back( + ptr.getBase(), static_cast(MWMechanics::getEnchantmentCharge(*enchantment))); } Ptr MWWorld::CellStore::getMovedActor(int actorId) const diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 5de939b6e9..30287b4d4e 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -16,6 +16,7 @@ #include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/recharge.hpp" +#include "../mwmechanics/spellutil.hpp" #include "class.hpp" #include "esmstore.hpp" @@ -268,7 +269,7 @@ bool MWWorld::ContainerStore::stacks(const ConstPtr& ptr1, const ConstPtr& ptr2) { const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get().find( ptr1.getClass().getEnchantment(ptr1)); - float maxCharge = static_cast(enchantment->mData.mCharge); + const float maxCharge = static_cast(MWMechanics::getEnchantmentCharge(*enchantment)); float enchantCharge1 = ptr1.getCellRef().getEnchantmentCharge() == -1 ? maxCharge : ptr1.getCellRef().getEnchantmentCharge(); float enchantCharge2 @@ -507,7 +508,7 @@ void MWWorld::ContainerStore::updateRechargingItems() if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) - mRechargingItems.emplace_back(it, static_cast(enchantment->mData.mCharge)); + mRechargingItems.emplace_back(it, static_cast(MWMechanics::getEnchantmentCharge(*enchantment))); } } }