mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-30 03:32:36 +00:00
ce263af393
Remove WindowManager wrappers. It's not safe to use WindowManager in all places and it's not required. Environment stores resource system providing VFS required to call these functions. In the case of ObjectPaging it's available from the member variable. Also ObjectPaging::createChunk may access WindowManager when it's already destructed when exiting the game because it's destructed before CellPreloader finishes all background jobs. Engine::mResourceSystem is destructed after all other systems so it's safe to use it.
202 lines
7.5 KiB
C++
202 lines
7.5 KiB
C++
#include "spellutil.hpp"
|
|
|
|
#include <limits>
|
|
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
|
|
#include "../mwworld/class.hpp"
|
|
#include "../mwworld/esmstore.hpp"
|
|
|
|
#include "actorutil.hpp"
|
|
#include "creaturestats.hpp"
|
|
|
|
namespace MWMechanics
|
|
{
|
|
ESM::Skill::SkillEnum spellSchoolToSkill(int school)
|
|
{
|
|
static const std::array<ESM::Skill::SkillEnum, 6> schoolSkillArray
|
|
{
|
|
ESM::Skill::Alteration, ESM::Skill::Conjuration, ESM::Skill::Destruction,
|
|
ESM::Skill::Illusion, ESM::Skill::Mysticism, ESM::Skill::Restoration
|
|
};
|
|
return schoolSkillArray.at(school);
|
|
}
|
|
|
|
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect, const EffectCostMethod method)
|
|
{
|
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
|
if (!magicEffect)
|
|
magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
|
|
bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude);
|
|
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
|
bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce;
|
|
int minMagn = hasMagnitude ? effect.mMagnMin : 1;
|
|
int maxMagn = hasMagnitude ? effect.mMagnMax : 1;
|
|
int duration = hasDuration ? effect.mDuration : 1;
|
|
if (!appliedOnce)
|
|
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 *= 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)
|
|
{
|
|
/*
|
|
* Each point of enchant skill above/under 10 subtracts/adds
|
|
* one percent of enchantment cost while minimum is 1.
|
|
*/
|
|
int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant);
|
|
const float result = castCost - (castCost / 100) * (eSkill - 10);
|
|
|
|
return static_cast<int>((result < 1) ? 1 : result);
|
|
}
|
|
|
|
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool)
|
|
{
|
|
// Morrowind for some reason uses a formula slightly different from magicka cost calculation
|
|
float y = std::numeric_limits<float>::max();
|
|
float lowestSkill = 0;
|
|
|
|
for (const ESM::ENAMstruct& effect : spell->mEffects.mList)
|
|
{
|
|
float x = static_cast<float>(effect.mDuration);
|
|
const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
|
|
|
|
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce))
|
|
x = std::max(1.f, x);
|
|
|
|
x *= 0.1f * magicEffect->mData.mBaseCost;
|
|
x *= 0.5f * (effect.mMagnMin + effect.mMagnMax);
|
|
x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost;
|
|
if (effect.mRange == ESM::RT_Target)
|
|
x *= 1.5f;
|
|
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
|
"fEffectCostMult")->mValue.getFloat();
|
|
x *= fEffectCostMult;
|
|
|
|
float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
|
|
if (s - x < y)
|
|
{
|
|
y = s - x;
|
|
if (effectiveSchool)
|
|
*effectiveSchool = magicEffect->mData.mSchool;
|
|
lowestSkill = s;
|
|
}
|
|
}
|
|
|
|
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
|
|
float actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
|
float actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
|
|
|
float castChance = (lowestSkill - calcSpellCost(*spell) + 0.2f * actorWillpower + 0.1f * actorLuck);
|
|
|
|
return castChance;
|
|
}
|
|
|
|
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
|
{
|
|
// NB: Base chance is calculated here because the effective school pointer must be filled
|
|
float baseChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool);
|
|
|
|
bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
|
|
|
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
|
|
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() && !godmode)
|
|
return 0;
|
|
|
|
if (spell->mData.mType == ESM::Spell::ST_Power)
|
|
return stats.getSpells().canUsePower(spell) ? 100 : 0;
|
|
|
|
if (godmode)
|
|
return 100;
|
|
|
|
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
|
return 100;
|
|
|
|
if (checkMagicka && calcSpellCost(*spell) > 0 && stats.getMagicka().getCurrent() < calcSpellCost(*spell))
|
|
return 0;
|
|
|
|
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
|
return 100;
|
|
|
|
float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude();
|
|
float castChance = baseChance + castBonus;
|
|
castChance *= stats.getFatigueTerm();
|
|
|
|
if (cap)
|
|
return std::clamp(castChance, 0.f, 100.f);
|
|
|
|
return std::max(castChance, 0.f);
|
|
}
|
|
|
|
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
|
{
|
|
if (const auto spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId))
|
|
return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka);
|
|
return 0.f;
|
|
}
|
|
|
|
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
|
{
|
|
int school = 0;
|
|
getSpellSuccessChance(spellId, actor, &school);
|
|
return school;
|
|
}
|
|
|
|
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
|
{
|
|
int school = 0;
|
|
getSpellSuccessChance(spell, actor, &school);
|
|
return school;
|
|
}
|
|
|
|
bool spellIncreasesSkill(const ESM::Spell *spell)
|
|
{
|
|
return spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always);
|
|
}
|
|
|
|
bool spellIncreasesSkill(const std::string &spellId)
|
|
{
|
|
const auto spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId);
|
|
return spell && spellIncreasesSkill(spell);
|
|
}
|
|
}
|