1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-21 06:53:53 +00:00

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

Fix #5483 / Compute spell cost dynamically

Closes #5483

See merge request OpenMW/openmw!703
This commit is contained in:
Evil Eye 2021-06-20 16:00:02 +00:00
commit 4dad58b806
11 changed files with 72 additions and 26 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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