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)
Lars Söderberg (Lazaroth)
lazydev
Léo Peltier
Leon Krieg (lkrieg)
Leon Saunders (emoose)
logzero

@ -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
------

@ -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;

@ -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;
}

@ -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);
}

@ -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<ESM::GameSetting>().find("fAutoPCSpellChance")->mValue.getFloat();
@ -185,19 +189,20 @@ namespace MWMechanics
for (const std::string& testSpellName : selectedSpells)
{
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;
}
}
}
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<ESM::GameSetting>().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;
}
}

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

@ -584,7 +584,7 @@ namespace MWMechanics
DynamicStat<float> 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);

@ -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<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));
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)

@ -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);

@ -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<ESM::Spell>().find(selectedSpell);
int spellCost = MWMechanics::calcSpellCost(*spell);
// Check mana
bool godmode = (isPlayer && mGodMode);
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}";
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);
}
}

Loading…
Cancel
Save