diff --git a/AUTHORS.md b/AUTHORS.md index 75302908ea..22a18506e8 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -118,6 +118,7 @@ Programmers Kurnevsky Evgeny (kurnevsky) Lars Söderberg (Lazaroth) lazydev + Léo Peltier Leon Krieg (lkrieg) Leon Saunders (emoose) logzero diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4a5e9024..d4ffe9a630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) Bug #3846: Strings starting with "-" fail to compile if not enclosed in quotes + Bug #5483: AutoCalc flag is not used to calculate spells cost 0.47.0 ------ diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 5a5dec60f7..01653d9e6f 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -456,7 +456,7 @@ namespace MWGui for (const ESM::ENAMstruct& effect : mEffects) { - y += std::max(1.f, MWMechanics::calcEffectCost(effect)); + y += std::max(1.f, MWMechanics::calcEffectCost(effect, nullptr, MWMechanics::EffectCostMethod::PlayerSpell)); if (effect.mRange == ESM::RT_Target) y *= 1.5; diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 78b9171e59..fe18b0f082 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -109,7 +109,7 @@ namespace MWGui if (spell->mData.mType == ESM::Spell::ST_Spell) { newSpell.mType = Spell::Type_Spell; - std::string cost = std::to_string(spell->mData.mCost); + std::string cost = std::to_string(MWMechanics::calcSpellCost(*spell)); std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor))); newSpell.mCostColumn = cost + "/" + chance; } diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index a821d0106b..84d8b65592 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -251,7 +251,7 @@ namespace MWGui } std::string cost = focus->getUserString("SpellCost"); if (cost != "" && cost != "0") - info.text += MWGui::ToolTips::getValueString(spell->mData.mCost, "#{sCastCost}"); + info.text += MWGui::ToolTips::getValueString(MWMechanics::calcSpellCost(*spell), "#{sCastCost}"); info.effects = effects; tooltipSize = createToolTip(info); } diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index 662cfe473b..d82b8505f1 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -68,7 +68,8 @@ namespace MWMechanics if (!(spell.mData.mFlags & ESM::Spell::F_Autocalc)) continue; static const int iAutoSpellTimesCanCast = gmst.find("iAutoSpellTimesCanCast")->mValue.getInteger(); - if (baseMagicka < iAutoSpellTimesCanCast * spell.mData.mCost) + int spellCost = MWMechanics::calcSpellCost(spell); + if (baseMagicka < iAutoSpellTimesCanCast * spellCost) continue; if (race && race->mPowers.exists(spell.mId)) @@ -83,7 +84,7 @@ namespace MWMechanics assert(school >= 0 && school < 6); SchoolCaps& cap = schoolCaps[school]; - if (cap.mReachedLimit && spell.mData.mCost <= cap.mMinCost) + if (cap.mReachedLimit && spellCost <= cap.mMinCost) continue; static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->mValue.getFloat(); @@ -102,6 +103,7 @@ namespace MWMechanics for (const std::string& testSpellName : selectedSpells) { const ESM::Spell* testSpell = spells.find(testSpellName); + int testSpellCost = MWMechanics::calcSpellCost(*testSpell); //int testSchool; //float dummySkillTerm; @@ -115,9 +117,9 @@ namespace MWMechanics // already erased it, and so the number of spells would often exceed the sum of limits. // This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested. //testSchool == school && - testSpell->mData.mCost < cap.mMinCost) + testSpellCost < cap.mMinCost) { - cap.mMinCost = testSpell->mData.mCost; + cap.mMinCost = testSpellCost; cap.mWeakestSpell = testSpell->mId; } } @@ -128,10 +130,10 @@ namespace MWMechanics if (cap.mCount == cap.mLimit) cap.mReachedLimit = true; - if (spell.mData.mCost < cap.mMinCost) + if (spellCost < cap.mMinCost) { cap.mWeakestSpell = spell.mId; - cap.mMinCost = spell.mData.mCost; + cap.mMinCost = spellCost; } } } @@ -159,11 +161,13 @@ namespace MWMechanics continue; if (!(spell.mData.mFlags & ESM::Spell::F_PCStart)) continue; - if (reachedLimit && spell.mData.mCost <= minCost) + + int spellCost = MWMechanics::calcSpellCost(spell); + if (reachedLimit && spellCost <= minCost) continue; if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell.mId) != race->mPowers.mList.end()) continue; - if (baseMagicka < spell.mData.mCost) + if (baseMagicka < spellCost) continue; static const float fAutoPCSpellChance = esmStore.get().find("fAutoPCSpellChance")->mValue.getFloat(); @@ -185,19 +189,20 @@ namespace MWMechanics for (const std::string& testSpellName : selectedSpells) { const ESM::Spell* testSpell = esmStore.get().find(testSpellName); - if (testSpell->mData.mCost < minCost) + int testSpellCost = MWMechanics::calcSpellCost(*testSpell); + if (testSpellCost < minCost) { - minCost = testSpell->mData.mCost; + minCost = testSpellCost; weakestSpell = testSpell; } } } else { - if (spell.mData.mCost < minCost) + if (spellCost < minCost) { weakestSpell = &spell; - minCost = weakestSpell->mData.mCost; + minCost = MWMechanics::calcSpellCost(*weakestSpell); } static const unsigned int iAutoPCSpellMax = esmStore.get().find("iAutoPCSpellMax")->mValue.getInteger(); if (selectedSpells.size() == iAutoPCSpellMax) @@ -291,7 +296,9 @@ namespace MWMechanics else calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this - float castChance = skillTerm - spell->mData.mCost + 0.2f * actorAttributes[ESM::Attribute::Willpower] + 0.1f * actorAttributes[ESM::Attribute::Luck]; + float castChance = skillTerm - MWMechanics::calcSpellCost(*spell) + + 0.2f * actorAttributes[ESM::Attribute::Willpower] + + 0.1f * actorAttributes[ESM::Attribute::Luck]; return castChance; } } diff --git a/apps/openmw/mwmechanics/spellabsorption.cpp b/apps/openmw/mwmechanics/spellabsorption.cpp index bab290fdab..cb80921b33 100644 --- a/apps/openmw/mwmechanics/spellabsorption.cpp +++ b/apps/openmw/mwmechanics/spellabsorption.cpp @@ -72,7 +72,7 @@ namespace MWMechanics int spellCost = 0; if (spell) { - spellCost = spell->mData.mCost; + spellCost = MWMechanics::calcSpellCost(*spell); } else { diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 5a367b5020..47225bc6f7 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -584,7 +584,7 @@ namespace MWMechanics DynamicStat fatigue = stats.getFatigue(); const float normalizedEncumbrance = mCaster.getClass().getNormalizedEncumbrance(mCaster); - float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); + float fatigueLoss = MWMechanics::calcSpellCost(*spell) * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult); fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); stats.setFatigue(fatigue); diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 0c667e680a..18a1c0004e 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -24,7 +24,7 @@ namespace MWMechanics return schoolSkillArray.at(school); } - float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect) + float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, const EffectCostMethod method) { const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); if (!magicEffect) @@ -39,14 +39,43 @@ namespace MWMechanics duration = std::max(1, duration); static const float fEffectCostMult = store.get().find("fEffectCostMult")->mValue.getFloat(); + int durationOffset = 0; + int minArea = 0; + if (method == EffectCostMethod::PlayerSpell) { + durationOffset = 1; + minArea = 1; + } + float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn)); x *= 0.1 * magicEffect->mData.mBaseCost; - x *= 1 + duration; - x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; + x *= durationOffset + duration; + x += 0.05 * std::max(minArea, effect.mArea) * magicEffect->mData.mBaseCost; return x * fEffectCostMult; } + int calcSpellCost (const ESM::Spell& spell) + { + 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; + } + + return std::round(cost); + } + int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) { /* @@ -97,7 +126,7 @@ namespace MWMechanics float actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); float actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified(); - float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck); + float castChance = (lowestSkill - calcSpellCost(*spell) + 0.2f * actorWillpower + 0.1f * actorLuck); return castChance; } @@ -123,7 +152,7 @@ namespace MWMechanics if (spell->mData.mType != ESM::Spell::ST_Spell) return 100; - if (checkMagicka && spell->mData.mCost > 0 && stats.getMagicka().getCurrent() < spell->mData.mCost) + if (checkMagicka && calcSpellCost(*spell) > 0 && stats.getMagicka().getCurrent() < calcSpellCost(*spell)) return 0; if (spell->mData.mFlags & ESM::Spell::F_Always) diff --git a/apps/openmw/mwmechanics/spellutil.hpp b/apps/openmw/mwmechanics/spellutil.hpp index 865a9126e7..81f39b6dda 100644 --- a/apps/openmw/mwmechanics/spellutil.hpp +++ b/apps/openmw/mwmechanics/spellutil.hpp @@ -19,7 +19,13 @@ namespace MWMechanics { ESM::Skill::SkillEnum spellSchoolToSkill(int school); - float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr); + enum class EffectCostMethod { + GameSpell, + PlayerSpell, + }; + + float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr, const EffectCostMethod method = EffectCostMethod::GameSpell); + int calcSpellCost (const ESM::Spell& spell); int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 45a7dd8a74..611e535c88 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -40,6 +40,7 @@ #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/spellcasting.hpp" +#include "../mwmechanics/spellutil.hpp" #include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/combat.hpp" #include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors @@ -3017,11 +3018,12 @@ namespace MWWorld if (!selectedSpell.empty()) { const ESM::Spell* spell = mStore.get().find(selectedSpell); + int spellCost = MWMechanics::calcSpellCost(*spell); // Check mana bool godmode = (isPlayer && mGodMode); MWMechanics::DynamicStat magicka = stats.getMagicka(); - if (spell->mData.mCost > 0 && magicka.getCurrent() < spell->mData.mCost && !godmode) + if (spellCost > 0 && magicka.getCurrent() < spellCost && !godmode) { message = "#{sMagicInsufficientSP}"; fail = true; @@ -3037,7 +3039,7 @@ namespace MWWorld // Reduce mana if (!fail && !godmode) { - magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); + magicka.setCurrent(magicka.getCurrent() - spellCost); stats.setMagicka(magicka); } }