Merge branch 'fix5483-spell-cost' into 'master'

Fix #5483 / Compute spell cost dynamically

Closes #5483

See merge request OpenMW/openmw!703
pull/3094/head
Evil Eye 4 years ago
commit 4dad58b806

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