1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-30 03:32:36 +00:00
OpenMW/apps/openmw/mwmechanics/spelleffects.cpp
elsid ce263af393
Use existing functions and objects to call correctMeshPath etc
Remove WindowManager wrappers.

It's not safe to use WindowManager in all places and it's not required.
Environment stores resource system providing VFS required to call these
functions. In the case of ObjectPaging it's available from the member variable.
Also ObjectPaging::createChunk may access WindowManager when it's already
destructed when exiting the game because it's destructed before CellPreloader
finishes all background jobs. Engine::mResourceSystem is destructed after all
other systems so it's safe to use it.
2022-06-29 00:58:49 +02:00

1187 lines
53 KiB
C++

#include "spelleffects.hpp"
#include <algorithm>
#include <components/esm3/loadmgef.hpp>
#include <components/misc/rng.hpp>
#include <components/settings/settings.hpp>
#include <components/misc/resourcehelpers.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/soundmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/world.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/aifollow.hpp"
#include "../mwmechanics/npcstats.hpp"
#include "../mwmechanics/spellresistance.hpp"
#include "../mwmechanics/spellutil.hpp"
#include "../mwmechanics/summoning.hpp"
#include "../mwrender/animation.hpp"
#include "../mwworld/actionequip.hpp"
#include "../mwworld/actionteleport.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/player.hpp"
namespace
{
float roll(const ESM::ActiveEffect& effect)
{
if(effect.mMinMagnitude == effect.mMaxMagnitude)
return effect.mMinMagnitude;
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
return effect.mMinMagnitude + Misc::Rng::rollDice(effect.mMaxMagnitude - effect.mMinMagnitude + 1, prng);
}
void modifyAiSetting(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, ESM::MagicEffect::Effects creatureEffect, MWMechanics::CreatureStats::AiSetting setting, float magnitude, bool& invalid)
{
if(target == MWMechanics::getPlayer() || (effect.mEffectId == creatureEffect) == target.getClass().isNpc())
invalid = true;
else
{
auto& creatureStats = target.getClass().getCreatureStats(target);
auto stat = creatureStats.getAiSetting(setting);
stat.setModifier(static_cast<int>(stat.getModifier() + magnitude));
creatureStats.setAiSetting(setting, stat);
}
}
void adjustDynamicStat(const MWWorld::Ptr& target, int index, float magnitude, bool allowDecreaseBelowZero = false, bool allowIncreaseAboveModified = false)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
auto stat = creatureStats.getDynamic(index);
stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero, allowIncreaseAboveModified);
creatureStats.setDynamic(index, stat);
}
void modDynamicStat(const MWWorld::Ptr& target, int index, float magnitude)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
auto stat = creatureStats.getDynamic(index);
float current = stat.getCurrent();
stat.setBase(std::max(0.f, stat.getBase() + magnitude));
stat.setCurrent(current + magnitude);
creatureStats.setDynamic(index, stat);
}
void damageAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
auto attr = creatureStats.getAttribute(effect.mArg);
if(effect.mEffectId == ESM::MagicEffect::DamageAttribute)
magnitude = std::min(attr.getModified(), magnitude);
attr.damage(magnitude);
creatureStats.setAttribute(effect.mArg, attr);
}
void restoreAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
auto attr = creatureStats.getAttribute(effect.mArg);
attr.restore(magnitude);
creatureStats.setAttribute(effect.mArg, attr);
}
void fortifyAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
auto attr = creatureStats.getAttribute(effect.mArg);
attr.setModifier(attr.getModifier() + magnitude);
creatureStats.setAttribute(effect.mArg, attr);
}
void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg);
if(effect.mEffectId == ESM::MagicEffect::DamageSkill)
magnitude = std::min(skill.getModified(), magnitude);
skill.damage(magnitude);
}
void restoreSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg);
skill.restore(magnitude);
}
void fortifySkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg);
skill.setModifier(skill.getModifier() + magnitude);
}
bool disintegrateSlot(const MWWorld::Ptr& ptr, int slot, float disintegrate)
{
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 != MWMechanics::getPlayer())
inv.autoEquip(ptr);
else
inv.unequipItem(*item, ptr);
}
return true;
}
return false;
}
int getBoundItemSlot(const MWWorld::Ptr& boundPtr)
{
const auto [slots, _] = boundPtr.getClass().getEquipmentSlots(boundPtr);
if(!slots.empty())
return slots[0];
return -1;
}
void addBoundItem(const std::string& itemId, const MWWorld::Ptr& actor)
{
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1, actor);
int slot = getBoundItemSlot(boundPtr);
auto prevItem = slot >= 0 ? store.getSlot(slot) : store.end();
MWWorld::ActionEquip action(boundPtr);
action.execute(actor);
if (actor != MWMechanics::getPlayer())
return;
MWWorld::Ptr newItem;
auto it = slot >= 0 ? store.getSlot(slot) : store.end();
// Equip can fail because beast races cannot equip boots/helmets
if(it != store.end())
newItem = *it;
if (newItem.isEmpty() || boundPtr != newItem)
return;
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
// change draw state only if the item is in player's right hand
if (slot == MWWorld::InventoryStore::Slot_CarriedRight)
player.setDrawState(MWMechanics::DrawState_Weapon);
if (prevItem != store.end())
player.setPreviousItem(itemId, prevItem->getCellRef().getRefId());
}
void removeBoundItem(const std::string& itemId, const MWWorld::Ptr& actor)
{
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
auto item = std::find_if(store.begin(), store.end(), [&] (const auto& it)
{
return Misc::StringUtils::ciEqual(it.getCellRef().getRefId(), itemId);
});
if(item == store.end())
return;
int slot = getBoundItemSlot(*item);
auto currentItem = store.getSlot(slot);
bool wasEquipped = currentItem != store.end() && Misc::StringUtils::ciEqual(currentItem->getCellRef().getRefId(), itemId);
if (actor != MWMechanics::getPlayer())
{
store.remove(itemId, 1, actor);
// Equip a replacement
if (!wasEquipped)
return;
auto type = currentItem->getType();
if (type != ESM::Weapon::sRecordId && type != ESM::Armor::sRecordId && type != ESM::Clothing::sRecordId)
return;
if (actor.getClass().getCreatureStats(actor).isDead())
return;
if (!actor.getClass().hasInventoryStore(actor))
return;
if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())
return;
actor.getClass().getInventoryStore(actor).autoEquip(actor);
return;
}
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
std::string prevItemId = player.getPreviousItem(itemId);
player.erasePreviousItem(itemId);
if (!prevItemId.empty() && wasEquipped)
{
// Find previous item (or its replacement) by id.
// we should equip previous item only if expired bound item was equipped.
MWWorld::Ptr prevItem = store.findReplacement(prevItemId);
if (!prevItem.isEmpty())
{
MWWorld::ActionEquip action(prevItem);
action.execute(actor);
}
}
store.remove(itemId, 1, actor);
}
bool isCorprusEffect(const MWMechanics::ActiveSpells::ActiveEffect& effect, bool harmfulOnly = false)
{
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied && effect.mEffectId != ESM::MagicEffect::Corprus)
{
const auto* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce && (!harmfulOnly || magicEffect->mData.mFlags & ESM::MagicEffect::Flags::Harmful))
return true;
}
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())
{
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
animation->addEffect(
Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel, vfs),
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)
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
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(prng) < activeEffect.mMagnitude)
{
return MWMechanics::MagicApplicationResult::REFLECTED;
}
}
else if(activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption)
{
if(canAbsorb && Misc::Rng::roll0to99(prng) < 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{
{ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"},
{ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"},
{ESM::MagicEffect::BoundCuirass, "sMagicBoundCuirassID"},
{ESM::MagicEffect::BoundDagger, "sMagicBoundDaggerID"},
{ESM::MagicEffect::BoundGloves, "sMagicBoundLeftGauntletID"},
{ESM::MagicEffect::BoundHelm, "sMagicBoundHelmID"},
{ESM::MagicEffect::BoundLongbow, "sMagicBoundLongbowID"},
{ESM::MagicEffect::BoundLongsword, "sMagicBoundLongswordID"},
{ESM::MagicEffect::BoundMace, "sMagicBoundMaceID"},
{ESM::MagicEffect::BoundShield, "sMagicBoundShieldID"},
{ESM::MagicEffect::BoundSpear, "sMagicBoundSpearID"}
};
}
namespace MWMechanics
{
void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& invalid, bool& receivedMagicDamage, bool& recalculateMagicka)
{
const auto world = MWBase::Environment::get().getWorld();
bool godmode = target == getPlayer() && world->getGodModeState();
switch(effect.mEffectId)
{
case ESM::MagicEffect::CureCommonDisease:
target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease();
break;
case ESM::MagicEffect::CureBlightDisease:
target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease();
break;
case ESM::MagicEffect::RemoveCurse:
target.getClass().getCreatureStats(target).getSpells().purgeCurses();
break;
case ESM::MagicEffect::CureCorprusDisease:
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Corprus);
break;
case ESM::MagicEffect::CurePoison:
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Poison);
break;
case ESM::MagicEffect::CureParalyzation:
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Paralyze);
break;
case ESM::MagicEffect::Dispel:
// Dispel removes entire spells at once
target.getClass().getCreatureStats(target).getActiveSpells().purge([magnitude=effect.mMagnitude] (const ActiveSpells::ActiveSpellParams& params)
{
if(params.getType() == ESM::ActiveSpells::Type_Temporary)
{
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(params.getId());
if (spell && spell->mData.mType == ESM::Spell::ST_Spell)
{
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
return Misc::Rng::roll0to99(prng) < magnitude;
}
}
return false;
}, target);
break;
case ESM::MagicEffect::AlmsiviIntervention:
case ESM::MagicEffect::DivineIntervention:
if (target != getPlayer())
invalid = true;
else if (world->isTeleportingEnabled())
{
auto marker = (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker";
world->teleportToClosestMarker(target, marker);
if(!caster.isEmpty())
{
MWRender::Animation* anim = world->getAnimation(caster);
anim->removeEffect(effect.mEffectId);
const ESM::Static* fx = world->getStore().get<ESM::Static>().search("VFX_Summon_end");
if (fx)
{
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1);
}
}
}
else if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
break;
case ESM::MagicEffect::Mark:
if (target != getPlayer())
invalid = true;
else if (world->isTeleportingEnabled())
world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition());
else if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
break;
case ESM::MagicEffect::Recall:
if (target != getPlayer())
invalid = true;
else if (world->isTeleportingEnabled())
{
MWWorld::CellStore* markedCell = nullptr;
ESM::Position markedPosition;
world->getPlayer().getMarkedPosition(markedCell, markedPosition);
if (markedCell)
{
MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName, markedPosition, false);
action.execute(target);
if(!caster.isEmpty())
{
MWRender::Animation* anim = world->getAnimation(caster);
anim->removeEffect(effect.mEffectId);
}
}
}
else if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
break;
case ESM::MagicEffect::CommandCreature:
case ESM::MagicEffect::CommandHumanoid:
if(caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc())
invalid = true;
else if(effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel())
{
MWMechanics::AiFollow package(caster, true);
target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
}
break;
case ESM::MagicEffect::ExtraSpell:
if(target.getClass().hasInventoryStore(target))
{
auto& store = target.getClass().getInventoryStore(target);
store.unequipAll(target);
}
else
invalid = true;
break;
case ESM::MagicEffect::TurnUndead:
if(target.getClass().isNpc() || target.get<ESM::Creature>()->mBase->mData.mType != ESM::Creature::Undead)
invalid = true;
else
{
auto& creatureStats = target.getClass().getCreatureStats(target);
Stat<int> stat = creatureStats.getAiSetting(CreatureStats::AI_Flee);
stat.setModifier(static_cast<int>(stat.getModifier() + effect.mMagnitude));
creatureStats.setAiSetting(CreatureStats::AI_Flee, stat);
}
break;
case ESM::MagicEffect::FrenzyCreature:
case ESM::MagicEffect::FrenzyHumanoid:
modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::CalmCreature:
case ESM::MagicEffect::CalmHumanoid:
modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid);
if(!invalid && effect.mMagnitude > 0)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
creatureStats.getAiSequence().stopCombat();
}
break;
case ESM::MagicEffect::DemoralizeCreature:
case ESM::MagicEffect::DemoralizeHumanoid:
modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, CreatureStats::AI_Flee, effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::RallyCreature:
case ESM::MagicEffect::RallyHumanoid:
modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, CreatureStats::AI_Flee, -effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::SummonScamp:
case ESM::MagicEffect::SummonClannfear:
case ESM::MagicEffect::SummonDaedroth:
case ESM::MagicEffect::SummonDremora:
case ESM::MagicEffect::SummonAncestralGhost:
case ESM::MagicEffect::SummonSkeletalMinion:
case ESM::MagicEffect::SummonBonewalker:
case ESM::MagicEffect::SummonGreaterBonewalker:
case ESM::MagicEffect::SummonBonelord:
case ESM::MagicEffect::SummonWingedTwilight:
case ESM::MagicEffect::SummonHunger:
case ESM::MagicEffect::SummonGoldenSaint:
case ESM::MagicEffect::SummonFlameAtronach:
case ESM::MagicEffect::SummonFrostAtronach:
case ESM::MagicEffect::SummonStormAtronach:
case ESM::MagicEffect::SummonCenturionSphere:
case ESM::MagicEffect::SummonFabricant:
case ESM::MagicEffect::SummonWolf:
case ESM::MagicEffect::SummonBear:
case ESM::MagicEffect::SummonBonewolf:
case ESM::MagicEffect::SummonCreature04:
case ESM::MagicEffect::SummonCreature05:
if(!target.isInCell())
invalid = true;
else
effect.mArg = summonCreature(effect.mEffectId, target);
break;
case ESM::MagicEffect::BoundGloves:
if(!target.getClass().hasInventoryStore(target))
{
invalid = true;
break;
}
addBoundItem(world->getStore().get<ESM::GameSetting>().find("sMagicBoundRightGauntletID")->mValue.getString(), target);
// left gauntlet added below
[[fallthrough]];
case ESM::MagicEffect::BoundDagger:
case ESM::MagicEffect::BoundLongsword:
case ESM::MagicEffect::BoundMace:
case ESM::MagicEffect::BoundBattleAxe:
case ESM::MagicEffect::BoundSpear:
case ESM::MagicEffect::BoundLongbow:
case ESM::MagicEffect::BoundCuirass:
case ESM::MagicEffect::BoundHelm:
case ESM::MagicEffect::BoundBoots:
case ESM::MagicEffect::BoundShield:
if(!target.getClass().hasInventoryStore(target))
invalid = true;
else
{
const std::string& item = sBoundItemsMap.at(effect.mEffectId);
addBoundItem(world->getStore().get<ESM::GameSetting>().find(item)->mValue.getString(), target);
}
break;
case ESM::MagicEffect::FireDamage:
case ESM::MagicEffect::ShockDamage:
case ESM::MagicEffect::FrostDamage:
case ESM::MagicEffect::DamageHealth:
case ESM::MagicEffect::Poison:
case ESM::MagicEffect::DamageMagicka:
case ESM::MagicEffect::DamageFatigue:
if(!godmode)
{
int index = 0;
if(effect.mEffectId == ESM::MagicEffect::DamageMagicka)
index = 1;
else if(effect.mEffectId == ESM::MagicEffect::DamageFatigue)
index = 2;
// Damage "Dynamic" abilities reduce the base value
if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
modDynamicStat(target, index, -effect.mMagnitude);
else
{
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
adjustDynamicStat(target, index, -effect.mMagnitude, index == 2 && uncappedDamageFatigue);
if(index == 0)
receivedMagicDamage = true;
}
}
break;
case ESM::MagicEffect::DamageAttribute:
if(!godmode)
damageAttribute(target, effect, effect.mMagnitude);
break;
case ESM::MagicEffect::DamageSkill:
if(!target.getClass().isNpc())
invalid = true;
else if(!godmode)
{
// Damage Skill abilities reduce base skill :todd:
if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
{
auto& npcStats = target.getClass().getNpcStats(target);
SkillValue& skill = npcStats.getSkill(effect.mArg);
// Damage Skill abilities reduce base skill :todd:
skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f));
}
else
damageSkill(target, effect, effect.mMagnitude);
}
break;
case ESM::MagicEffect::RestoreAttribute:
restoreAttribute(target, effect, effect.mMagnitude);
break;
case ESM::MagicEffect::RestoreSkill:
if(!target.getClass().isNpc())
invalid = true;
else
restoreSkill(target, effect, effect.mMagnitude);
break;
case ESM::MagicEffect::RestoreHealth:
case ESM::MagicEffect::RestoreMagicka:
case ESM::MagicEffect::RestoreFatigue:
adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::RestoreHealth, effect.mMagnitude);
break;
case ESM::MagicEffect::SunDamage:
{
// isInCell shouldn't be needed, but updateActor called during game start
if (!target.isInCell() || !target.getCell()->isExterior() || godmode)
break;
float time = world->getTimeStamp().getHour();
float timeDiff = std::clamp(std::abs(time - 13.f), 0.f, 7.f);
float damageScale = 1.f - timeDiff / 7.f;
// When cloudy, the sun damage effect is halved
static float fMagicSunBlockedMult = world->getStore().get<ESM::GameSetting>().find("fMagicSunBlockedMult")->mValue.getFloat();
int weather = world->getCurrentWeather();
if (weather > 1)
damageScale *= fMagicSunBlockedMult;
float damage = effect.mMagnitude * damageScale;
adjustDynamicStat(target, 0, -damage);
if (damage > 0.f)
receivedMagicDamage = true;
}
break;
case ESM::MagicEffect::DrainHealth:
case ESM::MagicEffect::DrainMagicka:
case ESM::MagicEffect::DrainFatigue:
if(!godmode)
{
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
int index = effect.mEffectId - ESM::MagicEffect::DrainHealth;
adjustDynamicStat(target, index, -effect.mMagnitude, uncappedDamageFatigue && index == 2);
if(index == 0)
receivedMagicDamage = true;
}
break;
case ESM::MagicEffect::FortifyHealth:
case ESM::MagicEffect::FortifyMagicka:
case ESM::MagicEffect::FortifyFatigue:
if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude);
else
adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude, false, true);
break;
case ESM::MagicEffect::DrainAttribute:
if(!godmode)
damageAttribute(target, effect, effect.mMagnitude);
break;
case ESM::MagicEffect::FortifyAttribute:
// Abilities affect base stats, but not for drain
if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
AttributeValue attr = creatureStats.getAttribute(effect.mArg);
attr.setBase(attr.getBase() + effect.mMagnitude);
creatureStats.setAttribute(effect.mArg, attr);
}
else
fortifyAttribute(target, effect, effect.mMagnitude);
break;
case ESM::MagicEffect::DrainSkill:
if(!target.getClass().isNpc())
invalid = true;
else if(!godmode)
damageSkill(target, effect, effect.mMagnitude);
break;
case ESM::MagicEffect::FortifySkill:
if(!target.getClass().isNpc())
invalid = true;
else if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
{
// Abilities affect base stats, but not for drain
auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg);
skill.setBase(skill.getBase() + effect.mMagnitude);
}
else
fortifySkill(target, effect, effect.mMagnitude);
break;
case ESM::MagicEffect::FortifyMaximumMagicka:
recalculateMagicka = true;
break;
case ESM::MagicEffect::AbsorbHealth:
case ESM::MagicEffect::AbsorbMagicka:
case ESM::MagicEffect::AbsorbFatigue:
if(!godmode)
{
int index = effect.mEffectId - ESM::MagicEffect::AbsorbHealth;
adjustDynamicStat(target, index, -effect.mMagnitude);
if(!caster.isEmpty())
adjustDynamicStat(caster, index, effect.mMagnitude);
if(index == 0)
receivedMagicDamage = true;
}
break;
case ESM::MagicEffect::AbsorbAttribute:
if(!godmode)
{
damageAttribute(target, effect, effect.mMagnitude);
if(!caster.isEmpty())
fortifyAttribute(caster, effect, effect.mMagnitude);
}
break;
case ESM::MagicEffect::AbsorbSkill:
if(!target.getClass().isNpc())
invalid = true;
else if(!godmode)
{
damageSkill(target, effect, effect.mMagnitude);
if(!caster.isEmpty())
fortifySkill(caster, effect, effect.mMagnitude);
}
break;
case ESM::MagicEffect::DisintegrateArmor:
{
if (!target.getClass().hasInventoryStore(target))
{
invalid = true;
break;
}
if (godmode)
break;
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(target, priority, effect.mMagnitude))
break;
}
break;
}
case ESM::MagicEffect::DisintegrateWeapon:
if (!target.getClass().hasInventoryStore(target))
{
invalid = true;
break;
}
if (!godmode)
disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude);
break;
}
}
bool shouldRemoveEffect(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect)
{
const auto world = MWBase::Environment::get().getWorld();
switch(effect.mEffectId)
{
case ESM::MagicEffect::Levitate:
{
if(!world->isLevitationEnabled())
{
if(target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
return true;
}
break;
}
case ESM::MagicEffect::Recall:
case ESM::MagicEffect::DivineIntervention:
case ESM::MagicEffect::AlmsiviIntervention:
{
return effect.mFlags & ESM::ActiveEffect::Flag_Applied;
}
case ESM::MagicEffect::WaterWalking:
{
if (target.getClass().isPureWaterCreature(target) && world->isSwimming(target))
return true;
if (effect.mFlags & ESM::ActiveEffect::Flag_Applied)
break;
if (!world->isWaterWalkingCastableOnTarget(target))
{
if(target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidEffect}");
return true;
}
break;
}
}
return false;
}
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();
bool invalid = false;
bool receivedMagicDamage = false;
bool recalculateMagicka = false;
if(effect.mEffectId == ESM::MagicEffect::Corprus && spellParams.shouldWorsen())
{
spellParams.worsen();
for(auto& otherEffect : spellParams.getEffects())
{
if(isCorprusEffect(otherEffect))
applyMagicEffect(target, caster, spellParams, otherEffect, invalid, receivedMagicDamage, recalculateMagicka);
}
if(target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
return MagicApplicationResult::APPLIED;
}
else if(shouldRemoveEffect(target, effect))
{
onMagicEffectRemoved(target, spellParams, effect);
return MagicApplicationResult::REMOVED;
}
const auto* magicEffect = world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
{
if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce)
{
effect.mTimeLeft -= dt;
return MagicApplicationResult::APPLIED;
}
else if(!dt)
return MagicApplicationResult::APPLIED;
}
if(effect.mEffectId == ESM::MagicEffect::Lock)
{
if(target.getClass().canLock(target))
{
MWRender::Animation* animation = world->getAnimation(target);
if(animation)
animation->addSpellCastGlow(magicEffect);
int magnitude = static_cast<int>(roll(effect));
if(target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude
{
if(caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}");
target.getCellRef().lock(magnitude);
}
}
else
invalid = true;
}
else if(effect.mEffectId == ESM::MagicEffect::Open)
{
if(target.getClass().canLock(target))
{
// Use the player instead of the caster for vanilla crime compatibility
MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target);
MWRender::Animation* animation = world->getAnimation(target);
if(animation)
animation->addSpellCastGlow(magicEffect);
int magnitude = static_cast<int>(roll(effect));
if(target.getCellRef().getLockLevel() <= magnitude)
{
if(target.getCellRef().getLockLevel() > 0)
{
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);
if(caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}");
}
target.getCellRef().unlock();
}
else
{
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f);
}
}
else
invalid = true;
}
else if(!target.getClass().isActor())
{
invalid = true;
}
else
{
auto& stats = target.getClass().getCreatureStats(target);
auto& magnitudes = stats.getMagicEffects();
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
{
MagicApplicationResult result = applyProtections(target, caster, spellParams, effect, magicEffect);
if(result != MagicApplicationResult::APPLIED)
return result;
}
float oldMagnitude = 0.f;
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
oldMagnitude = effect.mMagnitude;
else
{
if(spellParams.getType() != ESM::ActiveSpells::Type_Enchantment)
playEffects(target, *magicEffect, spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary);
if(effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc() && target.getType() == ESM::Creature::sRecordId && target.get<ESM::Creature>()->mBase->mData.mSoul == 0 && caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}");
}
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
effect.mMagnitude = magnitude;
if(!(magicEffect->mData.mFlags & (ESM::MagicEffect::Flags::NoMagnitude | ESM::MagicEffect::Flags::AppliedOnce)))
{
if(effect.mDuration != 0)
{
float mult = dt;
if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
mult = std::min(effect.mTimeLeft, dt);
effect.mMagnitude *= mult;
}
if(effect.mMagnitude == 0)
{
effect.mMagnitude = oldMagnitude;
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
effect.mTimeLeft -= dt;
return MagicApplicationResult::APPLIED;
}
}
if(effect.mEffectId == ESM::MagicEffect::Corprus)
spellParams.worsen();
else
applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage, recalculateMagicka);
effect.mMagnitude = magnitude;
magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(effect.mMagnitude - oldMagnitude));
}
effect.mTimeLeft -= dt;
if(invalid)
{
effect.mTimeLeft = 0;
effect.mFlags |= ESM::ActiveEffect::Flag_Remove;
auto anim = world->getAnimation(target);
if(anim)
anim->removeEffect(effect.mEffectId);
}
else
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
if (receivedMagicDamage && target == getPlayer())
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
if(recalculateMagicka)
target.getClass().getCreatureStats(target).recalculateMagicka();
return MagicApplicationResult::APPLIED;
}
void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)
{
const auto world = MWBase::Environment::get().getWorld();
auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
bool invalid;
switch(effect.mEffectId)
{
case ESM::MagicEffect::CommandCreature:
case ESM::MagicEffect::CommandHumanoid:
if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
{
auto& seq = target.getClass().getCreatureStats(target).getAiSequence();
seq.erasePackageIf([&](const auto& package)
{
return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow && static_cast<const MWMechanics::AiFollow*>(package.get())->isCommanded();
});
}
break;
case ESM::MagicEffect::ExtraSpell:
if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
target.getClass().getInventoryStore(target).autoEquip(target);
break;
case ESM::MagicEffect::TurnUndead:
{
auto& creatureStats = target.getClass().getCreatureStats(target);
Stat<int> stat = creatureStats.getAiSetting(CreatureStats::AI_Flee);
stat.setModifier(static_cast<int>(stat.getModifier() - effect.mMagnitude));
creatureStats.setAiSetting(CreatureStats::AI_Flee, stat);
}
break;
case ESM::MagicEffect::FrenzyCreature:
case ESM::MagicEffect::FrenzyHumanoid:
modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, CreatureStats::AI_Fight, -effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::CalmCreature:
case ESM::MagicEffect::CalmHumanoid:
modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, CreatureStats::AI_Fight, effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::DemoralizeCreature:
case ESM::MagicEffect::DemoralizeHumanoid:
modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, CreatureStats::AI_Flee, -effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::RallyCreature:
case ESM::MagicEffect::RallyHumanoid:
modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, CreatureStats::AI_Flee, effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::SummonScamp:
case ESM::MagicEffect::SummonClannfear:
case ESM::MagicEffect::SummonDaedroth:
case ESM::MagicEffect::SummonDremora:
case ESM::MagicEffect::SummonAncestralGhost:
case ESM::MagicEffect::SummonSkeletalMinion:
case ESM::MagicEffect::SummonBonewalker:
case ESM::MagicEffect::SummonGreaterBonewalker:
case ESM::MagicEffect::SummonBonelord:
case ESM::MagicEffect::SummonWingedTwilight:
case ESM::MagicEffect::SummonHunger:
case ESM::MagicEffect::SummonGoldenSaint:
case ESM::MagicEffect::SummonFlameAtronach:
case ESM::MagicEffect::SummonFrostAtronach:
case ESM::MagicEffect::SummonStormAtronach:
case ESM::MagicEffect::SummonCenturionSphere:
case ESM::MagicEffect::SummonFabricant:
case ESM::MagicEffect::SummonWolf:
case ESM::MagicEffect::SummonBear:
case ESM::MagicEffect::SummonBonewolf:
case ESM::MagicEffect::SummonCreature04:
case ESM::MagicEffect::SummonCreature05:
{
if(effect.mArg != -1)
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, effect.mArg);
auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap();
auto [begin, end] = summons.equal_range(effect.mEffectId);
for(auto it = begin; it != end; ++it)
{
if(it->second == effect.mArg)
{
summons.erase(it);
break;
}
}
}
break;
case ESM::MagicEffect::BoundGloves:
removeBoundItem(world->getStore().get<ESM::GameSetting>().find("sMagicBoundRightGauntletID")->mValue.getString(), target);
[[fallthrough]];
case ESM::MagicEffect::BoundDagger:
case ESM::MagicEffect::BoundLongsword:
case ESM::MagicEffect::BoundMace:
case ESM::MagicEffect::BoundBattleAxe:
case ESM::MagicEffect::BoundSpear:
case ESM::MagicEffect::BoundLongbow:
case ESM::MagicEffect::BoundCuirass:
case ESM::MagicEffect::BoundHelm:
case ESM::MagicEffect::BoundBoots:
case ESM::MagicEffect::BoundShield:
{
const std::string& item = sBoundItemsMap.at(effect.mEffectId);
removeBoundItem(world->getStore().get<ESM::GameSetting>().find(item)->mValue.getString(), target);
}
break;
case ESM::MagicEffect::DrainHealth:
case ESM::MagicEffect::DrainMagicka:
case ESM::MagicEffect::DrainFatigue:
adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::DrainHealth, effect.mMagnitude);
break;
case ESM::MagicEffect::FortifyHealth:
case ESM::MagicEffect::FortifyMagicka:
case ESM::MagicEffect::FortifyFatigue:
if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude);
else
adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude, true);
break;
case ESM::MagicEffect::DrainAttribute:
restoreAttribute(target, effect, effect.mMagnitude);
break;
case ESM::MagicEffect::FortifyAttribute:
// Abilities affect base stats, but not for drain
if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
AttributeValue attr = creatureStats.getAttribute(effect.mArg);
attr.setBase(attr.getBase() - effect.mMagnitude);
creatureStats.setAttribute(effect.mArg, attr);
}
else
fortifyAttribute(target, effect, -effect.mMagnitude);
break;
case ESM::MagicEffect::DrainSkill:
restoreSkill(target, effect, effect.mMagnitude);
break;
case ESM::MagicEffect::FortifySkill:
// Abilities affect base stats, but not for drain
if(spellParams.getType() == ESM::ActiveSpells::Type_Ability)
{
auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(effect.mArg);
skill.setBase(skill.getBase() - effect.mMagnitude);
}
else
fortifySkill(target, effect, -effect.mMagnitude);
break;
case ESM::MagicEffect::FortifyMaximumMagicka:
target.getClass().getCreatureStats(target).recalculateMagicka();
break;
case ESM::MagicEffect::AbsorbAttribute:
{
const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId());
restoreAttribute(target, effect, effect.mMagnitude);
if(!caster.isEmpty())
fortifyAttribute(caster, effect, -effect.mMagnitude);
}
break;
case ESM::MagicEffect::AbsorbSkill:
{
const auto caster = world->searchPtrViaActorId(spellParams.getCasterActorId());
restoreSkill(target, effect, effect.mMagnitude);
if(!caster.isEmpty())
fortifySkill(caster, effect, -effect.mMagnitude);
}
break;
case ESM::MagicEffect::Corprus:
{
int worsenings = spellParams.getWorsenings();
spellParams.resetWorsenings();
if(worsenings > 0)
{
for(const auto& otherEffect : spellParams.getEffects())
{
if(isCorprusEffect(otherEffect, true))
{
for(int i = 0; i < worsenings; i++)
removeMagicEffect(target, spellParams, otherEffect);
}
}
}
//Note that we remove the effects, but keep the params
target.getClass().getCreatureStats(target).getActiveSpells().purge([&spellParams] (const ActiveSpells::ActiveSpellParams& params, const auto&)
{
return &spellParams == &params;
}, target);
}
break;
}
}
void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)
{
if(!(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
return;
auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
magnitudes.add(EffectKey(effect.mEffectId, effect.mArg), EffectParam(-effect.mMagnitude));
removeMagicEffect(target, spellParams, effect);
if(magnitudes.get(effect.mEffectId).getMagnitude() <= 0.f)
{
auto anim = MWBase::Environment::get().getWorld()->getAnimation(target);
if(anim)
anim->removeEffect(effect.mEffectId);
}
}
}