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:
commit
4dad58b806
11 changed files with 72 additions and 26 deletions
|
@ -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…
Reference in a new issue