mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-25 15:35:23 +00:00
1178 lines
52 KiB
C++
1178 lines
52 KiB
C++
#include "spelleffects.hpp"
|
|
|
|
#include <algorithm>
|
|
|
|
#include <components/esm3/loadmgef.hpp>
|
|
#include <components/misc/rng.hpp>
|
|
#include <components/settings/settings.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())
|
|
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)
|
|
{
|
|
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)
|
|
anim->addEffect("meshes\\" + fx->mModel, -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 == ¶ms;
|
|
}, 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);
|
|
}
|
|
}
|
|
|
|
}
|