1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-25 15:35:23 +00:00

Merge pull request #2809 from Capostrophic/spellcasting

Refactor spellcasting header and implementation (task #5339)
This commit is contained in:
Bret Curtis 2020-05-05 17:17:13 +02:00 committed by GitHub
commit b4aeb2711c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 984 additions and 778 deletions

View File

@ -83,9 +83,10 @@ add_openmw_dir (mwclass
add_openmw_dir (mwmechanics
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellsuccess spellcasting
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype
character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype spellutil tickableeffects
spellabsorption linkedeffects
)
add_openmw_dir (mwstate

View File

@ -19,7 +19,7 @@
#include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/actorutil.hpp"

View File

@ -15,10 +15,10 @@
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spells.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "tooltips.hpp"
#include "class.hpp"

View File

@ -7,7 +7,7 @@
#include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"

View File

@ -18,7 +18,7 @@
#include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwmechanics/spells.hpp"
#include "../mwmechanics/creaturestats.hpp"
#include "../mwmechanics/actorutil.hpp"

View File

@ -17,7 +17,7 @@
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "mapwindow.hpp"

View File

@ -40,6 +40,7 @@
#include "summoning.hpp"
#include "combat.hpp"
#include "actorutil.hpp"
#include "tickableeffects.hpp"
namespace
{

View File

@ -14,7 +14,6 @@
#include "../mwworld/cellstore.hpp"
#include "npcstats.hpp"
#include "spellcasting.hpp"
#include "combat.hpp"
#include "weaponpriority.hpp"
#include "spellpriority.hpp"

View File

@ -1,5 +1,4 @@
#include "autocalcspell.hpp"
#include "spellcasting.hpp"
#include <limits>
@ -8,6 +7,7 @@
#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "spellutil.hpp"
namespace MWMechanics
{

View File

@ -18,6 +18,7 @@
#include "npcstats.hpp"
#include "movement.hpp"
#include "spellcasting.hpp"
#include "spellresistance.hpp"
#include "difficultyscaling.hpp"
#include "actorutil.hpp"
#include "pathfinding.hpp"

View File

@ -13,7 +13,7 @@
#include "../mwbase/mechanicsmanager.hpp"
#include "creaturestats.hpp"
#include "spellcasting.hpp"
#include "spellutil.hpp"
#include "actorutil.hpp"
#include "weapontype.hpp"

View File

@ -0,0 +1,74 @@
#include "linkedeffects.hpp"
#include <components/misc/rng.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwrender/animation.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "creaturestats.hpp"
namespace MWMechanics
{
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects)
{
if (caster.isEmpty() || caster == target || !target.getClass().isActor())
return false;
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable;
if (!isHarmful || isUnreflectable)
return false;
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
if (Misc::Rng::roll0to99() >= reflect)
return false;
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation && !reflectStatic->mModel.empty())
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
reflectedEffects.mList.emplace_back(effect);
return true;
}
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source)
{
if (caster.isEmpty() || caster == target)
return;
if (!target.getClass().isActor() || !caster.getClass().isActor())
return;
// Make sure callers don't do something weird
if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill)
throw std::runtime_error("invalid absorb stat effect");
if (appliedEffect.mMagnitude == 0)
return;
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
ActiveSpells::ActiveEffect absorbEffect = appliedEffect;
absorbEffect.mMagnitude *= -1;
absorbEffects.emplace_back(absorbEffect);
// Morrowind negates reflected Absorb spells so the original caster won't be harmed.
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
{
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true,
absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId());
return;
}
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true,
absorbEffects, source, target.getClass().getCreatureStats(target).getActorId());
}
}

View File

@ -0,0 +1,32 @@
#ifndef MWMECHANICS_LINKEDEFFECTS_H
#define MWMECHANICS_LINKEDEFFECTS_H
#include <string>
namespace ESM
{
struct ActiveEffect;
struct EffectList;
struct ENAMstruct;
struct MagicEffect;
struct Spell;
}
namespace MWWorld
{
class Ptr;
}
namespace MWMechanics
{
// Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list.
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects);
// Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster.
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source);
}
#endif

View File

@ -21,7 +21,7 @@
#include "aicombat.hpp"
#include "aipursue.hpp"
#include "spellcasting.hpp"
#include "spellutil.hpp"
#include "autocalcspell.hpp"
#include "npcstats.hpp"
#include "actorutil.hpp"
@ -376,7 +376,7 @@ namespace MWMechanics
{
const std::string& spell = winMgr->getSelectedSpell();
if (!spell.empty())
winMgr->setSelectedSpell(spell, int(MWMechanics::getSpellSuccessChance(spell, mWatched)));
winMgr->setSelectedSpell(spell, int(getSpellSuccessChance(spell, mWatched)));
else
winMgr->unsetSelectedSpell();
}

View File

@ -0,0 +1,76 @@
#include "spellabsorption.hpp"
#include <components/misc/rng.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwrender/animation.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "creaturestats.hpp"
namespace MWMechanics
{
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
{
public:
float mProbability{0.f};
GetAbsorptionProbability() = default;
virtual void visit (MWMechanics::EffectKey key,
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
float magnitude, float /*remainingTime*/, float /*totalTime*/)
{
if (key.mId == ESM::MagicEffect::SpellAbsorption)
{
if (mProbability == 0.f)
mProbability = magnitude / 100;
else
{
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
// Real absorption probability will be the (1 - total fail chance) in this case.
float failProbability = 1.f - mProbability;
failProbability *= 1.f - magnitude / 100;
mProbability = 1.f - failProbability;
}
}
}
};
bool absorbSpell (const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{
if (!spell || caster == target || !target.getClass().isActor())
return false;
CreatureStats& stats = target.getClass().getCreatureStats(target);
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f)
return false;
GetAbsorptionProbability check;
stats.getActiveSpells().visitEffectSources(check);
stats.getSpells().visitEffectSources(check);
if (target.getClass().hasInventoryStore(target))
target.getClass().getInventoryStore(target).visitEffectSources(check);
int chance = check.mProbability * 100;
if (Misc::Rng::roll0to99() >= chance)
return false;
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find("VFX_Absorb");
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation && !absorbStatic->mModel.empty())
animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string());
// Magicka is increased by the cost of the spell
DynamicStat<float> magicka = stats.getMagicka();
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
stats.setMagicka(magicka);
return true;
}
}

View File

@ -0,0 +1,20 @@
#ifndef MWMECHANICS_SPELLABSORPTION_H
#define MWMECHANICS_SPELLABSORPTION_H
namespace ESM
{
struct Spell;
}
namespace MWWorld
{
class Ptr;
}
namespace MWMechanics
{
// Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target.
bool absorbSpell(const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
}
#endif

View File

@ -1,11 +1,7 @@
#include "spellcasting.hpp"
#include <limits>
#include <iomanip>
#include <components/misc/constants.hpp>
#include <components/misc/rng.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
@ -23,293 +19,19 @@
#include "../mwrender/animation.hpp"
#include "npcstats.hpp"
#include "actorutil.hpp"
#include "aifollow.hpp"
#include "creaturestats.hpp"
#include "linkedeffects.hpp"
#include "spellabsorption.hpp"
#include "spellresistance.hpp"
#include "spellutil.hpp"
#include "summoning.hpp"
#include "tickableeffects.hpp"
#include "weapontype.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)
{
if (!magicEffect)
magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude);
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
int minMagn = hasMagnitude ? effect.mMagnMin : 1;
int maxMagn = hasMagnitude ? effect.mMagnMax : 1;
int duration = hasDuration ? effect.mDuration : 1;
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore()
.get<ESM::GameSetting>().find("fEffectCostMult")->mValue.getFloat();
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;
return x * fEffectCostMult;
}
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);
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck);
return castChance;
}
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
{
bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude();
float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus;
castChance *= stats.getFatigueTerm();
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 (spell->mData.mType != ESM::Spell::ST_Spell)
return 100;
if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost && !godmode)
return 0;
if (spell->mData.mFlags & ESM::Spell::F_Always)
return 100;
if (godmode)
return 100;
return std::max(0.f, cap ? std::min(100.f, castChance) : castChance);
}
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);
}
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects)
{
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
float resistance = 0;
if (resistanceEffect != -1)
resistance += actorEffects->get(resistanceEffect).getMagnitude();
if (weaknessEffect != -1)
resistance -= actorEffects->get(weaknessEffect).getMagnitude();
if (effectId == ESM::MagicEffect::FireDamage)
resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude();
if (effectId == ESM::MagicEffect::ShockDamage)
resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude();
if (effectId == ESM::MagicEffect::FrostDamage)
resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude();
return resistance;
}
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
const ESM::Spell* spell, const MagicEffects* effects)
{
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effectId);
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects();
if (effects)
magicEffects = effects;
// Effects with no resistance attribute belonging to them can not be resisted
if (ESM::MagicEffect::getResistanceEffect(effectId) == -1)
return 0.f;
float resistance = getEffectResistanceAttribute(effectId, magicEffects);
int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
float x = (willpower + 0.1f * luck) * stats.getFatigueTerm();
// This makes spells that are easy to cast harder to resist and vice versa
float castChance = 100.f;
if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor())
{
castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance
}
if (castChance > 0)
x *= 50 / castChance;
float roll = Misc::Rng::rollClosedProbability() * 100;
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
roll -= resistance;
if (x <= roll)
x = 0;
else
{
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
x = 100;
else
x = roll / std::min(x, 100.f);
}
x = std::min(x + resistance, 100.f);
return x;
}
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
const ESM::Spell* spell, const MagicEffects* effects)
{
float resistance = getEffectResistance(effectId, actor, caster, spell, effects);
return 1 - resistance / 100.f;
}
/// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure.
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer)
{
switch (effectId)
{
case ESM::MagicEffect::Levitate:
if (!MWBase::Environment::get().getWorld()->isLevitationEnabled())
{
if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
return false;
}
break;
case ESM::MagicEffect::Soultrap:
if (!target.getClass().isNpc() // no messagebox for NPCs
&& (target.getTypeName() == typeid(ESM::Creature).name() && target.get<ESM::Creature>()->mBase->mData.mSoul == 0))
{
if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}");
return true; // must still apply to get visual effect and have target regard it as attack
}
break;
case ESM::MagicEffect::WaterWalking:
if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target))
return false;
MWBase::World *world = MWBase::Environment::get().getWorld();
if (!world->isWaterWalkingCastableOnTarget(target))
{
if (castByPlayer && caster == target)
MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}");
return false;
}
break;
}
return true;
}
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
{
public:
float mProbability{0.f};
GetAbsorptionProbability() = default;
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, const std::string& sourceId, int casterActorId,
float magnitude, float remainingTime = -1, float totalTime = -1)
{
if (key.mId == ESM::MagicEffect::SpellAbsorption)
{
if (mProbability == 0.f)
mProbability = magnitude / 100;
else
{
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
// Real absorption probability will be the (1 - total fail chance) in this case.
float failProbability = 1.f - mProbability;
failProbability *= 1.f - magnitude / 100;
mProbability = 1.f - failProbability;
}
}
}
};
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
: mCaster(caster)
, mTarget(target)
@ -391,6 +113,9 @@ namespace MWMechanics
// throughout the iteration of this spell's
// effects, we display a "can't re-cast" message
// Try absorbing the spell. Some handling must still happen for absorbed effects.
bool absorbed = absorbSpell(spell, caster, target);
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt)
{
@ -418,89 +143,30 @@ namespace MWMechanics
&& (caster.isEmpty() || !caster.getClass().isActor()))
continue;
// If player is healing someone, show the target's HP bar
if (castByPlayer && target != caster
&& effectIt->mEffectID == ESM::MagicEffect::RestoreHealth
&& target.getClass().isActor())
MWBase::Environment::get().getWindowManager()->setEnemy(target);
// Notify the target actor they've been hit
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
// Try absorbing if it's a spell
// Unlike Reflect, this is done once per spell absorption effect source
bool absorbed = false;
if (spell && caster != target && target.getClass().isActor())
// Avoid proceeding further for absorbed spells.
if (absorbed)
continue;
// Reflect harmful effects
if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects))
continue;
// Try resisting.
float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects);
if (magnitudeMult == 0)
{
CreatureStats& stats = target.getClass().getCreatureStats(target);
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f)
{
GetAbsorptionProbability check;
stats.getActiveSpells().visitEffectSources(check);
stats.getSpells().visitEffectSources(check);
if (target.getClass().hasInventoryStore(target))
target.getClass().getInventoryStore(target).visitEffectSources(check);
int absorb = check.mProbability * 100;
absorbed = (Misc::Rng::roll0to99() < absorb);
if (absorbed)
{
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, "");
// Magicka is increased by cost of spell
DynamicStat<float> magicka = stats.getMagicka();
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
stats.setMagicka(magicka);
}
}
// Fully resisted, show message
if (target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
else if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
}
float magnitudeMult = 1;
if (target.getClass().isActor())
{
if (absorbed)
continue;
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
// Reflect harmful effects
if (isHarmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
{
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
bool isReflected = (Misc::Rng::roll0to99() < reflect);
if (isReflected)
{
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
"meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, "");
reflectedEffects.mList.push_back(*effectIt);
continue;
}
}
// Try resisting
magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects);
if (magnitudeMult == 0)
{
// Fully resisted, show message
if (target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
else if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
}
else if (isHarmful && castByPlayer && target != caster)
{
// If player is attempting to cast a harmful spell and it wasn't fully resisted, show the target's HP bar
MWBase::Environment::get().getWindowManager()->setEnemy(target);
}
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
magnitudeMult = 0;
// Notify the target actor they've been hit
if (target != caster && !caster.isEmpty() && isHarmful)
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
}
if (magnitudeMult > 0 && !absorbed)
else
{
float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1);
magnitude *= magnitudeMult;
@ -527,6 +193,19 @@ namespace MWMechanics
effect.mMagnitude = 0;
}
// Avoid applying harmful effects to the player in god mode
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
{
effect.mMagnitude = 0;
}
bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
if (castByPlayer && target != caster && effectAffectsHealth)
{
// If player is attempting to cast a harmful spell or is healing someone, show the target's HP bar.
MWBase::Environment::get().getWindowManager()->setEnemy(target);
}
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
if (hasDuration && effectIt->mDuration == 0)
{
@ -536,7 +215,7 @@ namespace MWMechanics
// duration 0 means apply full magnitude instantly
bool wasDead = target.getClass().getCreatureStats(target).isDead();
effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude);
effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), effect.mMagnitude);
bool isDead = target.getClass().getCreatureStats(target).isDead();
if (!wasDead && isDead)
@ -561,7 +240,7 @@ namespace MWMechanics
// Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target
if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc())
|| (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name()))
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel())
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel())
{
MWMechanics::AiFollow package(caster, true);
target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
@ -569,23 +248,8 @@ namespace MWMechanics
// For absorb effects, also apply the effect to the caster - but with a negative
// magnitude, since we're transferring stats from the target to the caster
if (!caster.isEmpty() && caster != target && caster.getClass().isActor())
{
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute &&
effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
{
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
ActiveSpells::ActiveEffect effect_ = effect;
effect_.mMagnitude *= -1;
absorbEffects.push_back(effect_);
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true,
absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId());
else
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true,
absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId());
}
}
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
absorbStat(*effectIt, effect, caster, target, reflected, mSourceName);
}
}
@ -767,16 +431,14 @@ namespace MWMechanics
bool CastSpell::cast(const std::string &id)
{
if (const ESM::Spell *spell =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
if (const auto spell = store.get<ESM::Spell>().search(id))
return cast(spell);
if (const ESM::Potion *potion =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
if (const auto potion = store.get<ESM::Potion>().search(id))
return cast(potion);
if (const ESM::Ingredient *ingredient =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
if (const auto ingredient = store.get<ESM::Ingredient>().search(id))
return cast(ingredient);
throw std::runtime_error("ID type cannot be casted");
@ -939,10 +601,9 @@ namespace MWMechanics
stats.getSpells().usePower(spell);
}
if (mCaster == getPlayer() && spellIncreasesSkill())
mCaster.getClass().skillUsageSucceeded(mCaster,
spellSchoolToSkill(school), 0);
if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0);
// A non-actor doesn't play its spell cast effects from a character controller, so play them here
if (!mCaster.getClass().isActor())
playSpellCastingEffects(spell->mEffects.mList);
@ -970,10 +631,8 @@ namespace MWMechanics
effect.mRange = ESM::RT_Self;
effect.mArea = 0;
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
effect.mEffectID);
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
const auto magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) +
@ -985,7 +644,7 @@ namespace MWMechanics
if (roll > x)
{
// "X has no effect on you"
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage50")->mValue.getString();
std::string message = store.get<ESM::GameSetting>().find("sNotifyMessage50")->mValue.getString();
message = Misc::StringUtils::format(message, ingredient->mName);
MWBase::Environment::get().getWindowManager()->messageBox(message);
return false;
@ -1086,280 +745,4 @@ namespace MWMechanics
sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f);
}
}
bool CastSpell::spellIncreasesSkill()
{
return !mManualSpell && MWMechanics::spellIncreasesSkill(mId);
}
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);
}
bool isSummoningEffect(int effectId)
{
return ((effectId >= ESM::MagicEffect::SummonScamp
&& effectId <= ESM::MagicEffect::SummonStormAtronach)
|| effectId == ESM::MagicEffect::SummonCenturionSphere
|| (effectId >= ESM::MagicEffect::SummonFabricant
&& effectId <= ESM::MagicEffect::SummonCreature05));
}
bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
{
if (ptr.getClass().hasInventoryStore(ptr))
{
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
MWWorld::ContainerStoreIterator item = inv.getSlot(slot);
if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon))
{
if (!item->getClass().hasItemHealth(*item))
return false;
int charge = item->getClass().getItemHealth(*item);
if (charge == 0)
return false;
// Store remainder of disintegrate amount (automatically subtracted if > 1)
item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));
charge = item->getClass().getItemHealth(*item);
charge -= std::min(static_cast<int>(disintegrate), charge);
item->getCellRef().setCharge(charge);
if (charge == 0)
{
// Will unequip the broken item and try to find a replacement
if (ptr != getPlayer())
inv.autoEquip(ptr);
else
inv.unequipItem(*item, ptr);
}
return true;
}
}
return false;
}
void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false)
{
DynamicStat<float> stat = creatureStats.getDynamic(index);
stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero);
creatureStats.setDynamic(index, stat);
}
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
{
if (magnitude == 0.f)
return false;
bool receivedMagicDamage = false;
switch (effectKey.mId)
{
case ESM::MagicEffect::DamageAttribute:
{
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
attr.damage(magnitude);
creatureStats.setAttribute(effectKey.mArg, attr);
break;
}
case ESM::MagicEffect::RestoreAttribute:
{
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
attr.restore(magnitude);
creatureStats.setAttribute(effectKey.mArg, attr);
break;
}
case ESM::MagicEffect::RestoreHealth:
case ESM::MagicEffect::RestoreMagicka:
case ESM::MagicEffect::RestoreFatigue:
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
break;
case ESM::MagicEffect::DamageHealth:
receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
break;
case ESM::MagicEffect::DamageMagicka:
case ESM::MagicEffect::DamageFatigue:
{
int index = effectKey.mId-ESM::MagicEffect::DamageHealth;
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue);
break;
}
case ESM::MagicEffect::AbsorbHealth:
if (magnitude > 0.f)
receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
break;
case ESM::MagicEffect::AbsorbMagicka:
case ESM::MagicEffect::AbsorbFatigue:
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
break;
case ESM::MagicEffect::DisintegrateArmor:
{
// According to UESP
int priorities[] = {
MWWorld::InventoryStore::Slot_CarriedLeft,
MWWorld::InventoryStore::Slot_Cuirass,
MWWorld::InventoryStore::Slot_LeftPauldron,
MWWorld::InventoryStore::Slot_RightPauldron,
MWWorld::InventoryStore::Slot_LeftGauntlet,
MWWorld::InventoryStore::Slot_RightGauntlet,
MWWorld::InventoryStore::Slot_Helmet,
MWWorld::InventoryStore::Slot_Greaves,
MWWorld::InventoryStore::Slot_Boots
};
for (unsigned int i=0; i<sizeof(priorities)/sizeof(int); ++i)
{
if (disintegrateSlot(actor, priorities[i], magnitude))
break;
}
break;
}
case ESM::MagicEffect::DisintegrateWeapon:
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
break;
case ESM::MagicEffect::SunDamage:
{
// isInCell shouldn't be needed, but updateActor called during game start
if (!actor.isInCell() || !actor.getCell()->isExterior())
break;
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
float damageScale = 1.f - timeDiff / 7.f;
// When cloudy, the sun damage effect is halved
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fMagicSunBlockedMult")->mValue.getFloat();
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
if (weather > 1)
damageScale *= fMagicSunBlockedMult;
adjustDynamicStat(creatureStats, 0, -magnitude * damageScale);
if (magnitude * damageScale > 0.f)
receivedMagicDamage = true;
break;
}
case ESM::MagicEffect::FireDamage:
case ESM::MagicEffect::ShockDamage:
case ESM::MagicEffect::FrostDamage:
case ESM::MagicEffect::Poison:
{
adjustDynamicStat(creatureStats, 0, -magnitude);
receivedMagicDamage = true;
break;
}
case ESM::MagicEffect::DamageSkill:
case ESM::MagicEffect::RestoreSkill:
{
if (!actor.getClass().isNpc())
break;
NpcStats &npcStats = actor.getClass().getNpcStats(actor);
SkillValue& skill = npcStats.getSkill(effectKey.mArg);
if (effectKey.mId == ESM::MagicEffect::RestoreSkill)
skill.restore(magnitude);
else
skill.damage(magnitude);
break;
}
case ESM::MagicEffect::CurePoison:
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
break;
case ESM::MagicEffect::CureParalyzation:
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
break;
case ESM::MagicEffect::CureCommonDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease();
break;
case ESM::MagicEffect::CureBlightDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease();
break;
case ESM::MagicEffect::CureCorprusDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease();
break;
case ESM::MagicEffect::RemoveCurse:
actor.getClass().getCreatureStats(actor).getSpells().purgeCurses();
break;
default:
return false;
}
if (receivedMagicDamage && actor == getPlayer())
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
return true;
}
std::string getSummonedCreature(int effectId)
{
static const std::map<int, std::string> summonMap
{
{ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"},
{ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"},
{ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"},
{ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"},
{ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"},
{ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"},
{ESM::MagicEffect::SummonDremora, "sMagicDremoraID"},
{ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"},
{ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"},
{ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"},
{ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"},
{ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"},
{ESM::MagicEffect::SummonHunger, "sMagicHungerID"},
{ESM::MagicEffect::SummonScamp, "sMagicScampID"},
{ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"},
{ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"},
{ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"},
{ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"},
{ESM::MagicEffect::SummonBear, "sMagicCreature02ID"},
{ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"},
{ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"},
{ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"}
};
auto it = summonMap.find(effectId);
if (it != summonMap.end())
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(it->second)->mValue.getString();
return std::string();
}
void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key,
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
float /*magnitude*/, float /*remainingTime*/, float /*totalTime*/)
{
const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(key.mId);
if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0)
return;
const ESM::Static* castStatic;
if (!magicEffect->mHit.empty())
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
else
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_DefaultHit");
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor);
if (anim && !castStatic->mModel.empty())
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle);
}
}

View File

@ -1,14 +1,11 @@
#ifndef MWMECHANICS_SPELLSUCCESS_H
#define MWMECHANICS_SPELLSUCCESS_H
#ifndef MWMECHANICS_SPELLCASTING_H
#define MWMECHANICS_SPELLCASTING_H
#include <components/esm/effectlist.hpp>
#include <components/esm/loadskil.hpp>
#include <components/esm/loadmgef.hpp>
#include "../mwworld/ptr.hpp"
#include "magiceffects.hpp"
namespace ESM
{
struct Spell;
@ -20,62 +17,6 @@ namespace ESM
namespace MWMechanics
{
struct EffectKey;
class MagicEffects;
class CreatureStats;
ESM::Skill::SkillEnum spellSchoolToSkill(int school);
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr);
bool isSummoningEffect(int effectId);
/**
* @param spell spell to cast
* @param actor calculate spell success chance for this actor (depends on actor's skills)
* @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here
* @param cap cap the result to 100%?
* @param checkMagicka check magicka?
* @note actor can be an NPC or a creature
* @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned.
*/
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
/// Get whether or not the given spell contributes to skill progress.
bool spellIncreasesSkill(const ESM::Spell* spell);
bool spellIncreasesSkill(const std::string& spellId);
/// Get the resistance attribute against an effect for a given actor. This will add together
/// ResistX and Weakness to X effects relevant against the given effect.
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects);
/// Get the effective resistance against an effect casted by the given actor in the given spell (optional).
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
/// being applied (but not applied yet) that should also be considered.
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
/// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional).
/// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness)
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
/// being applied (but not applied yet) that should also be considered.
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer);
int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool);
/// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
/// @return Was the effect a tickable effect with a magnitude?
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude);
std::string getSummonedCreature(int effectId);
class CastSpell
{
@ -113,8 +54,6 @@ namespace MWMechanics
void playSpellCastingEffects(const std::string &spellid, bool enchantment);
bool spellIncreasesSkill();
/// Launch a bolt with the given effects.
void launchMagicBolt ();
@ -127,22 +66,6 @@ namespace MWMechanics
/// @return was the target suitable for the effect?
bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude);
};
class ApplyLoopingParticlesVisitor : public EffectSourceVisitor
{
private:
MWWorld::Ptr mActor;
public:
ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor)
: mActor(actor)
{
}
virtual void visit (MWMechanics::EffectKey key,
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1);
};
}
#endif

View File

@ -15,9 +15,11 @@
#include "../mwworld/cellstore.hpp"
#include "creaturestats.hpp"
#include "spellcasting.hpp"
#include "spellresistance.hpp"
#include "weapontype.hpp"
#include "combat.hpp"
#include "summoning.hpp"
#include "spellutil.hpp"
namespace
{

View File

@ -0,0 +1,93 @@
#include "spellresistance.hpp"
#include <components/misc/rng.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "creaturestats.hpp"
#include "spellutil.hpp"
namespace MWMechanics
{
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
const ESM::Spell* spell, const MagicEffects* effects)
{
if (!actor.getClass().isActor())
return 1;
float resistance = getEffectResistance(effectId, actor, caster, spell, effects);
return 1 - resistance / 100.f;
}
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
const ESM::Spell* spell, const MagicEffects* effects)
{
// Effects with no resistance attribute belonging to them can not be resisted
if (ESM::MagicEffect::getResistanceEffect(effectId) == -1)
return 0.f;
const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectId);
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects();
if (effects)
magicEffects = effects;
float resistance = getEffectResistanceAttribute(effectId, magicEffects);
int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
float x = (willpower + 0.1f * luck) * stats.getFatigueTerm();
// This makes spells that are easy to cast harder to resist and vice versa
float castChance = 100.f;
if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor())
castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance
if (castChance > 0)
x *= 50 / castChance;
float roll = Misc::Rng::rollClosedProbability() * 100;
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
roll -= resistance;
if (x <= roll)
x = 0;
else
{
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
x = 100;
else
x = roll / std::min(x, 100.f);
}
x = std::min(x + resistance, 100.f);
return x;
}
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects)
{
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
float resistance = 0;
if (resistanceEffect != -1)
resistance += actorEffects->get(resistanceEffect).getMagnitude();
if (weaknessEffect != -1)
resistance -= actorEffects->get(weaknessEffect).getMagnitude();
if (effectId == ESM::MagicEffect::FireDamage)
resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude();
if (effectId == ESM::MagicEffect::ShockDamage)
resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude();
if (effectId == ESM::MagicEffect::FrostDamage)
resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude();
return resistance;
}
}

View File

@ -0,0 +1,37 @@
#ifndef MWMECHANICS_SPELLRESISTANCE_H
#define MWMECHANICS_SPELLRESISTANCE_H
namespace ESM
{
struct Spell;
}
namespace MWWorld
{
class Ptr;
}
namespace MWMechanics
{
class MagicEffects;
/// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional).
/// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness)
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
/// being applied (but not applied yet) that should also be considered.
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
/// Get the effective resistance against an effect casted by the given actor in the given spell (optional).
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
/// being applied (but not applied yet) that should also be considered.
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
/// Get the resistance attribute against an effect for a given actor. This will add together
/// ResistX and Weakness to X effects relevant against the given effect.
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects);
}
#endif

View File

@ -0,0 +1,208 @@
#include "spellutil.hpp"
#include <limits>
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.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 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);
int minMagn = hasMagnitude ? effect.mMagnMin : 1;
int maxMagn = hasMagnitude ? effect.mMagnMax : 1;
int duration = hasDuration ? effect.mDuration : 1;
static const float fEffectCostMult = store.get<ESM::GameSetting>().find("fEffectCostMult")->mValue.getFloat();
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;
return x * fEffectCostMult;
}
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);
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck);
return castChance;
}
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
{
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 && stats.getMagicka().getCurrent() < spell->mData.mCost)
return 0;
if (spell->mData.mFlags & ESM::Spell::F_Always)
return 100;
float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude();
float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus;
castChance *= stats.getFatigueTerm();
return std::max(0.f, cap ? std::min(100.f, castChance) : castChance);
}
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);
}
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer)
{
switch (effectId)
{
case ESM::MagicEffect::Levitate:
{
if (!MWBase::Environment::get().getWorld()->isLevitationEnabled())
{
if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
return false;
}
break;
}
case ESM::MagicEffect::Soultrap:
{
if (!target.getClass().isNpc() // no messagebox for NPCs
&& (target.getTypeName() == typeid(ESM::Creature).name() && target.get<ESM::Creature>()->mBase->mData.mSoul == 0))
{
if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}");
return true; // must still apply to get visual effect and have target regard it as attack
}
break;
}
case ESM::MagicEffect::WaterWalking:
{
if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target))
return false;
MWBase::World *world = MWBase::Environment::get().getWorld();
if (!world->isWaterWalkingCastableOnTarget(target))
{
if (castByPlayer && caster == target)
MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}");
return false;
}
break;
}
}
return true;
}
}

View File

@ -0,0 +1,50 @@
#ifndef MWMECHANICS_SPELLUTIL_H
#define MWMECHANICS_SPELLUTIL_H
#include <components/esm/loadskil.hpp>
namespace ESM
{
struct ENAMstruct;
struct MagicEffect;
struct Spell;
}
namespace MWWorld
{
class Ptr;
}
namespace MWMechanics
{
ESM::Skill::SkillEnum spellSchoolToSkill(int school);
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr);
int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);
/**
* @param spell spell to cast
* @param actor calculate spell success chance for this actor (depends on actor's skills)
* @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here
* @param cap cap the result to 100%?
* @param checkMagicka check magicka?
* @note actor can be an NPC or a creature
* @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned.
*/
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool);
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
/// Get whether or not the given spell contributes to skill progress.
bool spellIncreasesSkill(const ESM::Spell* spell);
bool spellIncreasesSkill(const std::string& spellId);
/// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure.
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer);
}
#endif

View File

@ -13,20 +13,55 @@
#include "../mwrender/animation.hpp"
#include "spellcasting.hpp"
#include "creaturestats.hpp"
#include "aifollow.hpp"
namespace MWMechanics
{
UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor)
: mActor(actor)
bool isSummoningEffect(int effectId)
{
return ((effectId >= ESM::MagicEffect::SummonScamp && effectId <= ESM::MagicEffect::SummonStormAtronach)
|| (effectId == ESM::MagicEffect::SummonCenturionSphere)
|| (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05));
}
UpdateSummonedCreatures::~UpdateSummonedCreatures()
std::string getSummonedCreature(int effectId)
{
static const std::map<int, std::string> summonMap
{
{ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"},
{ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"},
{ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"},
{ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"},
{ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"},
{ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"},
{ESM::MagicEffect::SummonDremora, "sMagicDremoraID"},
{ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"},
{ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"},
{ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"},
{ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"},
{ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"},
{ESM::MagicEffect::SummonHunger, "sMagicHungerID"},
{ESM::MagicEffect::SummonScamp, "sMagicScampID"},
{ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"},
{ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"},
{ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"},
{ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"},
{ESM::MagicEffect::SummonBear, "sMagicCreature02ID"},
{ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"},
{ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"},
{ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"}
};
auto it = summonMap.find(effectId);
if (it != summonMap.end())
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(it->second)->mValue.getString();
return std::string();
}
UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor)
: mActor(actor)
{
}

View File

@ -9,13 +9,16 @@
namespace MWMechanics
{
class CreatureStats;
bool isSummoningEffect(int effectId);
std::string getSummonedCreature(int effectId);
struct UpdateSummonedCreatures : public EffectSourceVisitor
{
UpdateSummonedCreatures(const MWWorld::Ptr& actor);
virtual ~UpdateSummonedCreatures();
virtual ~UpdateSummonedCreatures() = default;
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, const std::string& sourceId, int casterActorId,

View File

@ -0,0 +1,217 @@
#include "tickableeffects.hpp"
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "actorutil.hpp"
#include "npcstats.hpp"
namespace MWMechanics
{
void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false)
{
DynamicStat<float> stat = creatureStats.getDynamic(index);
stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero);
creatureStats.setDynamic(index, stat);
}
bool disintegrateSlot (const MWWorld::Ptr& ptr, int slot, float disintegrate)
{
if (!ptr.getClass().hasInventoryStore(ptr))
return false;
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
MWWorld::ContainerStoreIterator item = inv.getSlot(slot);
if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon))
{
if (!item->getClass().hasItemHealth(*item))
return false;
int charge = item->getClass().getItemHealth(*item);
if (charge == 0)
return false;
// Store remainder of disintegrate amount (automatically subtracted if > 1)
item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));
charge = item->getClass().getItemHealth(*item);
charge -= std::min(static_cast<int>(disintegrate), charge);
item->getCellRef().setCharge(charge);
if (charge == 0)
{
// Will unequip the broken item and try to find a replacement
if (ptr != getPlayer())
inv.autoEquip(ptr);
else
inv.unequipItem(*item, ptr);
}
return true;
}
return false;
}
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
{
if (magnitude == 0.f)
return false;
bool receivedMagicDamage = false;
switch (effectKey.mId)
{
case ESM::MagicEffect::DamageAttribute:
{
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
attr.damage(magnitude);
creatureStats.setAttribute(effectKey.mArg, attr);
break;
}
case ESM::MagicEffect::RestoreAttribute:
{
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
attr.restore(magnitude);
creatureStats.setAttribute(effectKey.mArg, attr);
break;
}
case ESM::MagicEffect::RestoreHealth:
case ESM::MagicEffect::RestoreMagicka:
case ESM::MagicEffect::RestoreFatigue:
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
break;
case ESM::MagicEffect::DamageHealth:
receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
break;
case ESM::MagicEffect::DamageMagicka:
case ESM::MagicEffect::DamageFatigue:
{
int index = effectKey.mId-ESM::MagicEffect::DamageHealth;
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue);
break;
}
case ESM::MagicEffect::AbsorbHealth:
if (magnitude > 0.f)
receivedMagicDamage = true;
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
break;
case ESM::MagicEffect::AbsorbMagicka:
case ESM::MagicEffect::AbsorbFatigue:
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
break;
case ESM::MagicEffect::DisintegrateArmor:
{
static const std::array<int, 9> priorities
{
MWWorld::InventoryStore::Slot_CarriedLeft,
MWWorld::InventoryStore::Slot_Cuirass,
MWWorld::InventoryStore::Slot_LeftPauldron,
MWWorld::InventoryStore::Slot_RightPauldron,
MWWorld::InventoryStore::Slot_LeftGauntlet,
MWWorld::InventoryStore::Slot_RightGauntlet,
MWWorld::InventoryStore::Slot_Helmet,
MWWorld::InventoryStore::Slot_Greaves,
MWWorld::InventoryStore::Slot_Boots
};
for (const int priority : priorities)
{
if (disintegrateSlot(actor, priority, magnitude))
break;
}
break;
}
case ESM::MagicEffect::DisintegrateWeapon:
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
break;
case ESM::MagicEffect::SunDamage:
{
// isInCell shouldn't be needed, but updateActor called during game start
if (!actor.isInCell() || !actor.getCell()->isExterior())
break;
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
float damageScale = 1.f - timeDiff / 7.f;
// When cloudy, the sun damage effect is halved
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
"fMagicSunBlockedMult")->mValue.getFloat();
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
if (weather > 1)
damageScale *= fMagicSunBlockedMult;
adjustDynamicStat(creatureStats, 0, -magnitude * damageScale);
if (magnitude * damageScale > 0.f)
receivedMagicDamage = true;
break;
}
case ESM::MagicEffect::FireDamage:
case ESM::MagicEffect::ShockDamage:
case ESM::MagicEffect::FrostDamage:
case ESM::MagicEffect::Poison:
{
adjustDynamicStat(creatureStats, 0, -magnitude);
receivedMagicDamage = true;
break;
}
case ESM::MagicEffect::DamageSkill:
case ESM::MagicEffect::RestoreSkill:
{
if (!actor.getClass().isNpc())
break;
NpcStats &npcStats = actor.getClass().getNpcStats(actor);
SkillValue& skill = npcStats.getSkill(effectKey.mArg);
if (effectKey.mId == ESM::MagicEffect::RestoreSkill)
skill.restore(magnitude);
else
skill.damage(magnitude);
break;
}
case ESM::MagicEffect::CurePoison:
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
break;
case ESM::MagicEffect::CureParalyzation:
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
break;
case ESM::MagicEffect::CureCommonDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease();
break;
case ESM::MagicEffect::CureBlightDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease();
break;
case ESM::MagicEffect::CureCorprusDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease();
break;
case ESM::MagicEffect::RemoveCurse:
actor.getClass().getCreatureStats(actor).getSpells().purgeCurses();
break;
default:
return false;
}
if (receivedMagicDamage && actor == getPlayer())
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
return true;
}
}

View File

@ -0,0 +1,19 @@
#ifndef MWMECHANICS_TICKABLEEFFECTS_H
#define MWMECHANICS_TICKABLEEFFECTS_H
namespace MWWorld
{
class Ptr;
}
namespace MWMechanics
{
class CreatureStats;
struct EffectKey;
/// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
/// @return Was the effect a tickable effect with a magnitude?
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude);
}
#endif

View File

@ -13,7 +13,7 @@
#include "combat.hpp"
#include "aicombataction.hpp"
#include "spellpriority.hpp"
#include "spellcasting.hpp"
#include "spellutil.hpp"
#include "weapontype.hpp"
namespace MWMechanics

View File

@ -13,7 +13,8 @@
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spellresistance.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/weapontype.hpp"

View File

@ -20,7 +20,7 @@
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "class.hpp"
#include "ptr.hpp"

View File

@ -41,6 +41,7 @@
#include "../mwmechanics/levelledlist.hpp"
#include "../mwmechanics/combat.hpp"
#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
#include "../mwmechanics/summoning.hpp"
#include "../mwrender/animation.hpp"
#include "../mwrender/npcanimation.hpp"
@ -3160,12 +3161,42 @@ namespace MWWorld
mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection);
}
class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor
{
private:
MWWorld::Ptr mActor;
public:
ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor)
: mActor(actor)
{
}
virtual void visit (MWMechanics::EffectKey key,
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1)
{
const ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
const auto magicEffect = store.get<ESM::MagicEffect>().find(key.mId);
if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0)
return;
const ESM::Static* castStatic;
if (!magicEffect->mHit.empty())
castStatic = store.get<ESM::Static>().find (magicEffect->mHit);
else
castStatic = store.get<ESM::Static>().find ("VFX_DefaultHit");
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor);
if (anim && !castStatic->mModel.empty())
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle);
}
};
void World::applyLoopingParticles(const MWWorld::Ptr& ptr)
{
const MWWorld::Class &cls = ptr.getClass();
if (cls.isActor())
{
MWMechanics::ApplyLoopingParticlesVisitor visitor(ptr);
ApplyLoopingParticlesVisitor visitor(ptr);
cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor);
cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor);
if (cls.hasInventoryStore(ptr))