Merge branch 'puddle' into 'master'

Give each reflect and spell absorption effect a chance to apply

Closes #6255 and #6253

See merge request OpenMW/openmw!1279
pull/3210/head
psi29a 3 years ago
commit 4c81518abb

@ -48,6 +48,8 @@
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
Bug #6197: Infinite Casting Loop Bug #6197: Infinite Casting Loop
Bug #6253: Multiple instances of Reflect stack additively
Bug #6255: Reflect is different from vanilla
Bug #6258: Barter menu glitches out when modifying prices Bug #6258: Barter menu glitches out when modifying prices
Bug #6273: Respawning NPCs rotation is inconsistent Bug #6273: Respawning NPCs rotation is inconsistent
Bug #6282: Laura craft doesn't follow the player character Bug #6282: Laura craft doesn't follow the player character

@ -94,7 +94,7 @@ add_openmw_dir (mwmechanics
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil character actors objects aistate trading weaponpriority spellpriority weapontype spellutil
spellabsorption spelleffects spelleffects
) )
add_openmw_dir (mwstate add_openmw_dir (mwstate

@ -1,5 +1,7 @@
#include "activespells.hpp" #include "activespells.hpp"
#include <optional>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
@ -14,6 +16,8 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwrender/animation.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
@ -96,6 +100,11 @@ namespace MWMechanics
, mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening}) , mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening})
{} {}
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor)
: mId(params.mId), mDisplayName(params.mDisplayName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
, mSlot(params.mSlot), mType(params.mType), mWorsenings(-1)
{}
ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const
{ {
ESM::ActiveSpells::ActiveSpellParams params; ESM::ActiveSpells::ActiveSpellParams params;
@ -220,10 +229,19 @@ namespace MWMechanics
{ {
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid? const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
bool removedSpell = false; bool removedSpell = false;
std::optional<ActiveSpellParams> reflected;
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
{ {
bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration); auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
if(remove) if(result == MagicApplicationResult::REFLECTED)
{
if(!reflected)
reflected = {*spellIt, ptr};
auto& reflectedEffect = reflected->mEffects.emplace_back(*it);
reflectedEffect.mFlags = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
it = spellIt->mEffects.erase(it);
}
else if(result == MagicApplicationResult::REMOVED)
it = spellIt->mEffects.erase(it); it = spellIt->mEffects.erase(it);
else else
++it; ++it;
@ -231,6 +249,14 @@ namespace MWMechanics
if(removedSpell) if(removedSpell)
break; break;
} }
if(reflected)
{
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find("VFX_Reflect");
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
if(animation && !reflectStatic->mModel.empty())
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected);
}
if(removedSpell) if(removedSpell)
continue; continue;

@ -50,6 +50,8 @@ namespace MWMechanics
ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor); ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor);
ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor);
ESM::ActiveSpells::ActiveSpellParams toEsm() const; ESM::ActiveSpells::ActiveSpellParams toEsm() const;
friend class ActiveSpells; friend class ActiveSpells;

@ -1,82 +0,0 @@
#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"
#include "spellutil.hpp"
namespace MWMechanics
{
float getProbability(const MWMechanics::ActiveSpells& activeSpells)
{
float probability = 0.f;
for(const auto& params : activeSpells)
{
for(const auto& effect : params.getEffects())
{
if(effect.mEffectId == ESM::MagicEffect::SpellAbsorption)
{
if(probability == 0.f)
probability = effect.mMagnitude / 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 - probability;
failProbability *= 1.f - effect.mMagnitude / 100;
probability = 1.f - failProbability;
}
}
}
}
return static_cast<int>(probability * 100);
}
bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{
if (spellId.empty() || target.isEmpty() || 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;
int chance = getProbability(stats.getActiveSpells());
if (Misc::Rng::roll0to99() >= chance)
return false;
const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
const ESM::Static* absorbStatic = esmStore.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());
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
int spellCost = 0;
if (spell)
{
spellCost = MWMechanics::calcSpellCost(*spell);
}
else
{
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
if (enchantment)
spellCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), caster);
}
// Magicka is increased by the cost of the spell
DynamicStat<float> magicka = stats.getMagicka();
magicka.setCurrent(magicka.getCurrent() + spellCost);
stats.setMagicka(magicka);
return true;
}
}

@ -1,17 +0,0 @@
#ifndef MWMECHANICS_SPELLABSORPTION_H
#define MWMECHANICS_SPELLABSORPTION_H
#include <string>
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 std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
}
#endif

@ -22,38 +22,11 @@
#include "actorutil.hpp" #include "actorutil.hpp"
#include "aifollow.hpp" #include "aifollow.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
#include "spellabsorption.hpp"
#include "spelleffects.hpp" #include "spelleffects.hpp"
#include "spellutil.hpp" #include "spellutil.hpp"
#include "summoning.hpp" #include "summoning.hpp"
#include "weapontype.hpp" #include "weapontype.hpp"
namespace
{
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;
}
}
namespace MWMechanics namespace MWMechanics
{ {
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell) CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
@ -82,7 +55,7 @@ namespace MWMechanics
} }
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) const ESM::EffectList &effects, ESM::RangeType range, bool exploded)
{ {
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor(); const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
if (targetIsActor) if (targetIsActor)
@ -123,7 +96,6 @@ namespace MWMechanics
} }
} }
ESM::EffectList reflectedEffects;
ActiveSpells::ActiveSpellParams params(*this, caster); ActiveSpells::ActiveSpellParams params(*this, caster);
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer()); bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
@ -136,9 +108,6 @@ namespace MWMechanics
// throughout the iteration of this spell's // throughout the iteration of this spell's
// effects, we display a "can't re-cast" message // effects, we display a "can't re-cast" message
// Try absorbing the spell. Some handling must still happen for absorbed effects.
bool absorbed = absorbSpell(mId, caster, target);
int currentEffectIndex = 0; int currentEffectIndex = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin()); for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex) !target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
@ -167,19 +136,6 @@ namespace MWMechanics
&& (caster.isEmpty() || !caster.getClass().isActor())) && (caster.isEmpty() || !caster.getClass().isActor()))
continue; continue;
// 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);
// Avoid proceeding further for absorbed spells.
if (absorbed)
continue;
// Reflect harmful effects
if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects))
continue;
ActiveSpells::ActiveEffect effect; ActiveSpells::ActiveEffect effect;
effect.mEffectId = effectIt->mEffectID; effect.mEffectId = effectIt->mEffectID;
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg; effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
@ -189,13 +145,8 @@ namespace MWMechanics
effect.mTimeLeft = 0.f; effect.mTimeLeft = 0.f;
effect.mEffectIndex = currentEffectIndex; effect.mEffectIndex = currentEffectIndex;
effect.mFlags = ESM::ActiveEffect::Flag_None; effect.mFlags = ESM::ActiveEffect::Flag_None;
if(mManualSpell)
// Avoid applying harmful effects to the player in god mode effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect;
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
{
effect.mMinMagnitude = 0;
effect.mMaxMagnitude = 0;
}
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f; effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
@ -209,14 +160,14 @@ namespace MWMechanics
// add to list of active effects, to apply in next frame // add to list of active effects, to apply in next frame
params.getEffects().emplace_back(effect); params.getEffects().emplace_back(effect);
bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth; bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth) if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth)
{ {
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar. // If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
MWBase::Environment::get().getWindowManager()->setEnemy(target); MWBase::Environment::get().getWindowManager()->setEnemy(target);
} }
if (targetIsActor || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) if (!targetIsActor && magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
{ {
playEffects(target, *magicEffect); playEffects(target, *magicEffect);
} }
@ -227,9 +178,6 @@ namespace MWMechanics
if (!target.isEmpty()) if (!target.isEmpty())
{ {
if (!reflectedEffects.mList.empty())
inflict(caster, target, reflectedEffects, range, true, exploded);
if (!params.getEffects().empty()) if (!params.getEffects().empty())
{ {
if(targetIsActor) if(targetIsActor)
@ -237,6 +185,7 @@ namespace MWMechanics
else else
{ {
// Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway // Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway
// and we can ignore reflection since non-actors cannot reflect spells
for(auto& effect : params.getEffects()) for(auto& effect : params.getEffects())
applyMagicEffect(target, caster, params, effect, 0.f); applyMagicEffect(target, caster, params, effect, 0.f);
} }

@ -62,7 +62,7 @@ namespace MWMechanics
/// @note \a target can be any type of object, not just actors. /// @note \a target can be any type of object, not just actors.
/// @note \a caster can be any type of object, or even an empty object. /// @note \a caster can be any type of object, or even an empty object.
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); const ESM::EffectList& effects, ESM::RangeType range, bool exploded=false);
}; };
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true); void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true);

@ -16,6 +16,7 @@
#include "../mwmechanics/aifollow.hpp" #include "../mwmechanics/aifollow.hpp"
#include "../mwmechanics/npcstats.hpp" #include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellresistance.hpp" #include "../mwmechanics/spellresistance.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwmechanics/summoning.hpp" #include "../mwmechanics/summoning.hpp"
#include "../mwrender/animation.hpp" #include "../mwrender/animation.hpp"
@ -261,6 +262,97 @@ namespace
return false; return false;
} }
void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{
const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
const ESM::Static* absorbStatic = esmStore.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());
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
int spellCost = 0;
if (spell)
{
spellCost = MWMechanics::calcSpellCost(*spell);
}
else
{
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
if (enchantment)
spellCost = MWMechanics::getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), caster);
}
// Magicka is increased by the cost of the spell
auto& stats = target.getClass().getCreatureStats(target);
auto magicka = stats.getMagicka();
magicka.setCurrent(magicka.getCurrent() + spellCost);
stats.setMagicka(magicka);
}
MWMechanics::MagicApplicationResult applyProtections(const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, const ESM::MagicEffect* magicEffect)
{
auto& stats = target.getClass().getCreatureStats(target);
auto& magnitudes = stats.getMagicEffects();
// Apply reflect and spell absorption
if(target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment && spellParams.getType() != ESM::ActiveSpells::Type_Permanent)
{
bool canReflect = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) &&
!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) && magnitudes.get(ESM::MagicEffect::Reflect).getMagnitude() > 0.f;
bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) && magnitudes.get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f;
if(canReflect || canAbsorb)
{
for(const auto& activeParam : stats.getActiveSpells())
{
for(const auto& activeEffect : activeParam.getEffects())
{
if(!(activeEffect.mFlags & ESM::ActiveEffect::Flag_Applied))
continue;
if(activeEffect.mEffectId == ESM::MagicEffect::Reflect)
{
if(canReflect && Misc::Rng::roll0to99() < activeEffect.mMagnitude)
{
return MWMechanics::MagicApplicationResult::REFLECTED;
}
}
else if(activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption)
{
if(canAbsorb && Misc::Rng::roll0to99() < activeEffect.mMagnitude)
{
absorbSpell(spellParams.getId(), caster, target);
return MWMechanics::MagicApplicationResult::REMOVED;
}
}
}
}
}
}
// 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);
// Apply resistances
if(!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances))
{
const ESM::Spell* spell = nullptr;
if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellParams.getId());
float magnitudeMult = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes);
if (magnitudeMult == 0)
{
// Fully resisted, show message
if (target == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
else if (caster == MWMechanics::getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
return MWMechanics::MagicApplicationResult::REMOVED;
}
effect.mMinMagnitude *= magnitudeMult;
effect.mMaxMagnitude *= magnitudeMult;
}
return MWMechanics::MagicApplicationResult::APPLIED;
}
static const std::map<int, std::string> sBoundItemsMap{ static const std::map<int, std::string> sBoundItemsMap{
{ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"}, {ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"},
{ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"}, {ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"},
@ -682,7 +774,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co
} }
} }
bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt) MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt)
{ {
const auto world = MWBase::Environment::get().getWorld(); const auto world = MWBase::Environment::get().getWorld();
bool invalid = false; bool invalid = false;
@ -698,13 +790,13 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
} }
if(target == getPlayer()) if(target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
return false; return MagicApplicationResult::APPLIED;
} }
else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled()) else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled())
{ {
if(target == getPlayer()) if(target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}"); MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}");
return true; return MagicApplicationResult::REMOVED;
} }
const auto* magicEffect = world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId); const auto* magicEffect = world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
@ -712,10 +804,10 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce) if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce)
{ {
effect.mTimeLeft -= dt; effect.mTimeLeft -= dt;
return false; return MagicApplicationResult::APPLIED;
} }
else if(!dt) else if(!dt)
return false; return MagicApplicationResult::APPLIED;
} }
if(effect.mEffectId == ESM::MagicEffect::Lock) if(effect.mEffectId == ESM::MagicEffect::Lock)
{ {
@ -771,28 +863,19 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
} }
else else
{ {
auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); auto& stats = target.getClass().getCreatureStats(target);
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & (ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Ignore_Resistances))) auto& magnitudes = stats.getMagicEffects();
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
{ {
const ESM::Spell* spell = nullptr; MagicApplicationResult result = applyProtections(target, caster, spellParams, effect, magicEffect);
if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary) if(result != MagicApplicationResult::APPLIED)
spell = world->getStore().get<ESM::Spell>().search(spellParams.getId()); return result;
float magnitudeMult = getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes);
if (magnitudeMult == 0)
{
// Fully resisted, show message
if (target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
else if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
return true;
}
effect.mMinMagnitude *= magnitudeMult;
effect.mMaxMagnitude *= magnitudeMult;
} }
float oldMagnitude = 0.f; float oldMagnitude = 0.f;
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied) if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
oldMagnitude = effect.mMagnitude; oldMagnitude = effect.mMagnitude;
else if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
playEffects(target, *magicEffect);
float magnitude = roll(effect); float magnitude = roll(effect);
//Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here //Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here
effect.mMagnitude = magnitude; effect.mMagnitude = magnitude;
@ -810,7 +893,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
effect.mMagnitude = oldMagnitude; effect.mMagnitude = oldMagnitude;
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove; effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
effect.mTimeLeft -= dt; effect.mTimeLeft -= dt;
return false; return MagicApplicationResult::APPLIED;
} }
} }
if(effect.mEffectId == ESM::MagicEffect::Corprus) if(effect.mEffectId == ESM::MagicEffect::Corprus)
@ -835,7 +918,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false); MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
if(recalculateMagicka) if(recalculateMagicka)
target.getClass().getCreatureStats(target).recalculateMagicka(); target.getClass().getCreatureStats(target).recalculateMagicka();
return false; return MagicApplicationResult::APPLIED;
} }
void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)

@ -10,8 +10,13 @@
namespace MWMechanics namespace MWMechanics
{ {
enum class MagicApplicationResult
{
APPLIED, REMOVED, REFLECTED
};
// Applies a tick of a single effect. Returns true if the effect should be removed immediately // Applies a tick of a single effect. Returns true if the effect should be removed immediately
bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt); MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt);
// Undoes permanent effects created by ESM::MagicEffect::AppliedOnce // Undoes permanent effects created by ESM::MagicEffect::AppliedOnce
void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect); void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect);

@ -104,8 +104,8 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index)
effect.mMagnitude = magnitude; effect.mMagnitude = magnitude;
effect.mMinMagnitude = magnitude; effect.mMinMagnitude = magnitude;
effect.mMaxMagnitude = magnitude; effect.mMaxMagnitude = magnitude;
// Prevent recalculation of resistances // Prevent recalculation of resistances and don't reflect or absorb the effect
effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances; effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
} }
else else
{ {
@ -172,8 +172,8 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index)
effect.mDuration = -1; effect.mDuration = -1;
effect.mTimeLeft = -1; effect.mTimeLeft = -1;
effect.mEffectIndex = static_cast<int>(effectIndex); effect.mEffectIndex = static_cast<int>(effectIndex);
// Prevent recalculation of resistances // Prevent recalculation of resistances and don't reflect or absorb the effect
effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances; effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
params.mEffects.emplace_back(effect); params.mEffects.emplace_back(effect);
} }
auto [begin, end] = equippedItems.equal_range(id); auto [begin, end] = equippedItems.equal_range(id);

@ -542,7 +542,7 @@ namespace MWWorld
cast.mId = magicBoltState.mSpellId; cast.mId = magicBoltState.mSpellId;
cast.mSourceName = magicBoltState.mSourceName; cast.mSourceName = magicBoltState.mSourceName;
cast.mSlot = magicBoltState.mSlot; cast.mSlot = magicBoltState.mSlot;
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, true);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName, false, magicBoltState.mSlot); MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName, false, magicBoltState.mSlot);
magicBoltState.mToDelete = true; magicBoltState.mToDelete = true;

@ -3805,7 +3805,7 @@ namespace MWWorld
cast.mSlot = slot; cast.mSlot = slot;
ESM::EffectList effectsToApply; ESM::EffectList effectsToApply;
effectsToApply.mList = applyPair.second; effectsToApply.mList = applyPair.second;
cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true); cast.inflict(applyPair.first, caster, effectsToApply, rangeType, true);
} }
} }

@ -22,7 +22,9 @@ namespace ESM
Flag_None = 0, Flag_None = 0,
Flag_Applied = 1 << 0, Flag_Applied = 1 << 0,
Flag_Remove = 1 << 1, Flag_Remove = 1 << 1,
Flag_Ignore_Resistances = 1 << 2 Flag_Ignore_Resistances = 1 << 2,
Flag_Ignore_Reflect = 1 << 3,
Flag_Ignore_SpellAbsorption = 1 << 4
}; };
int mEffectId; int mEffectId;

Loading…
Cancel
Save