Handle AutoCalc flag when getting spell cost

Fixes #5483

This only applies to "base game" spells.
When adding an AutoCalc spell with TES:CS its cost is computed and
stored inside game files. This stored cost was being used by OpenMW and
the actual cost was never recomputed at runtime whereas Morrowind.exe
discards the stored cost.
While this worked fine in vanilla, mods can update AutoCalc spell
effects without ever updating the stored cost.

The formula was mostly there already but there was a few differences,
namely a 1 second offset in duration.
pull/3094/head
Léo Peltier 4 years ago
parent 6a9f2fdb17
commit 5c4e1252e9

@ -118,6 +118,7 @@ Programmers
Kurnevsky Evgeny (kurnevsky) Kurnevsky Evgeny (kurnevsky)
Lars Söderberg (Lazaroth) Lars Söderberg (Lazaroth)
lazydev lazydev
Léo Peltier
Leon Krieg (lkrieg) Leon Krieg (lkrieg)
Leon Saunders (emoose) Leon Saunders (emoose)
logzero logzero

@ -3,6 +3,7 @@
Bug #3737: Scripts from The Underground 2 .esp do not play (all patched versions) 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 #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 0.47.0
------ ------

@ -456,7 +456,7 @@ namespace MWGui
for (const ESM::ENAMstruct& effect : mEffects) 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) if (effect.mRange == ESM::RT_Target)
y *= 1.5; y *= 1.5;

@ -109,7 +109,7 @@ namespace MWGui
if (spell->mData.mType == ESM::Spell::ST_Spell) if (spell->mData.mType == ESM::Spell::ST_Spell)
{ {
newSpell.mType = Spell::Type_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))); std::string chance = std::to_string(int(MWMechanics::getSpellSuccessChance(spell, mActor)));
newSpell.mCostColumn = cost + "/" + chance; newSpell.mCostColumn = cost + "/" + chance;
} }

@ -251,7 +251,7 @@ namespace MWGui
} }
std::string cost = focus->getUserString("SpellCost"); std::string cost = focus->getUserString("SpellCost");
if (cost != "" && cost != "0") 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; info.effects = effects;
tooltipSize = createToolTip(info); tooltipSize = createToolTip(info);
} }

@ -68,7 +68,8 @@ namespace MWMechanics
if (!(spell.mData.mFlags & ESM::Spell::F_Autocalc)) if (!(spell.mData.mFlags & ESM::Spell::F_Autocalc))
continue; continue;
static const int iAutoSpellTimesCanCast = gmst.find("iAutoSpellTimesCanCast")->mValue.getInteger(); 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; continue;
if (race && race->mPowers.exists(spell.mId)) if (race && race->mPowers.exists(spell.mId))
@ -83,7 +84,7 @@ namespace MWMechanics
assert(school >= 0 && school < 6); assert(school >= 0 && school < 6);
SchoolCaps& cap = schoolCaps[school]; SchoolCaps& cap = schoolCaps[school];
if (cap.mReachedLimit && spell.mData.mCost <= cap.mMinCost) if (cap.mReachedLimit && spellCost <= cap.mMinCost)
continue; continue;
static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->mValue.getFloat(); static const float fAutoSpellChance = gmst.find("fAutoSpellChance")->mValue.getFloat();
@ -102,6 +103,7 @@ namespace MWMechanics
for (const std::string& testSpellName : selectedSpells) for (const std::string& testSpellName : selectedSpells)
{ {
const ESM::Spell* testSpell = spells.find(testSpellName); const ESM::Spell* testSpell = spells.find(testSpellName);
int testSpellCost = MWMechanics::calcSpellCost(*testSpell);
//int testSchool; //int testSchool;
//float dummySkillTerm; //float dummySkillTerm;
@ -115,9 +117,9 @@ namespace MWMechanics
// already erased it, and so the number of spells would often exceed the sum of limits. // 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. // This bug cannot be fixed without significantly changing the results of the spell autocalc, which will not have been playtested.
//testSchool == school && //testSchool == school &&
testSpell->mData.mCost < cap.mMinCost) testSpellCost < cap.mMinCost)
{ {
cap.mMinCost = testSpell->mData.mCost; cap.mMinCost = testSpellCost;
cap.mWeakestSpell = testSpell->mId; cap.mWeakestSpell = testSpell->mId;
} }
} }
@ -128,10 +130,10 @@ namespace MWMechanics
if (cap.mCount == cap.mLimit) if (cap.mCount == cap.mLimit)
cap.mReachedLimit = true; cap.mReachedLimit = true;
if (spell.mData.mCost < cap.mMinCost) if (spellCost < cap.mMinCost)
{ {
cap.mWeakestSpell = spell.mId; cap.mWeakestSpell = spell.mId;
cap.mMinCost = spell.mData.mCost; cap.mMinCost = spellCost;
} }
} }
} }
@ -159,11 +161,13 @@ namespace MWMechanics
continue; continue;
if (!(spell.mData.mFlags & ESM::Spell::F_PCStart)) if (!(spell.mData.mFlags & ESM::Spell::F_PCStart))
continue; continue;
if (reachedLimit && spell.mData.mCost <= minCost)
int spellCost = MWMechanics::calcSpellCost(spell);
if (reachedLimit && spellCost <= minCost)
continue; continue;
if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell.mId) != race->mPowers.mList.end()) if (race && std::find(race->mPowers.mList.begin(), race->mPowers.mList.end(), spell.mId) != race->mPowers.mList.end())
continue; continue;
if (baseMagicka < spell.mData.mCost) if (baseMagicka < spellCost)
continue; continue;
static const float fAutoPCSpellChance = esmStore.get<ESM::GameSetting>().find("fAutoPCSpellChance")->mValue.getFloat(); static const float fAutoPCSpellChance = esmStore.get<ESM::GameSetting>().find("fAutoPCSpellChance")->mValue.getFloat();
@ -185,19 +189,20 @@ namespace MWMechanics
for (const std::string& testSpellName : selectedSpells) for (const std::string& testSpellName : selectedSpells)
{ {
const ESM::Spell* testSpell = esmStore.get<ESM::Spell>().find(testSpellName); const ESM::Spell* testSpell = esmStore.get<ESM::Spell>().find(testSpellName);
if (testSpell->mData.mCost < minCost) int testSpellCost = MWMechanics::calcSpellCost(*testSpell);
if (testSpellCost < minCost)
{ {
minCost = testSpell->mData.mCost; minCost = testSpellCost;
weakestSpell = testSpell; weakestSpell = testSpell;
} }
} }
} }
else else
{ {
if (spell.mData.mCost < minCost) if (spellCost < minCost)
{ {
weakestSpell = &spell; weakestSpell = &spell;
minCost = weakestSpell->mData.mCost; minCost = MWMechanics::calcSpellCost(*weakestSpell);
} }
static const unsigned int iAutoPCSpellMax = esmStore.get<ESM::GameSetting>().find("iAutoPCSpellMax")->mValue.getInteger(); static const unsigned int iAutoPCSpellMax = esmStore.get<ESM::GameSetting>().find("iAutoPCSpellMax")->mValue.getInteger();
if (selectedSpells.size() == iAutoPCSpellMax) if (selectedSpells.size() == iAutoPCSpellMax)
@ -291,7 +296,9 @@ namespace MWMechanics
else else
calcWeakestSchool(spell, actorSkills, effectiveSchool, skillTerm); // Note effectiveSchool is unused after this 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; return castChance;
} }
} }

@ -72,7 +72,7 @@ namespace MWMechanics
int spellCost = 0; int spellCost = 0;
if (spell) if (spell)
{ {
spellCost = spell->mData.mCost; spellCost = MWMechanics::calcSpellCost(*spell);
} }
else else
{ {

@ -584,7 +584,7 @@ namespace MWMechanics
DynamicStat<float> fatigue = stats.getFatigue(); DynamicStat<float> fatigue = stats.getFatigue();
const float normalizedEncumbrance = mCaster.getClass().getNormalizedEncumbrance(mCaster); 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); fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
stats.setFatigue(fatigue); stats.setFatigue(fatigue);

@ -24,7 +24,7 @@ namespace MWMechanics
return schoolSkillArray.at(school); 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(); const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
if (!magicEffect) if (!magicEffect)
@ -39,14 +39,43 @@ namespace MWMechanics
duration = std::max(1, duration); duration = std::max(1, duration);
static const float fEffectCostMult = store.get<ESM::GameSetting>().find("fEffectCostMult")->mValue.getFloat(); static const float fEffectCostMult = store.get<ESM::GameSetting>().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)); float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn));
x *= 0.1 * magicEffect->mData.mBaseCost; x *= 0.1 * magicEffect->mData.mBaseCost;
x *= 1 + duration; x *= durationOffset + duration;
x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost; x += 0.05 * std::max(minArea, effect.mArea) * magicEffect->mData.mBaseCost;
return x * fEffectCostMult; 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) int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor)
{ {
/* /*
@ -97,7 +126,7 @@ namespace MWMechanics
float actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified(); float actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float actorLuck = stats.getAttribute(ESM::Attribute::Luck).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; return castChance;
} }
@ -123,7 +152,7 @@ namespace MWMechanics
if (spell->mData.mType != ESM::Spell::ST_Spell) if (spell->mData.mType != ESM::Spell::ST_Spell)
return 100; 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; return 0;
if (spell->mData.mFlags & ESM::Spell::F_Always) if (spell->mData.mFlags & ESM::Spell::F_Always)

@ -19,7 +19,13 @@ namespace MWMechanics
{ {
ESM::Skill::SkillEnum spellSchoolToSkill(int school); 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); int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);

@ -40,6 +40,7 @@
#include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwmechanics/levelledlist.hpp" #include "../mwmechanics/levelledlist.hpp"
#include "../mwmechanics/combat.hpp" #include "../mwmechanics/combat.hpp"
#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors #include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
@ -3017,11 +3018,12 @@ namespace MWWorld
if (!selectedSpell.empty()) if (!selectedSpell.empty())
{ {
const ESM::Spell* spell = mStore.get<ESM::Spell>().find(selectedSpell); const ESM::Spell* spell = mStore.get<ESM::Spell>().find(selectedSpell);
int spellCost = MWMechanics::calcSpellCost(*spell);
// Check mana // Check mana
bool godmode = (isPlayer && mGodMode); bool godmode = (isPlayer && mGodMode);
MWMechanics::DynamicStat<float> magicka = stats.getMagicka(); MWMechanics::DynamicStat<float> magicka = stats.getMagicka();
if (spell->mData.mCost > 0 && magicka.getCurrent() < spell->mData.mCost && !godmode) if (spellCost > 0 && magicka.getCurrent() < spellCost && !godmode)
{ {
message = "#{sMagicInsufficientSP}"; message = "#{sMagicInsufficientSP}";
fail = true; fail = true;
@ -3037,7 +3039,7 @@ namespace MWWorld
// Reduce mana // Reduce mana
if (!fail && !godmode) if (!fail && !godmode)
{ {
magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost); magicka.setCurrent(magicka.getCurrent() - spellCost);
stats.setMagicka(magicka); stats.setMagicka(magicka);
} }
} }

Loading…
Cancel
Save