mirror of
https://github.com/OpenMW/openmw.git
synced 2025-03-03 02:09:41 +00:00
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.
This commit is contained in:
parent
6a9f2fdb17
commit
5c4e1252e9
11 changed files with 72 additions and 26 deletions
|
@ -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…
Reference in a new issue