1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-26 18:35:20 +00:00
OpenMW/apps/openmw/mwmechanics/spelleffects.cpp
2023-07-01 00:59:35 +02:00

1290 lines
60 KiB
C++

#include "spelleffects.hpp"
#include <algorithm>
#include <components/esm3/loadcrea.hpp>
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadstat.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/rng.hpp>
#include <components/settings/values.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::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 attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
auto attr = creatureStats.getAttribute(attribute);
if (effect.mEffectId == ESM::MagicEffect::DamageAttribute)
magnitude = std::min(attr.getModified(), magnitude);
attr.damage(magnitude);
creatureStats.setAttribute(attribute, attr);
}
void restoreAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
auto attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
auto attr = creatureStats.getAttribute(attribute);
attr.restore(magnitude);
creatureStats.setAttribute(attribute, attr);
}
void fortifyAttribute(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& creatureStats = target.getClass().getCreatureStats(target);
auto attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
auto attr = creatureStats.getAttribute(attribute);
attr.setModifier(attr.getModifier() + magnitude);
creatureStats.setAttribute(attribute, attr);
}
void damageSkill(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, float magnitude)
{
auto& npcStats = target.getClass().getNpcStats(target);
auto& skill = npcStats.getSkill(ESM::Skill::indexToRefId(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(ESM::Skill::indexToRefId(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(ESM::Skill::indexToRefId(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();
else
inv.unequipItem(*item);
}
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 ESM::RefId& itemId, const MWWorld::Ptr& actor)
{
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
MWWorld::Ptr boundPtr = *store.MWWorld::ContainerStore::add(itemId, 1);
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 ESM::RefId& 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 it.getCellRef().getRefId() == itemId; });
if (item == store.end())
return;
int slot = getBoundItemSlot(*item);
auto currentItem = store.getSlot(slot);
bool wasEquipped = currentItem != store.end() && currentItem->getCellRef().getRefId() == itemId;
if (wasEquipped)
store.remove(*currentItem, 1);
else
store.remove(itemId, 1);
if (actor != MWMechanics::getPlayer())
{
// 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();
return;
}
MWWorld::Player& player = MWBase::Environment::get().getWorld()->getPlayer();
ESM::RefId 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);
}
}
}
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().getESMStore()->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 ESM::RefId& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
{
const auto& esmStore = *MWBase::Environment::get().getESMStore();
const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find(ESM::RefId::stringRefId("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);
}
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(*enchantment, 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::Type 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::Unreflectable)
&& !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect)
&& magnitudes.getOrDefault(ESM::MagicEffect::Reflect).getMagnitude() > 0.f && !caster.isEmpty();
bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption)
&& magnitudes.getOrDefault(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::Type::REFLECTED;
}
}
else if (activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption)
{
if (canAbsorb && Misc::Rng::roll0to99(prng) < activeEffect.mMagnitude)
{
absorbSpell(spellParams.getId(), caster, target);
return MWMechanics::MagicApplicationResult::Type::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().getESMStore()->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::Type::REMOVED;
}
effect.mMinMagnitude *= magnitudeMult;
effect.mMaxMagnitude *= magnitudeMult;
}
return MWMechanics::MagicApplicationResult::Type::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& affectedHealth, 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().getESMStore()->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())
{
std::string_view marker
= (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker";
world->teleportToClosestMarker(target, ESM::RefId::stringRefId(marker));
if (!caster.isEmpty())
{
MWRender::Animation* anim = world->getAnimation(caster);
anim->removeEffect(effect.mEffectId);
const ESM::Static* fx
= world->getStore().get<ESM::Static>().search(ESM::RefId::stringRefId("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)
{
ESM::RefId dest = markedCell->getCell()->getId();
MWWorld::ActionTeleport action(dest, 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();
}
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(AiSetting::Flee);
stat.setModifier(static_cast<int>(stat.getModifier() + effect.mMagnitude));
creatureStats.setAiSetting(AiSetting::Flee, stat);
}
break;
case ESM::MagicEffect::FrenzyCreature:
case ESM::MagicEffect::FrenzyHumanoid:
modifyAiSetting(
target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::CalmCreature:
case ESM::MagicEffect::CalmHumanoid:
modifyAiSetting(
target, effect, ESM::MagicEffect::CalmCreature, AiSetting::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, AiSetting::Flee, effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::RallyCreature:
case ESM::MagicEffect::RallyHumanoid:
modifyAiSetting(
target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, -effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::Sound:
if (target == getPlayer())
{
const auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
float volume = std::clamp(
(magnitudes.getOrDefault(effect.mEffectId).getModifier() + effect.mMagnitude) / 100.f, 0.f,
1.f);
MWBase::Environment::get().getSoundManager()->playSound3D(target,
ESM::RefId::stringRefId("magic sound"), volume, 1.f, MWSound::Type::Sfx,
MWSound::PlayMode::LoopNoEnv);
}
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(ESM::RefId::stringRefId(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(ESM::RefId::stringRefId(
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
{
adjustDynamicStat(
target, index, -effect.mMagnitude, index == 2 && Settings::game().mUncappedDamageFatigue);
if (index == 0)
receivedMagicDamage = affectedHealth = 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(ESM::Skill::indexToRefId(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:
affectedHealth = true;
[[fallthrough]];
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() || target.getCell()->isQuasiExterior())
|| godmode)
break;
const float sunRisen = world->getSunPercentage();
static float fMagicSunBlockedMult
= world->getStore().get<ESM::GameSetting>().find("fMagicSunBlockedMult")->mValue.getFloat();
const float damageScale = std::clamp(
std::max(world->getSunVisibility() * sunRisen, fMagicSunBlockedMult * sunRisen), 0.f, 1.f);
float damage = effect.mMagnitude * damageScale;
adjustDynamicStat(target, 0, -damage);
if (damage > 0.f)
receivedMagicDamage = affectedHealth = true;
}
break;
case ESM::MagicEffect::DrainHealth:
case ESM::MagicEffect::DrainMagicka:
case ESM::MagicEffect::DrainFatigue:
if (!godmode)
{
int index = effect.mEffectId - ESM::MagicEffect::DrainHealth;
adjustDynamicStat(
target, index, -effect.mMagnitude, Settings::game().mUncappedDamageFatigue && index == 2);
if (index == 0)
receivedMagicDamage = affectedHealth = 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);
auto attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
AttributeValue attr = creatureStats.getAttribute(attribute);
attr.setBase(attr.getBase() + effect.mMagnitude);
creatureStats.setAttribute(attribute, 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(ESM::Skill::indexToRefId(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 = affectedHealth = 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;
bool affectedHealth = 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,
affectedHealth, recalculateMagicka);
}
if (target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth };
}
else if (shouldRemoveEffect(target, effect))
{
onMagicEffectRemoved(target, spellParams, effect);
return { MagicApplicationResult::Type::REMOVED, receivedMagicDamage, affectedHealth };
}
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::Type::APPLIED, receivedMagicDamage, affectedHealth };
}
else if (!dt)
return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth };
}
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().isLocked())
{
MWBase::Environment::get().getSoundManager()->playSound3D(
target, ESM::RefId::stringRefId("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, ESM::RefId::stringRefId("Open Lock Fail"), 1.f, 1.f);
}
}
else
invalid = true;
}
else if (!target.getClass().isActor())
{
invalid = true;
}
else
{
// Morrowind.exe doesn't apply magic effects while the menu is open, we do because we like to see stats
// updated instantly. We don't want to teleport instantly though
if (!dt
&& (effect.mEffectId == ESM::MagicEffect::Recall
|| effect.mEffectId == ESM::MagicEffect::DivineIntervention
|| effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention))
return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth };
auto& stats = target.getClass().getCreatureStats(target);
auto& magnitudes = stats.getMagicEffects();
if (spellParams.getType() != ESM::ActiveSpells::Type_Ability
&& !(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
{
MagicApplicationResult::Type result
= applyProtections(target, caster, spellParams, effect, magicEffect);
if (result != MagicApplicationResult::Type::APPLIED)
return { result, receivedMagicDamage, affectedHealth };
}
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::Type::APPLIED, receivedMagicDamage, affectedHealth };
}
}
if (effect.mEffectId == ESM::MagicEffect::Corprus)
spellParams.worsen();
else
applyMagicEffect(target, caster, spellParams, effect, invalid, receivedMagicDamage, affectedHealth,
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 (recalculateMagicka)
target.getClass().getCreatureStats(target).recalculateMagicka();
return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth };
}
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.getOrDefault(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.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f)
target.getClass().getInventoryStore(target).autoEquip();
break;
case ESM::MagicEffect::TurnUndead:
{
auto& creatureStats = target.getClass().getCreatureStats(target);
Stat<int> stat = creatureStats.getAiSetting(AiSetting::Flee);
stat.setModifier(static_cast<int>(stat.getModifier() - effect.mMagnitude));
creatureStats.setAiSetting(AiSetting::Flee, stat);
}
break;
case ESM::MagicEffect::FrenzyCreature:
case ESM::MagicEffect::FrenzyHumanoid:
modifyAiSetting(
target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, -effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::CalmCreature:
case ESM::MagicEffect::CalmHumanoid:
modifyAiSetting(
target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::DemoralizeCreature:
case ESM::MagicEffect::DemoralizeHumanoid:
modifyAiSetting(
target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, -effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::NightEye:
{
const MWMechanics::EffectParam nightEye = magnitudes.getOrDefault(effect.mEffectId);
if (nightEye.getMagnitude() < 0.f && nightEye.getBase() < 0)
{
// The PCVisionBonus functions are different from every other magic effect function in that they
// clamp the value to [0, 1]. Morrowind.exe applies the same clamping to the night-eye effect, which
// can create situations where an effect is still active (i.e. shown in the menu) but the screen is
// no longer bright. Modifying the base value here should prevent that while preserving their
// function.
float delta = std::clamp(-nightEye.getMagnitude(), 0.f, -static_cast<float>(nightEye.getBase()));
magnitudes.modifyBase(effect.mEffectId, static_cast<int>(delta));
}
}
break;
case ESM::MagicEffect::RallyCreature:
case ESM::MagicEffect::RallyHumanoid:
modifyAiSetting(
target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, effect.mMagnitude, invalid);
break;
case ESM::MagicEffect::Sound:
if (magnitudes.getOrDefault(effect.mEffectId).getModifier() <= 0.f && target == getPlayer())
MWBase::Environment::get().getSoundManager()->stopSound3D(
target, ESM::RefId::stringRefId("magic sound"));
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(ESM::RefId::stringRefId(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(
ESM::RefId::stringRefId(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);
auto attribute = static_cast<ESM::Attribute::AttributeID>(effect.mArg);
AttributeValue attr = creatureStats.getAttribute(attribute);
attr.setBase(attr.getBase() - effect.mMagnitude);
creatureStats.setAttribute(attribute, 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(ESM::Skill::indexToRefId(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.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f)
{
auto anim = MWBase::Environment::get().getWorld()->getAnimation(target);
if (anim)
anim->removeEffect(effect.mEffectId);
}
}
}