diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index ff52465180..33dff30479 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -381,6 +381,11 @@ namespace MWMechanics std::optional reflected; for (auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();) { + if (it->mFlags & ESM::ActiveEffect::Flag_Remove && it->mTimeLeft <= 0.f) + { + ++it; + continue; + } auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration, context.mPlayNonLooping); if (result.mType == MagicApplicationResult::Type::REFLECTED) { @@ -490,10 +495,8 @@ namespace MWMechanics { if (merge(found->mEffects, spell.mEffects)) return; - auto params = *found; - mSpells.erase(found); - for (const auto& effect : params.mEffects) - onMagicEffectRemoved(ptr, params, effect); + for (auto& effect : found->mEffects) + effect.mTimeLeft = 0.f; } } initParams(ptr, spell, context); diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index f62609d6e5..eef3d50ddb 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -428,551 +428,795 @@ namespace namespace MWMechanics { - - ESM::ActiveEffect::Flags applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, - const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& receivedMagicDamage, - bool& affectedHealth, bool& recalculateMagicka) + namespace { - const auto world = MWBase::Environment::get().getWorld(); - const bool godmode = target == getPlayer() && world->getGodModeState(); - if (effect.mEffectId == ESM::MagicEffect::CureCommonDisease) - purgePermanent(target, &Spells::purgeCommonDisease, ESM::Spell::ST_Disease); - else if (effect.mEffectId == ESM::MagicEffect::CureBlightDisease) - purgePermanent(target, &Spells::purgeBlightDisease, ESM::Spell::ST_Blight); - else if (effect.mEffectId == ESM::MagicEffect::RemoveCurse) - purgePermanent(target, &Spells::purgeCurses, ESM::Spell::ST_Curse); - else if (effect.mEffectId == ESM::MagicEffect::CureCorprusDisease) - target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Corprus); - else if (effect.mEffectId == ESM::MagicEffect::CurePoison) - target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Poison); - else if (effect.mEffectId == ESM::MagicEffect::CureParalyzation) - target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( - target, ESM::MagicEffect::Paralyze); - else if (effect.mEffectId == ESM::MagicEffect::Dispel) - // Dispel removes entire spells at once - target.getClass().getCreatureStats(target).getActiveSpells().purge( - [magnitude = effect.mMagnitude](const ActiveSpells::ActiveSpellParams& params) { - if (params.hasFlag(ESM::ActiveSpells::Flag_Temporary)) - { - const ESM::Spell* spell = params.getSpell(); - if (spell && spell->mData.mType == ESM::Spell::ST_Spell) + ESM::ActiveEffect::Flags applyActorMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, + const ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, bool& receivedMagicDamage, + bool& affectedHealth, bool& recalculateMagicka) + { + const auto world = MWBase::Environment::get().getWorld(); + const bool godmode = target == getPlayer() && world->getGodModeState(); + if (effect.mEffectId == ESM::MagicEffect::CureCommonDisease) + purgePermanent(target, &Spells::purgeCommonDisease, ESM::Spell::ST_Disease); + else if (effect.mEffectId == ESM::MagicEffect::CureBlightDisease) + purgePermanent(target, &Spells::purgeBlightDisease, ESM::Spell::ST_Blight); + else if (effect.mEffectId == ESM::MagicEffect::RemoveCurse) + purgePermanent(target, &Spells::purgeCurses, ESM::Spell::ST_Curse); + else if (effect.mEffectId == ESM::MagicEffect::CureCorprusDisease) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( + target, ESM::MagicEffect::Corprus); + else if (effect.mEffectId == ESM::MagicEffect::CurePoison) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( + target, ESM::MagicEffect::Poison); + else if (effect.mEffectId == ESM::MagicEffect::CureParalyzation) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( + target, ESM::MagicEffect::Paralyze); + else if (effect.mEffectId == ESM::MagicEffect::Dispel) + // Dispel removes entire spells at once + target.getClass().getCreatureStats(target).getActiveSpells().purge( + [magnitude = effect.mMagnitude](const ActiveSpells::ActiveSpellParams& params) { + if (params.hasFlag(ESM::ActiveSpells::Flag_Temporary)) { - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - return Misc::Rng::roll0to99(prng) < magnitude; + const ESM::Spell* spell = params.getSpell(); + 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); - else if (effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention - || effect.mEffectId == ESM::MagicEffect::DivineIntervention) - { - if (target != getPlayer()) - return ESM::ActiveEffect::Flag_Invalid; - else if (world->isTeleportingEnabled()) + return false; + }, + target); + else if (effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention + || effect.mEffectId == ESM::MagicEffect::DivineIntervention) { - std::string_view marker - = (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; - world->teleportToClosestMarker(target, ESM::RefId::stringRefId(marker)); - if (!caster.isEmpty()) + if (target != getPlayer()) + return ESM::ActiveEffect::Flag_Invalid; + else if (world->isTeleportingEnabled()) { - MWRender::Animation* anim = world->getAnimation(caster); - anim->removeEffect(effect.mEffectId.getRefIdString()); - const ESM::Static* fx - = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_end")); - if (fx != nullptr) - { - const VFS::Path::Normalized fxModel - = Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(fx->mModel)); - anim->addEffect(fxModel.value(), ""); - } - } - } - else if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - } - else if (effect.mEffectId == ESM::MagicEffect::Mark) - { - if (target != getPlayer()) - return ESM::ActiveEffect::Flag_Invalid; - else if (world->isTeleportingEnabled()) - world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition()); - else if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - } - else if (effect.mEffectId == ESM::MagicEffect::Recall) - { - if (target != getPlayer()) - return ESM::ActiveEffect::Flag_Invalid; - 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); + 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.getRefIdString()); - } - } - } - else if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - } - else if (effect.mEffectId == ESM::MagicEffect::CommandCreature - || effect.mEffectId == ESM::MagicEffect::CommandHumanoid) - { - if (caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() - || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc()) - return ESM::ActiveEffect::Flag_Invalid; - else if (effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) - { - MWMechanics::AiFollow package(caster, true); - target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); - } - } - else if (effect.mEffectId == ESM::MagicEffect::ExtraSpell) - { - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - if (target != getPlayer()) - { - auto& store = target.getClass().getInventoryStore(target); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - // Unequip everything except weapons, torches, and pants - switch (slot) - { - case MWWorld::InventoryStore::Slot_Ammunition: - case MWWorld::InventoryStore::Slot_CarriedRight: - case MWWorld::InventoryStore::Slot_Pants: - continue; - case MWWorld::InventoryStore::Slot_CarriedLeft: + const ESM::Static* fx + = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_end")); + if (fx != nullptr) { - auto carried = store.getSlot(slot); - if (carried == store.end() || carried.getType() != MWWorld::ContainerStore::Type_Armor) - continue; - [[fallthrough]]; + const VFS::Path::Normalized fxModel + = Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(fx->mModel)); + anim->addEffect(fxModel.value(), ""); + } + } + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + } + else if (effect.mEffectId == ESM::MagicEffect::Mark) + { + if (target != getPlayer()) + return ESM::ActiveEffect::Flag_Invalid; + else if (world->isTeleportingEnabled()) + world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition()); + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + } + else if (effect.mEffectId == ESM::MagicEffect::Recall) + { + if (target != getPlayer()) + return ESM::ActiveEffect::Flag_Invalid; + 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.getRefIdString()); + } + } + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + } + else if (effect.mEffectId == ESM::MagicEffect::CommandCreature + || effect.mEffectId == ESM::MagicEffect::CommandHumanoid) + { + if (caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() + || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc()) + return ESM::ActiveEffect::Flag_Invalid; + else if (effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) + { + MWMechanics::AiFollow package(caster, true); + target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); + } + } + else if (effect.mEffectId == ESM::MagicEffect::ExtraSpell) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + if (target != getPlayer()) + { + auto& store = target.getClass().getInventoryStore(target); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + // Unequip everything except weapons, torches, and pants + switch (slot) + { + case MWWorld::InventoryStore::Slot_Ammunition: + case MWWorld::InventoryStore::Slot_CarriedRight: + case MWWorld::InventoryStore::Slot_Pants: + continue; + case MWWorld::InventoryStore::Slot_CarriedLeft: + { + auto carried = store.getSlot(slot); + if (carried == store.end() || carried.getType() != MWWorld::ContainerStore::Type_Armor) + continue; + [[fallthrough]]; + } + default: + store.unequipSlot(slot); } - default: - store.unequipSlot(slot); } } } - } - else if (effect.mEffectId == ESM::MagicEffect::TurnUndead) - { - if (target.getClass().isNpc() || target.get()->mBase->mData.mType != ESM::Creature::Undead) - return ESM::ActiveEffect::Flag_Invalid; - else + else if (effect.mEffectId == ESM::MagicEffect::TurnUndead) { - auto& creatureStats = target.getClass().getCreatureStats(target); - Stat stat = creatureStats.getAiSetting(AiSetting::Flee); - stat.setModifier(static_cast(stat.getModifier() + effect.mMagnitude)); - creatureStats.setAiSetting(AiSetting::Flee, stat); - } - } - else if (effect.mEffectId == ESM::MagicEffect::FrenzyCreature - || effect.mEffectId == ESM::MagicEffect::FrenzyHumanoid) - return modifyAiSetting( - target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::CalmCreature - || effect.mEffectId == ESM::MagicEffect::CalmHumanoid) - { - ESM::ActiveEffect::Flags applied - = modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, -effect.mMagnitude); - if (applied != ESM::ActiveEffect::Flag_Applied) - return applied; - if (effect.mMagnitude > 0) - { - auto& creatureStats = target.getClass().getCreatureStats(target); - creatureStats.getAiSequence().stopCombat(); - } - } - else if (effect.mEffectId == ESM::MagicEffect::DemoralizeCreature - || effect.mEffectId == ESM::MagicEffect::DemoralizeHumanoid) - return modifyAiSetting( - target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::RallyCreature - || effect.mEffectId == ESM::MagicEffect::RallyHumanoid) - return modifyAiSetting( - target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, -effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::Charm) - { - if (!target.getClass().isNpc()) - return ESM::ActiveEffect::Flag_Invalid; - } - else if (effect.mEffectId == 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); - } - } - else if (effect.mEffectId == ESM::MagicEffect::SummonScamp - || effect.mEffectId == ESM::MagicEffect::SummonClannfear - || effect.mEffectId == ESM::MagicEffect::SummonDaedroth - || effect.mEffectId == ESM::MagicEffect::SummonDremora - || effect.mEffectId == ESM::MagicEffect::SummonAncestralGhost - || effect.mEffectId == ESM::MagicEffect::SummonSkeletalMinion - || effect.mEffectId == ESM::MagicEffect::SummonBonewalker - || effect.mEffectId == ESM::MagicEffect::SummonGreaterBonewalker - || effect.mEffectId == ESM::MagicEffect::SummonBonelord - || effect.mEffectId == ESM::MagicEffect::SummonWingedTwilight - || effect.mEffectId == ESM::MagicEffect::SummonHunger - || effect.mEffectId == ESM::MagicEffect::SummonGoldenSaint - || effect.mEffectId == ESM::MagicEffect::SummonFlameAtronach - || effect.mEffectId == ESM::MagicEffect::SummonFrostAtronach - || effect.mEffectId == ESM::MagicEffect::SummonStormAtronach - || effect.mEffectId == ESM::MagicEffect::SummonCenturionSphere - || effect.mEffectId == ESM::MagicEffect::SummonFabricant || effect.mEffectId == ESM::MagicEffect::SummonWolf - || effect.mEffectId == ESM::MagicEffect::SummonBear || effect.mEffectId == ESM::MagicEffect::SummonBonewolf - || effect.mEffectId == ESM::MagicEffect::SummonCreature04 - || effect.mEffectId == ESM::MagicEffect::SummonCreature05) - { - if (!target.isInCell()) - return ESM::ActiveEffect::Flag_Invalid; - effect.mArg = summonCreature(effect.mEffectId, target); - } - else if (effect.mEffectId == ESM::MagicEffect::BoundGloves) - { - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - addBoundItem( - ESM::RefId::stringRefId( - world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString()), - target); - addBoundItem( - ESM::RefId::stringRefId( - world->getStore().get().find("sMagicBoundLeftGauntletID")->mValue.getString()), - target); - } - else if (effect.mEffectId == ESM::MagicEffect::BoundDagger - || effect.mEffectId == ESM::MagicEffect::BoundLongsword || effect.mEffectId == ESM::MagicEffect::BoundMace - || effect.mEffectId == ESM::MagicEffect::BoundBattleAxe || effect.mEffectId == ESM::MagicEffect::BoundSpear - || effect.mEffectId == ESM::MagicEffect::BoundLongbow || effect.mEffectId == ESM::MagicEffect::BoundCuirass - || effect.mEffectId == ESM::MagicEffect::BoundHelm || effect.mEffectId == ESM::MagicEffect::BoundBoots - || effect.mEffectId == ESM::MagicEffect::BoundShield) - { - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - std::string_view item = getBoundItemsMap().at(effect.mEffectId); - const MWWorld::Store& gmst = world->getStore().get(); - const ESM::RefId itemId = ESM::RefId::stringRefId(gmst.find(item)->mValue.getString()); - if (!addBoundItem(itemId, target)) - effect.mTimeLeft = 0.f; - } - else if (effect.mEffectId == ESM::MagicEffect::FireDamage || effect.mEffectId == ESM::MagicEffect::ShockDamage - || effect.mEffectId == ESM::MagicEffect::FrostDamage || effect.mEffectId == ESM::MagicEffect::DamageHealth - || effect.mEffectId == ESM::MagicEffect::Poison || effect.mEffectId == ESM::MagicEffect::DamageMagicka - || effect.mEffectId == ESM::MagicEffect::DamageFatigue) - { - if (!godmode) - { - auto targetStat = Stats::Health; - if (effect.mEffectId == ESM::MagicEffect::DamageMagicka) - targetStat = Stats::Magicka; - else if (effect.mEffectId == ESM::MagicEffect::DamageFatigue) - targetStat = Stats::Fatigue; - // Damage "Dynamic" abilities reduce the base value - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, targetStat, -effect.mMagnitude); + if (target.getClass().isNpc() + || target.get()->mBase->mData.mType != ESM::Creature::Undead) + return ESM::ActiveEffect::Flag_Invalid; else { - adjustDynamicStat(target, targetStat, -effect.mMagnitude, - targetStat == Stats::Fatigue && Settings::game().mUncappedDamageFatigue); - if (targetStat == Stats::Health) + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(AiSetting::Flee); + stat.setModifier(static_cast(stat.getModifier() + effect.mMagnitude)); + creatureStats.setAiSetting(AiSetting::Flee, stat); + } + } + else if (effect.mEffectId == ESM::MagicEffect::FrenzyCreature + || effect.mEffectId == ESM::MagicEffect::FrenzyHumanoid) + return modifyAiSetting( + target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::CalmCreature + || effect.mEffectId == ESM::MagicEffect::CalmHumanoid) + { + ESM::ActiveEffect::Flags applied = modifyAiSetting( + target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, -effect.mMagnitude); + if (applied != ESM::ActiveEffect::Flag_Applied) + return applied; + if (effect.mMagnitude > 0) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + creatureStats.getAiSequence().stopCombat(); + } + } + else if (effect.mEffectId == ESM::MagicEffect::DemoralizeCreature + || effect.mEffectId == ESM::MagicEffect::DemoralizeHumanoid) + return modifyAiSetting( + target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::RallyCreature + || effect.mEffectId == ESM::MagicEffect::RallyHumanoid) + return modifyAiSetting( + target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, -effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::Charm) + { + if (!target.getClass().isNpc()) + return ESM::ActiveEffect::Flag_Invalid; + } + else if (effect.mEffectId == 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); + } + } + else if (effect.mEffectId == ESM::MagicEffect::SummonScamp + || effect.mEffectId == ESM::MagicEffect::SummonClannfear + || effect.mEffectId == ESM::MagicEffect::SummonDaedroth + || effect.mEffectId == ESM::MagicEffect::SummonDremora + || effect.mEffectId == ESM::MagicEffect::SummonAncestralGhost + || effect.mEffectId == ESM::MagicEffect::SummonSkeletalMinion + || effect.mEffectId == ESM::MagicEffect::SummonBonewalker + || effect.mEffectId == ESM::MagicEffect::SummonGreaterBonewalker + || effect.mEffectId == ESM::MagicEffect::SummonBonelord + || effect.mEffectId == ESM::MagicEffect::SummonWingedTwilight + || effect.mEffectId == ESM::MagicEffect::SummonHunger + || effect.mEffectId == ESM::MagicEffect::SummonGoldenSaint + || effect.mEffectId == ESM::MagicEffect::SummonFlameAtronach + || effect.mEffectId == ESM::MagicEffect::SummonFrostAtronach + || effect.mEffectId == ESM::MagicEffect::SummonStormAtronach + || effect.mEffectId == ESM::MagicEffect::SummonCenturionSphere + || effect.mEffectId == ESM::MagicEffect::SummonFabricant + || effect.mEffectId == ESM::MagicEffect::SummonWolf || effect.mEffectId == ESM::MagicEffect::SummonBear + || effect.mEffectId == ESM::MagicEffect::SummonBonewolf + || effect.mEffectId == ESM::MagicEffect::SummonCreature04 + || effect.mEffectId == ESM::MagicEffect::SummonCreature05) + { + if (!target.isInCell()) + return ESM::ActiveEffect::Flag_Invalid; + effect.mArg = summonCreature(effect.mEffectId, target); + } + else if (effect.mEffectId == ESM::MagicEffect::BoundGloves) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + addBoundItem(ESM::RefId::stringRefId(world->getStore() + .get() + .find("sMagicBoundRightGauntletID") + ->mValue.getString()), + target); + addBoundItem(ESM::RefId::stringRefId(world->getStore() + .get() + .find("sMagicBoundLeftGauntletID") + ->mValue.getString()), + target); + } + else if (effect.mEffectId == ESM::MagicEffect::BoundDagger + || effect.mEffectId == ESM::MagicEffect::BoundLongsword + || effect.mEffectId == ESM::MagicEffect::BoundMace + || effect.mEffectId == ESM::MagicEffect::BoundBattleAxe + || effect.mEffectId == ESM::MagicEffect::BoundSpear + || effect.mEffectId == ESM::MagicEffect::BoundLongbow + || effect.mEffectId == ESM::MagicEffect::BoundCuirass || effect.mEffectId == ESM::MagicEffect::BoundHelm + || effect.mEffectId == ESM::MagicEffect::BoundBoots + || effect.mEffectId == ESM::MagicEffect::BoundShield) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + std::string_view item = getBoundItemsMap().at(effect.mEffectId); + const MWWorld::Store& gmst = world->getStore().get(); + const ESM::RefId itemId = ESM::RefId::stringRefId(gmst.find(item)->mValue.getString()); + if (!addBoundItem(itemId, target)) + effect.mTimeLeft = 0.f; + } + else if (effect.mEffectId == ESM::MagicEffect::FireDamage + || effect.mEffectId == ESM::MagicEffect::ShockDamage + || effect.mEffectId == ESM::MagicEffect::FrostDamage + || effect.mEffectId == ESM::MagicEffect::DamageHealth || effect.mEffectId == ESM::MagicEffect::Poison + || effect.mEffectId == ESM::MagicEffect::DamageMagicka + || effect.mEffectId == ESM::MagicEffect::DamageFatigue) + { + if (!godmode) + { + auto targetStat = Stats::Health; + if (effect.mEffectId == ESM::MagicEffect::DamageMagicka) + targetStat = Stats::Magicka; + else if (effect.mEffectId == ESM::MagicEffect::DamageFatigue) + targetStat = Stats::Fatigue; + // Damage "Dynamic" abilities reduce the base value + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, targetStat, -effect.mMagnitude); + else + { + adjustDynamicStat(target, targetStat, -effect.mMagnitude, + targetStat == Stats::Fatigue && Settings::game().mUncappedDamageFatigue); + if (targetStat == Stats::Health) + receivedMagicDamage = affectedHealth = true; + } + } + } + else if (effect.mEffectId == ESM::MagicEffect::DamageAttribute) + { + if (!godmode) + damageAttribute(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::DamageSkill) + { + if (!godmode && target.getClass().isNpc()) + { + // Damage Skill abilities reduce base skill :todd: + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + { + auto& npcStats = target.getClass().getNpcStats(target); + SkillValue& skill = npcStats.getSkill(effect.getSkillOrAttribute()); + // Damage Skill abilities reduce base skill :todd: + skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f)); + } + else + damageSkill(target, effect, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::RestoreAttribute) + restoreAttribute(target, effect, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::RestoreSkill) + { + if (target.getClass().isNpc()) + restoreSkill(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::RestoreHealth) + { + affectedHealth = true; + adjustDynamicStat(target, Stats::Health, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::RestoreMagicka) + adjustDynamicStat(target, Stats::Magicka, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::RestoreFatigue) + adjustDynamicStat(target, Stats::Fatigue, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::SunDamage) + { + //// isInCell shouldn't be needed, but updateActor called during game start + if (!godmode && target.isInCell() + && (target.getCell()->isExterior() || target.getCell()->isQuasiExterior())) + { + const float sunRisen = world->getSunPercentage(); + static float fMagicSunBlockedMult + = world->getStore().get().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; } } - } - else if (effect.mEffectId == ESM::MagicEffect::DamageAttribute) - { - if (!godmode) - damageAttribute(target, effect, effect.mMagnitude); - } - else if (effect.mEffectId == ESM::MagicEffect::DamageSkill) - { - if (!godmode && target.getClass().isNpc()) + else if (effect.mEffectId == ESM::MagicEffect::DrainHealth) { - // Damage Skill abilities reduce base skill :todd: - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - { - auto& npcStats = target.getClass().getNpcStats(target); - SkillValue& skill = npcStats.getSkill(effect.getSkillOrAttribute()); - // Damage Skill abilities reduce base skill :todd: - skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f)); - } + if (godmode) + return ESM::ActiveEffect::Flag_Remove; else - damageSkill(target, effect, effect.mMagnitude); - } - } - else if (effect.mEffectId == ESM::MagicEffect::RestoreAttribute) - restoreAttribute(target, effect, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::RestoreSkill) - { - if (target.getClass().isNpc()) - restoreSkill(target, effect, effect.mMagnitude); - } - else if (effect.mEffectId == ESM::MagicEffect::RestoreHealth) - { - affectedHealth = true; - adjustDynamicStat(target, Stats::Health, effect.mMagnitude); - } - else if (effect.mEffectId == ESM::MagicEffect::RestoreMagicka) - adjustDynamicStat(target, Stats::Magicka, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::RestoreFatigue) - adjustDynamicStat(target, Stats::Fatigue, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::SunDamage) - { - //// isInCell shouldn't be needed, but updateActor called during game start - if (!godmode && target.isInCell() - && (target.getCell()->isExterior() || target.getCell()->isQuasiExterior())) - { - const float sunRisen = world->getSunPercentage(); - static float fMagicSunBlockedMult - = world->getStore().get().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) + { + // Unlike Absorb and Damage effects Drain effects can bring stats below zero + adjustDynamicStat(target, Stats::Health, -effect.mMagnitude, true); receivedMagicDamage = affectedHealth = true; + } } - } - else if (effect.mEffectId == ESM::MagicEffect::DrainHealth) - { - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else + else if (effect.mEffectId == ESM::MagicEffect::DrainMagicka) { - // Unlike Absorb and Damage effects Drain effects can bring stats below zero - adjustDynamicStat(target, Stats::Health, -effect.mMagnitude, true); - receivedMagicDamage = affectedHealth = true; + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + // Unlike Absorb and Damage effects Drain effects can bring stats below zero + adjustDynamicStat(target, Stats::Magicka, -effect.mMagnitude, true); + } } - } - else if (effect.mEffectId == ESM::MagicEffect::DrainMagicka) - { - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else + else if (effect.mEffectId == ESM::MagicEffect::DrainFatigue) { - // Unlike Absorb and Damage effects Drain effects can bring stats below zero - adjustDynamicStat(target, Stats::Magicka, -effect.mMagnitude, true); + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + // Unlike Absorb and Damage effects Drain effects can bring stats below zero + adjustDynamicStat(target, Stats::Fatigue, -effect.mMagnitude, true); + } } - } - else if (effect.mEffectId == ESM::MagicEffect::DrainFatigue) - { - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else - { - // Unlike Absorb and Damage effects Drain effects can bring stats below zero - adjustDynamicStat(target, Stats::Fatigue, -effect.mMagnitude, true); - } - } - else if (effect.mEffectId == ESM::MagicEffect::FortifyHealth) - { - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, Stats::Health, effect.mMagnitude); - else - adjustDynamicStat(target, Stats::Health, effect.mMagnitude, false, true); - } - else if (effect.mEffectId == ESM::MagicEffect::FortifyMagicka) - { - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, Stats::Magicka, effect.mMagnitude); - else - adjustDynamicStat(target, Stats::Magicka, effect.mMagnitude, false, true); - } - else if (effect.mEffectId == ESM::MagicEffect::FortifyFatigue) - { - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, Stats::Fatigue, effect.mMagnitude); - else - adjustDynamicStat(target, Stats::Fatigue, effect.mMagnitude, false, true); - } - else if (effect.mEffectId == ESM::MagicEffect::DrainAttribute) - { - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - damageAttribute(target, effect, effect.mMagnitude); - } - else if (effect.mEffectId == ESM::MagicEffect::FortifyAttribute) - { - // Abilities affect base stats, but not for drain - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - { - auto& creatureStats = target.getClass().getCreatureStats(target); - auto attribute = effect.getSkillOrAttribute(); - AttributeValue attr = creatureStats.getAttribute(attribute); - attr.setBase(attr.getBase() + effect.mMagnitude); - creatureStats.setAttribute(attribute, attr); - } - else - fortifyAttribute(target, effect, effect.mMagnitude); - } - else if (effect.mEffectId == ESM::MagicEffect::DrainSkill) - { - if (godmode || !target.getClass().isNpc()) - return ESM::ActiveEffect::Flag_Remove; - damageSkill(target, effect, effect.mMagnitude); - } - else if (effect.mEffectId == ESM::MagicEffect::FortifySkill) - { - if (target.getClass().isNpc()) + else if (effect.mEffectId == ESM::MagicEffect::FortifyHealth) { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Health, effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Health, effect.mMagnitude, false, true); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyMagicka) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Magicka, effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Magicka, effect.mMagnitude, false, true); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyFatigue) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Fatigue, effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Fatigue, effect.mMagnitude, false, true); + } + else if (effect.mEffectId == ESM::MagicEffect::DrainAttribute) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + damageAttribute(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyAttribute) + { + // Abilities affect base stats, but not for drain if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { - // Abilities affect base stats, but not for drain - auto& npcStats = target.getClass().getNpcStats(target); - auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); - skill.setBase(skill.getBase() + effect.mMagnitude); + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attribute = effect.getSkillOrAttribute(); + AttributeValue attr = creatureStats.getAttribute(attribute); + attr.setBase(attr.getBase() + effect.mMagnitude); + creatureStats.setAttribute(attribute, attr); } else - fortifySkill(target, effect, effect.mMagnitude); + fortifyAttribute(target, effect, effect.mMagnitude); } - } - else if (effect.mEffectId == ESM::MagicEffect::FortifyMaximumMagicka) - recalculateMagicka = true; - else if (effect.mEffectId == ESM::MagicEffect::AbsorbHealth) - { - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else + else if (effect.mEffectId == ESM::MagicEffect::DrainSkill) { - adjustDynamicStat(target, Stats::Health, -effect.mMagnitude); - if (!caster.isEmpty()) - { - adjustDynamicStat(caster, Stats::Health, effect.mMagnitude); - } - receivedMagicDamage = affectedHealth = true; + if (godmode || !target.getClass().isNpc()) + return ESM::ActiveEffect::Flag_Remove; + damageSkill(target, effect, effect.mMagnitude); } - } - else if (effect.mEffectId == ESM::MagicEffect::AbsorbMagicka) - { - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else - { - adjustDynamicStat(target, Stats::Magicka, -effect.mMagnitude); - if (!caster.isEmpty()) - adjustDynamicStat(caster, Stats::Magicka, effect.mMagnitude); - } - } - else if (effect.mEffectId == ESM::MagicEffect::AbsorbFatigue) - { - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else - { - adjustDynamicStat(target, Stats::Fatigue, -effect.mMagnitude); - if (!caster.isEmpty()) - adjustDynamicStat(caster, Stats::Fatigue, effect.mMagnitude); - } - } - else if (effect.mEffectId == ESM::MagicEffect::AbsorbAttribute) - { - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else - { - damageAttribute(target, effect, effect.mMagnitude); - if (!caster.isEmpty()) - fortifyAttribute(caster, effect, effect.mMagnitude); - } - } - else if (effect.mEffectId == ESM::MagicEffect::AbsorbSkill) - { - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else + else if (effect.mEffectId == ESM::MagicEffect::FortifySkill) { if (target.getClass().isNpc()) - damageSkill(target, effect, effect.mMagnitude); - if (!caster.isEmpty() && caster.getClass().isNpc()) - fortifySkill(caster, effect, effect.mMagnitude); - } - } - else if (effect.mEffectId == ESM::MagicEffect::DisintegrateArmor) - { - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - if (!godmode) - { - static const std::array 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; + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + { + // Abilities affect base stats, but not for drain + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); + skill.setBase(skill.getBase() + effect.mMagnitude); + } + else + fortifySkill(target, effect, effect.mMagnitude); } } + else if (effect.mEffectId == ESM::MagicEffect::FortifyMaximumMagicka) + recalculateMagicka = true; + else if (effect.mEffectId == ESM::MagicEffect::AbsorbHealth) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + adjustDynamicStat(target, Stats::Health, -effect.mMagnitude); + if (!caster.isEmpty()) + { + adjustDynamicStat(caster, Stats::Health, effect.mMagnitude); + } + receivedMagicDamage = affectedHealth = true; + } + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbMagicka) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + adjustDynamicStat(target, Stats::Magicka, -effect.mMagnitude); + if (!caster.isEmpty()) + adjustDynamicStat(caster, Stats::Magicka, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbFatigue) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + adjustDynamicStat(target, Stats::Fatigue, -effect.mMagnitude); + if (!caster.isEmpty()) + adjustDynamicStat(caster, Stats::Fatigue, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbAttribute) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + damageAttribute(target, effect, effect.mMagnitude); + if (!caster.isEmpty()) + fortifyAttribute(caster, effect, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbSkill) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + if (target.getClass().isNpc()) + damageSkill(target, effect, effect.mMagnitude); + if (!caster.isEmpty() && caster.getClass().isNpc()) + fortifySkill(caster, effect, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::DisintegrateArmor) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + if (!godmode) + { + static const std::array 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; + } + } + } + else if (effect.mEffectId == ESM::MagicEffect::DisintegrateWeapon) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + if (!godmode) + disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude); + } + return ESM::ActiveEffect::Flag_Applied; } - else if (effect.mEffectId == ESM::MagicEffect::DisintegrateWeapon) - { - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - if (!godmode) - disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude); - } - return ESM::ActiveEffect::Flag_Applied; - } - bool shouldRemoveEffect(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect) - { - if (effect.mFlags & ESM::ActiveEffect::Flag_Invalid) - return true; - const auto world = MWBase::Environment::get().getWorld(); - if (effect.mEffectId == ESM::MagicEffect::Levitate) + bool shouldRemoveEffect(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect) { - if (!world->isLevitationEnabled()) - { - if (target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + if (effect.mFlags & ESM::ActiveEffect::Flag_Invalid) return true; + const auto world = MWBase::Environment::get().getWorld(); + if (effect.mEffectId == ESM::MagicEffect::Levitate) + { + if (!world->isLevitationEnabled()) + { + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + return true; + } + } + else if (effect.mEffectId == ESM::MagicEffect::DivineIntervention + || effect.mEffectId == ESM::MagicEffect::Recall + || effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention) + { + return effect.mFlags & ESM::ActiveEffect::Flag_Applied; + } + else if (effect.mEffectId == ESM::MagicEffect::WaterWalking) + { + if (target.getClass().isPureWaterCreature(target) && world->isSwimming(target)) + return true; + if (effect.mFlags & ESM::ActiveEffect::Flag_Applied) + return false; + if (!world->isWaterWalkingCastableOnTarget(target)) + { + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidEffect}"); + return true; + } + } + return false; + } + + void removeMagicEffect( + const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) + { + const auto world = MWBase::Environment::get().getWorld(); + const auto worldModel = MWBase::Environment::get().getWorldModel(); + auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + if (effect.mEffectId == ESM::MagicEffect::CommandCreature + || effect.mEffectId == 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(package.get())->isCommanded(); + }); + } + } + else if (effect.mEffectId == ESM::MagicEffect::ExtraSpell) + { + if (magnitudes.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f && target != getPlayer()) + target.getClass().getInventoryStore(target).autoEquip(); + } + else if (effect.mEffectId == ESM::MagicEffect::TurnUndead) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(AiSetting::Flee); + stat.setModifier(static_cast(stat.getModifier() - effect.mMagnitude)); + creatureStats.setAiSetting(AiSetting::Flee, stat); + } + else if (effect.mEffectId == ESM::MagicEffect::FrenzyCreature + || effect.mEffectId == ESM::MagicEffect::FrenzyHumanoid) + modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, -effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::CalmCreature + || effect.mEffectId == ESM::MagicEffect::CalmHumanoid) + modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::DemoralizeCreature + || effect.mEffectId == ESM::MagicEffect::DemoralizeHumanoid) + modifyAiSetting( + target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, -effect.mMagnitude); + else if (effect.mEffectId == 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(nightEye.getBase())); + magnitudes.modifyBase(effect.mEffectId, static_cast(delta)); + } + } + else if (effect.mEffectId == ESM::MagicEffect::RallyCreature + || effect.mEffectId == ESM::MagicEffect::RallyHumanoid) + modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::Sound) + { + if (magnitudes.getOrDefault(effect.mEffectId).getModifier() <= 0.f && target == getPlayer()) + MWBase::Environment::get().getSoundManager()->stopSound3D( + target, ESM::RefId::stringRefId("magic sound")); + } + else if (effect.mEffectId == ESM::MagicEffect::SummonScamp + || effect.mEffectId == ESM::MagicEffect::SummonClannfear + || effect.mEffectId == ESM::MagicEffect::SummonDaedroth + || effect.mEffectId == ESM::MagicEffect::SummonDremora + || effect.mEffectId == ESM::MagicEffect::SummonAncestralGhost + || effect.mEffectId == ESM::MagicEffect::SummonSkeletalMinion + || effect.mEffectId == ESM::MagicEffect::SummonBonewalker + || effect.mEffectId == ESM::MagicEffect::SummonGreaterBonewalker + || effect.mEffectId == ESM::MagicEffect::SummonBonelord + || effect.mEffectId == ESM::MagicEffect::SummonWingedTwilight + || effect.mEffectId == ESM::MagicEffect::SummonHunger + || effect.mEffectId == ESM::MagicEffect::SummonGoldenSaint + || effect.mEffectId == ESM::MagicEffect::SummonFlameAtronach + || effect.mEffectId == ESM::MagicEffect::SummonFrostAtronach + || effect.mEffectId == ESM::MagicEffect::SummonStormAtronach + || effect.mEffectId == ESM::MagicEffect::SummonCenturionSphere + || effect.mEffectId == ESM::MagicEffect::SummonFabricant + || effect.mEffectId == ESM::MagicEffect::SummonWolf || effect.mEffectId == ESM::MagicEffect::SummonBear + || effect.mEffectId == ESM::MagicEffect::SummonBonewolf + || effect.mEffectId == ESM::MagicEffect::SummonCreature04 + || effect.mEffectId == ESM::MagicEffect::SummonCreature05) + { + ESM::RefNum actor = effect.getActor(); + if (actor.isSet()) + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(actor); + 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 == actor) + { + summons.erase(it); + break; + } + } + } + else if (effect.mEffectId == ESM::MagicEffect::BoundGloves) + { + removeBoundItem(ESM::RefId::stringRefId(world->getStore() + .get() + .find("sMagicBoundRightGauntletID") + ->mValue.getString()), + target); + removeBoundItem(ESM::RefId::stringRefId(world->getStore() + .get() + .find("sMagicBoundLeftGauntletID") + ->mValue.getString()), + target); + } + else if (effect.mEffectId == ESM::MagicEffect::BoundDagger + || effect.mEffectId == ESM::MagicEffect::BoundLongsword + || effect.mEffectId == ESM::MagicEffect::BoundMace + || effect.mEffectId == ESM::MagicEffect::BoundBattleAxe + || effect.mEffectId == ESM::MagicEffect::BoundSpear + || effect.mEffectId == ESM::MagicEffect::BoundLongbow + || effect.mEffectId == ESM::MagicEffect::BoundCuirass || effect.mEffectId == ESM::MagicEffect::BoundHelm + || effect.mEffectId == ESM::MagicEffect::BoundBoots + || effect.mEffectId == ESM::MagicEffect::BoundShield) + { + std::string_view item = getBoundItemsMap().at(effect.mEffectId); + removeBoundItem( + ESM::RefId::stringRefId(world->getStore().get().find(item)->mValue.getString()), + target); + } + else if (effect.mEffectId == ESM::MagicEffect::DrainHealth) + adjustDynamicStat(target, Stats::Health, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::DrainMagicka) + adjustDynamicStat(target, Stats::Magicka, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::DrainFatigue) + adjustDynamicStat(target, Stats::Fatigue, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::FortifyHealth) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Health, -effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Health, -effect.mMagnitude, true); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyMagicka) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Magicka, -effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Magicka, -effect.mMagnitude, true); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyFatigue) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Fatigue, -effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Fatigue, -effect.mMagnitude, true); + } + else if (effect.mEffectId == ESM::MagicEffect::DrainAttribute) + restoreAttribute(target, effect, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::FortifyAttribute) + { + // Abilities affect base stats, but not for drain + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attribute = effect.getSkillOrAttribute(); + AttributeValue attr = creatureStats.getAttribute(attribute); + attr.setBase(attr.getBase() - effect.mMagnitude); + creatureStats.setAttribute(attribute, attr); + } + else + fortifyAttribute(target, effect, -effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::DrainSkill) + { + if (target.getClass().isNpc()) + restoreSkill(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifySkill) + { + if (target.getClass().isNpc()) + { + // Abilities affect base stats, but not for drain + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + { + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); + skill.setBase(skill.getBase() - effect.mMagnitude); + } + else + fortifySkill(target, effect, -effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyMaximumMagicka) + target.getClass().getCreatureStats(target).recalculateMagicka(); + else if (effect.mEffectId == ESM::MagicEffect::AbsorbAttribute) + { + const auto caster = worldModel->getPtr(spellParams.getCaster()); + restoreAttribute(target, effect, effect.mMagnitude); + if (!caster.isEmpty()) + fortifyAttribute(caster, effect, -effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbSkill) + { + if (target.getClass().isNpc()) + restoreSkill(target, effect, effect.mMagnitude); + const auto caster = worldModel->getPtr(spellParams.getCaster()); + if (!caster.isEmpty() && caster.getClass().isNpc()) + fortifySkill(caster, effect, -effect.mMagnitude); + } + else if (effect.mEffectId == 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); } } - else if (effect.mEffectId == ESM::MagicEffect::DivineIntervention - || effect.mEffectId == ESM::MagicEffect::Recall - || effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention) - { - return effect.mFlags & ESM::ActiveEffect::Flag_Applied; - } - else if (effect.mEffectId == ESM::MagicEffect::WaterWalking) - { - if (target.getClass().isPureWaterCreature(target) && world->isSwimming(target)) - return true; - if (effect.mFlags & ESM::ActiveEffect::Flag_Applied) - return false; - if (!world->isWaterWalkingCastableOnTarget(target)) - { - if (target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidEffect}"); - return true; - } - } - return false; } MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, @@ -989,7 +1233,7 @@ namespace MWMechanics for (auto& otherEffect : spellParams.getEffects()) { if (isCorprusEffect(otherEffect)) - applyMagicEffect(target, caster, spellParams, otherEffect, receivedMagicDamage, affectedHealth, + applyActorMagicEffect(target, caster, spellParams, otherEffect, receivedMagicDamage, affectedHealth, recalculateMagicka); } if (target == getPlayer()) @@ -1134,7 +1378,7 @@ namespace MWMechanics applied |= ESM::ActiveEffect::Flag_Applied; } else - applied |= applyMagicEffect( + applied |= applyActorMagicEffect( target, caster, spellParams, effect, receivedMagicDamage, affectedHealth, recalculateMagicka); effect.mMagnitude = magnitude; magnitudes.add(EffectKey(effect.mEffectId, effect.getSkillOrAttribute()), @@ -1155,229 +1399,6 @@ namespace MWMechanics 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(); - const auto worldModel = MWBase::Environment::get().getWorldModel(); - auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); - if (effect.mEffectId == ESM::MagicEffect::CommandCreature - || effect.mEffectId == 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(package.get())->isCommanded(); - }); - } - } - else if (effect.mEffectId == ESM::MagicEffect::ExtraSpell) - { - if (magnitudes.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f && target != getPlayer()) - target.getClass().getInventoryStore(target).autoEquip(); - } - else if (effect.mEffectId == ESM::MagicEffect::TurnUndead) - { - auto& creatureStats = target.getClass().getCreatureStats(target); - Stat stat = creatureStats.getAiSetting(AiSetting::Flee); - stat.setModifier(static_cast(stat.getModifier() - effect.mMagnitude)); - creatureStats.setAiSetting(AiSetting::Flee, stat); - } - else if (effect.mEffectId == ESM::MagicEffect::FrenzyCreature - || effect.mEffectId == ESM::MagicEffect::FrenzyHumanoid) - modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, -effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::CalmCreature - || effect.mEffectId == ESM::MagicEffect::CalmHumanoid) - modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::DemoralizeCreature - || effect.mEffectId == ESM::MagicEffect::DemoralizeHumanoid) - modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, -effect.mMagnitude); - else if (effect.mEffectId == 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(nightEye.getBase())); - magnitudes.modifyBase(effect.mEffectId, static_cast(delta)); - } - } - else if (effect.mEffectId == ESM::MagicEffect::RallyCreature - || effect.mEffectId == ESM::MagicEffect::RallyHumanoid) - modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::Sound) - { - if (magnitudes.getOrDefault(effect.mEffectId).getModifier() <= 0.f && target == getPlayer()) - MWBase::Environment::get().getSoundManager()->stopSound3D( - target, ESM::RefId::stringRefId("magic sound")); - } - else if (effect.mEffectId == ESM::MagicEffect::SummonScamp - || effect.mEffectId == ESM::MagicEffect::SummonClannfear - || effect.mEffectId == ESM::MagicEffect::SummonDaedroth - || effect.mEffectId == ESM::MagicEffect::SummonDremora - || effect.mEffectId == ESM::MagicEffect::SummonAncestralGhost - || effect.mEffectId == ESM::MagicEffect::SummonSkeletalMinion - || effect.mEffectId == ESM::MagicEffect::SummonBonewalker - || effect.mEffectId == ESM::MagicEffect::SummonGreaterBonewalker - || effect.mEffectId == ESM::MagicEffect::SummonBonelord - || effect.mEffectId == ESM::MagicEffect::SummonWingedTwilight - || effect.mEffectId == ESM::MagicEffect::SummonHunger - || effect.mEffectId == ESM::MagicEffect::SummonGoldenSaint - || effect.mEffectId == ESM::MagicEffect::SummonFlameAtronach - || effect.mEffectId == ESM::MagicEffect::SummonFrostAtronach - || effect.mEffectId == ESM::MagicEffect::SummonStormAtronach - || effect.mEffectId == ESM::MagicEffect::SummonCenturionSphere - || effect.mEffectId == ESM::MagicEffect::SummonFabricant || effect.mEffectId == ESM::MagicEffect::SummonWolf - || effect.mEffectId == ESM::MagicEffect::SummonBear || effect.mEffectId == ESM::MagicEffect::SummonBonewolf - || effect.mEffectId == ESM::MagicEffect::SummonCreature04 - || effect.mEffectId == ESM::MagicEffect::SummonCreature05) - { - ESM::RefNum actor = effect.getActor(); - if (actor.isSet()) - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(actor); - 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 == actor) - { - summons.erase(it); - break; - } - } - } - else if (effect.mEffectId == ESM::MagicEffect::BoundGloves) - { - removeBoundItem( - ESM::RefId::stringRefId( - world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString()), - target); - removeBoundItem( - ESM::RefId::stringRefId( - world->getStore().get().find("sMagicBoundLeftGauntletID")->mValue.getString()), - target); - } - else if (effect.mEffectId == ESM::MagicEffect::BoundDagger - || effect.mEffectId == ESM::MagicEffect::BoundLongsword || effect.mEffectId == ESM::MagicEffect::BoundMace - || effect.mEffectId == ESM::MagicEffect::BoundBattleAxe || effect.mEffectId == ESM::MagicEffect::BoundSpear - || effect.mEffectId == ESM::MagicEffect::BoundLongbow || effect.mEffectId == ESM::MagicEffect::BoundCuirass - || effect.mEffectId == ESM::MagicEffect::BoundHelm || effect.mEffectId == ESM::MagicEffect::BoundBoots - || effect.mEffectId == ESM::MagicEffect::BoundShield) - { - std::string_view item = getBoundItemsMap().at(effect.mEffectId); - removeBoundItem( - ESM::RefId::stringRefId(world->getStore().get().find(item)->mValue.getString()), - target); - } - else if (effect.mEffectId == ESM::MagicEffect::DrainHealth) - adjustDynamicStat(target, Stats::Health, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::DrainMagicka) - adjustDynamicStat(target, Stats::Magicka, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::DrainFatigue) - adjustDynamicStat(target, Stats::Fatigue, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::FortifyHealth) - { - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, Stats::Health, -effect.mMagnitude); - else - adjustDynamicStat(target, Stats::Health, -effect.mMagnitude, true); - } - else if (effect.mEffectId == ESM::MagicEffect::FortifyMagicka) - { - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, Stats::Magicka, -effect.mMagnitude); - else - adjustDynamicStat(target, Stats::Magicka, -effect.mMagnitude, true); - } - else if (effect.mEffectId == ESM::MagicEffect::FortifyFatigue) - { - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, Stats::Fatigue, -effect.mMagnitude); - else - adjustDynamicStat(target, Stats::Fatigue, -effect.mMagnitude, true); - } - else if (effect.mEffectId == ESM::MagicEffect::DrainAttribute) - restoreAttribute(target, effect, effect.mMagnitude); - else if (effect.mEffectId == ESM::MagicEffect::FortifyAttribute) - { - // Abilities affect base stats, but not for drain - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - { - auto& creatureStats = target.getClass().getCreatureStats(target); - auto attribute = effect.getSkillOrAttribute(); - AttributeValue attr = creatureStats.getAttribute(attribute); - attr.setBase(attr.getBase() - effect.mMagnitude); - creatureStats.setAttribute(attribute, attr); - } - else - fortifyAttribute(target, effect, -effect.mMagnitude); - } - else if (effect.mEffectId == ESM::MagicEffect::DrainSkill) - { - if (target.getClass().isNpc()) - restoreSkill(target, effect, effect.mMagnitude); - } - else if (effect.mEffectId == ESM::MagicEffect::FortifySkill) - { - if (target.getClass().isNpc()) - { - // Abilities affect base stats, but not for drain - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - { - auto& npcStats = target.getClass().getNpcStats(target); - auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); - skill.setBase(skill.getBase() - effect.mMagnitude); - } - else - fortifySkill(target, effect, -effect.mMagnitude); - } - } - else if (effect.mEffectId == ESM::MagicEffect::FortifyMaximumMagicka) - target.getClass().getCreatureStats(target).recalculateMagicka(); - else if (effect.mEffectId == ESM::MagicEffect::AbsorbAttribute) - { - const auto caster = worldModel->getPtr(spellParams.getCaster()); - restoreAttribute(target, effect, effect.mMagnitude); - if (!caster.isEmpty()) - fortifyAttribute(caster, effect, -effect.mMagnitude); - } - else if (effect.mEffectId == ESM::MagicEffect::AbsorbSkill) - { - if (target.getClass().isNpc()) - restoreSkill(target, effect, effect.mMagnitude); - const auto caster = worldModel->getPtr(spellParams.getCaster()); - if (!caster.isEmpty() && caster.getClass().isNpc()) - fortifySkill(caster, effect, -effect.mMagnitude); - } - else if (effect.mEffectId == 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); - } - } - void onMagicEffectRemoved( const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect) {