mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-03-30 07:21:12 +00:00
Give each reflect and spell absorption effect a chance to apply
This commit is contained in:
parent
3006c496fc
commit
4ec927829f
@ -45,6 +45,8 @@
|
|||||||
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
|
Bug #6174: Spellmaking and Enchanting sliders differences from vanilla
|
||||||
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
|
Bug #6184: Command and Calm and Demoralize and Frenzy and Rally magic effects inconsistencies with vanilla
|
||||||
Bug #6197: Infinite Casting Loop
|
Bug #6197: Infinite Casting Loop
|
||||||
|
Bug #6253: Multiple instances of Reflect stack additively
|
||||||
|
Bug #6255: Reflect is different from vanilla
|
||||||
Bug #6258: Barter menu glitches out when modifying prices
|
Bug #6258: Barter menu glitches out when modifying prices
|
||||||
Bug #6273: Respawning NPCs rotation is inconsistent
|
Bug #6273: Respawning NPCs rotation is inconsistent
|
||||||
Bug #6282: Laura craft doesn't follow the player character
|
Bug #6282: Laura craft doesn't follow the player character
|
||||||
|
@ -94,7 +94,7 @@ add_openmw_dir (mwmechanics
|
|||||||
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
|
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellcasting spellresistance
|
||||||
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning
|
||||||
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil
|
character actors objects aistate trading weaponpriority spellpriority weapontype spellutil
|
||||||
spellabsorption spelleffects
|
spelleffects
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwstate
|
add_openmw_dir (mwstate
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#include "activespells.hpp"
|
#include "activespells.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
@ -14,6 +16,8 @@
|
|||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
@ -96,6 +100,11 @@ namespace MWMechanics
|
|||||||
, mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening})
|
, mType(params.mType), mWorsenings(params.mWorsenings), mNextWorsening({params.mNextWorsening})
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor)
|
||||||
|
: mId(params.mId), mDisplayName(params.mDisplayName), mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||||
|
, mSlot(params.mSlot), mType(params.mType), mWorsenings(-1)
|
||||||
|
{}
|
||||||
|
|
||||||
ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const
|
ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const
|
||||||
{
|
{
|
||||||
ESM::ActiveSpells::ActiveSpellParams params;
|
ESM::ActiveSpells::ActiveSpellParams params;
|
||||||
@ -220,10 +229,19 @@ namespace MWMechanics
|
|||||||
{
|
{
|
||||||
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
|
const auto caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(spellIt->mCasterActorId); //Maybe make this search outside active grid?
|
||||||
bool removedSpell = false;
|
bool removedSpell = false;
|
||||||
|
std::optional<ActiveSpellParams> reflected;
|
||||||
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
for(auto it = spellIt->mEffects.begin(); it != spellIt->mEffects.end();)
|
||||||
{
|
{
|
||||||
bool remove = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
|
auto result = applyMagicEffect(ptr, caster, *spellIt, *it, duration);
|
||||||
if(remove)
|
if(result == MagicApplicationResult::REFLECTED)
|
||||||
|
{
|
||||||
|
if(!reflected)
|
||||||
|
reflected = {*spellIt, ptr};
|
||||||
|
auto& reflectedEffect = reflected->mEffects.emplace_back(*it);
|
||||||
|
reflectedEffect.mFlags = ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
|
||||||
|
it = spellIt->mEffects.erase(it);
|
||||||
|
}
|
||||||
|
else if(result == MagicApplicationResult::REMOVED)
|
||||||
it = spellIt->mEffects.erase(it);
|
it = spellIt->mEffects.erase(it);
|
||||||
else
|
else
|
||||||
++it;
|
++it;
|
||||||
@ -231,6 +249,14 @@ namespace MWMechanics
|
|||||||
if(removedSpell)
|
if(removedSpell)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if(reflected)
|
||||||
|
{
|
||||||
|
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find("VFX_Reflect");
|
||||||
|
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr);
|
||||||
|
if(animation && !reflectStatic->mModel.empty())
|
||||||
|
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
|
||||||
|
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected);
|
||||||
|
}
|
||||||
if(removedSpell)
|
if(removedSpell)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -50,6 +50,8 @@ namespace MWMechanics
|
|||||||
|
|
||||||
ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor);
|
ActiveSpellParams(const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, int slotIndex, const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
|
ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
ESM::ActiveSpells::ActiveSpellParams toEsm() const;
|
ESM::ActiveSpells::ActiveSpellParams toEsm() const;
|
||||||
|
|
||||||
friend class ActiveSpells;
|
friend class ActiveSpells;
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
#include "spellabsorption.hpp"
|
|
||||||
|
|
||||||
#include <components/misc/rng.hpp>
|
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
|
||||||
#include "../mwbase/world.hpp"
|
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
|
||||||
#include "../mwworld/inventorystore.hpp"
|
|
||||||
|
|
||||||
#include "creaturestats.hpp"
|
|
||||||
#include "spellutil.hpp"
|
|
||||||
|
|
||||||
namespace MWMechanics
|
|
||||||
{
|
|
||||||
float getProbability(const MWMechanics::ActiveSpells& activeSpells)
|
|
||||||
{
|
|
||||||
float probability = 0.f;
|
|
||||||
for(const auto& params : activeSpells)
|
|
||||||
{
|
|
||||||
for(const auto& effect : params.getEffects())
|
|
||||||
{
|
|
||||||
if(effect.mEffectId == ESM::MagicEffect::SpellAbsorption)
|
|
||||||
{
|
|
||||||
if(probability == 0.f)
|
|
||||||
probability = effect.mMagnitude / 100;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
|
|
||||||
// Real absorption probability will be the (1 - total fail chance) in this case.
|
|
||||||
float failProbability = 1.f - probability;
|
|
||||||
failProbability *= 1.f - effect.mMagnitude / 100;
|
|
||||||
probability = 1.f - failProbability;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return static_cast<int>(probability * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool absorbSpell (const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
|
||||||
{
|
|
||||||
if (spellId.empty() || target.isEmpty() || caster == target || !target.getClass().isActor())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
CreatureStats& stats = target.getClass().getCreatureStats(target);
|
|
||||||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() <= 0.f)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
int chance = getProbability(stats.getActiveSpells());
|
|
||||||
if (Misc::Rng::roll0to99() >= chance)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
|
|
||||||
const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find("VFX_Absorb");
|
|
||||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
|
||||||
if (animation && !absorbStatic->mModel.empty())
|
|
||||||
animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string());
|
|
||||||
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
|
|
||||||
int spellCost = 0;
|
|
||||||
if (spell)
|
|
||||||
{
|
|
||||||
spellCost = MWMechanics::calcSpellCost(*spell);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
|
|
||||||
if (enchantment)
|
|
||||||
spellCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), caster);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Magicka is increased by the cost of the spell
|
|
||||||
DynamicStat<float> magicka = stats.getMagicka();
|
|
||||||
magicka.setCurrent(magicka.getCurrent() + spellCost);
|
|
||||||
stats.setMagicka(magicka);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
#ifndef MWMECHANICS_SPELLABSORPTION_H
|
|
||||||
#define MWMECHANICS_SPELLABSORPTION_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace MWWorld
|
|
||||||
{
|
|
||||||
class Ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWMechanics
|
|
||||||
{
|
|
||||||
// Try to absorb a spell based on the magnitude of every Spell Absorption effect source on the target.
|
|
||||||
bool absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -22,38 +22,11 @@
|
|||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
#include "aifollow.hpp"
|
#include "aifollow.hpp"
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
#include "spellabsorption.hpp"
|
|
||||||
#include "spelleffects.hpp"
|
#include "spelleffects.hpp"
|
||||||
#include "spellutil.hpp"
|
#include "spellutil.hpp"
|
||||||
#include "summoning.hpp"
|
#include "summoning.hpp"
|
||||||
#include "weapontype.hpp"
|
#include "weapontype.hpp"
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
|
|
||||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects)
|
|
||||||
{
|
|
||||||
if (caster.isEmpty() || caster == target || !target.getClass().isActor())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
|
||||||
bool isUnreflectable = magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable;
|
|
||||||
if (!isHarmful || isUnreflectable)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
|
|
||||||
if (Misc::Rng::roll0to99() >= reflect)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
|
|
||||||
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
|
||||||
if (animation && !reflectStatic->mModel.empty())
|
|
||||||
animation->addEffect("meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, std::string());
|
|
||||||
reflectedEffects.mList.emplace_back(effect);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
|
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
|
||||||
@ -82,7 +55,7 @@ namespace MWMechanics
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
||||||
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
|
const ESM::EffectList &effects, ESM::RangeType range, bool exploded)
|
||||||
{
|
{
|
||||||
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
|
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
|
||||||
if (targetIsActor)
|
if (targetIsActor)
|
||||||
@ -123,7 +96,6 @@ namespace MWMechanics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ESM::EffectList reflectedEffects;
|
|
||||||
ActiveSpells::ActiveSpellParams params(*this, caster);
|
ActiveSpells::ActiveSpellParams params(*this, caster);
|
||||||
|
|
||||||
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
|
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
|
||||||
@ -136,9 +108,6 @@ namespace MWMechanics
|
|||||||
// throughout the iteration of this spell's
|
// throughout the iteration of this spell's
|
||||||
// effects, we display a "can't re-cast" message
|
// effects, we display a "can't re-cast" message
|
||||||
|
|
||||||
// Try absorbing the spell. Some handling must still happen for absorbed effects.
|
|
||||||
bool absorbed = absorbSpell(mId, caster, target);
|
|
||||||
|
|
||||||
int currentEffectIndex = 0;
|
int currentEffectIndex = 0;
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
||||||
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
|
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
|
||||||
@ -167,19 +136,6 @@ namespace MWMechanics
|
|||||||
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Notify the target actor they've been hit
|
|
||||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
|
||||||
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
|
||||||
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
|
||||||
|
|
||||||
// Avoid proceeding further for absorbed spells.
|
|
||||||
if (absorbed)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Reflect harmful effects
|
|
||||||
if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
ActiveSpells::ActiveEffect effect;
|
ActiveSpells::ActiveEffect effect;
|
||||||
effect.mEffectId = effectIt->mEffectID;
|
effect.mEffectId = effectIt->mEffectID;
|
||||||
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
||||||
@ -189,13 +145,8 @@ namespace MWMechanics
|
|||||||
effect.mTimeLeft = 0.f;
|
effect.mTimeLeft = 0.f;
|
||||||
effect.mEffectIndex = currentEffectIndex;
|
effect.mEffectIndex = currentEffectIndex;
|
||||||
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
||||||
|
if(mManualSpell)
|
||||||
// Avoid applying harmful effects to the player in god mode
|
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect;
|
||||||
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
|
|
||||||
{
|
|
||||||
effect.mMinMagnitude = 0;
|
|
||||||
effect.mMaxMagnitude = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||||
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
|
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
|
||||||
@ -209,14 +160,14 @@ namespace MWMechanics
|
|||||||
// add to list of active effects, to apply in next frame
|
// add to list of active effects, to apply in next frame
|
||||||
params.getEffects().emplace_back(effect);
|
params.getEffects().emplace_back(effect);
|
||||||
|
|
||||||
bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
|
bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
|
||||||
if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth)
|
if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth)
|
||||||
{
|
{
|
||||||
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
|
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
|
||||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetIsActor || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
if (!targetIsActor && magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||||
{
|
{
|
||||||
playEffects(target, *magicEffect);
|
playEffects(target, *magicEffect);
|
||||||
}
|
}
|
||||||
@ -227,9 +178,6 @@ namespace MWMechanics
|
|||||||
|
|
||||||
if (!target.isEmpty())
|
if (!target.isEmpty())
|
||||||
{
|
{
|
||||||
if (!reflectedEffects.mList.empty())
|
|
||||||
inflict(caster, target, reflectedEffects, range, true, exploded);
|
|
||||||
|
|
||||||
if (!params.getEffects().empty())
|
if (!params.getEffects().empty())
|
||||||
{
|
{
|
||||||
if(targetIsActor)
|
if(targetIsActor)
|
||||||
@ -237,6 +185,7 @@ namespace MWMechanics
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway
|
// Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway
|
||||||
|
// and we can ignore reflection since non-actors cannot reflect spells
|
||||||
for(auto& effect : params.getEffects())
|
for(auto& effect : params.getEffects())
|
||||||
applyMagicEffect(target, caster, params, effect, 0.f);
|
applyMagicEffect(target, caster, params, effect, 0.f);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ namespace MWMechanics
|
|||||||
/// @note \a target can be any type of object, not just actors.
|
/// @note \a target can be any type of object, not just actors.
|
||||||
/// @note \a caster can be any type of object, or even an empty object.
|
/// @note \a caster can be any type of object, or even an empty object.
|
||||||
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
||||||
const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false);
|
const ESM::EffectList& effects, ESM::RangeType range, bool exploded=false);
|
||||||
};
|
};
|
||||||
|
|
||||||
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true);
|
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "../mwmechanics/aifollow.hpp"
|
#include "../mwmechanics/aifollow.hpp"
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/spellresistance.hpp"
|
#include "../mwmechanics/spellresistance.hpp"
|
||||||
|
#include "../mwmechanics/spellutil.hpp"
|
||||||
#include "../mwmechanics/summoning.hpp"
|
#include "../mwmechanics/summoning.hpp"
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
@ -261,6 +262,97 @@ namespace
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void absorbSpell(const std::string& spellId, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||||
|
{
|
||||||
|
const auto& esmStore = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
const ESM::Static* absorbStatic = esmStore.get<ESM::Static>().find("VFX_Absorb");
|
||||||
|
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||||
|
if (animation && !absorbStatic->mModel.empty())
|
||||||
|
animation->addEffect( "meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, std::string());
|
||||||
|
const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellId);
|
||||||
|
int spellCost = 0;
|
||||||
|
if (spell)
|
||||||
|
{
|
||||||
|
spellCost = MWMechanics::calcSpellCost(*spell);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const ESM::Enchantment* enchantment = esmStore.get<ESM::Enchantment>().search(spellId);
|
||||||
|
if (enchantment)
|
||||||
|
spellCost = MWMechanics::getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), caster);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Magicka is increased by the cost of the spell
|
||||||
|
auto& stats = target.getClass().getCreatureStats(target);
|
||||||
|
auto magicka = stats.getMagicka();
|
||||||
|
magicka.setCurrent(magicka.getCurrent() + spellCost);
|
||||||
|
stats.setMagicka(magicka);
|
||||||
|
}
|
||||||
|
|
||||||
|
MWMechanics::MagicApplicationResult applyProtections(const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
||||||
|
const MWMechanics::ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, const ESM::MagicEffect* magicEffect)
|
||||||
|
{
|
||||||
|
auto& stats = target.getClass().getCreatureStats(target);
|
||||||
|
auto& magnitudes = stats.getMagicEffects();
|
||||||
|
// Apply reflect and spell absorption
|
||||||
|
if(target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment && spellParams.getType() != ESM::ActiveSpells::Type_Permanent)
|
||||||
|
{
|
||||||
|
bool canReflect = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable) &&
|
||||||
|
!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect) && magnitudes.get(ESM::MagicEffect::Reflect).getMagnitude() > 0.f;
|
||||||
|
bool canAbsorb = !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_SpellAbsorption) && magnitudes.get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f;
|
||||||
|
if(canReflect || canAbsorb)
|
||||||
|
{
|
||||||
|
for(const auto& activeParam : stats.getActiveSpells())
|
||||||
|
{
|
||||||
|
for(const auto& activeEffect : activeParam.getEffects())
|
||||||
|
{
|
||||||
|
if(!(activeEffect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||||
|
continue;
|
||||||
|
if(activeEffect.mEffectId == ESM::MagicEffect::Reflect)
|
||||||
|
{
|
||||||
|
if(canReflect && Misc::Rng::roll0to99() < activeEffect.mMagnitude)
|
||||||
|
{
|
||||||
|
return MWMechanics::MagicApplicationResult::REFLECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(activeEffect.mEffectId == ESM::MagicEffect::SpellAbsorption)
|
||||||
|
{
|
||||||
|
if(canAbsorb && Misc::Rng::roll0to99() < activeEffect.mMagnitude)
|
||||||
|
{
|
||||||
|
absorbSpell(spellParams.getId(), caster, target);
|
||||||
|
return MWMechanics::MagicApplicationResult::REMOVED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Notify the target actor they've been hit
|
||||||
|
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||||
|
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
||||||
|
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
||||||
|
// Apply resistances
|
||||||
|
if(!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances))
|
||||||
|
{
|
||||||
|
const ESM::Spell* spell = nullptr;
|
||||||
|
if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
||||||
|
spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellParams.getId());
|
||||||
|
float magnitudeMult = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes);
|
||||||
|
if (magnitudeMult == 0)
|
||||||
|
{
|
||||||
|
// Fully resisted, show message
|
||||||
|
if (target == MWMechanics::getPlayer())
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||||
|
else if (caster == MWMechanics::getPlayer())
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||||
|
return MWMechanics::MagicApplicationResult::REMOVED;
|
||||||
|
}
|
||||||
|
effect.mMinMagnitude *= magnitudeMult;
|
||||||
|
effect.mMaxMagnitude *= magnitudeMult;
|
||||||
|
}
|
||||||
|
return MWMechanics::MagicApplicationResult::APPLIED;
|
||||||
|
}
|
||||||
|
|
||||||
static const std::map<int, std::string> sBoundItemsMap{
|
static const std::map<int, std::string> sBoundItemsMap{
|
||||||
{ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"},
|
{ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID"},
|
||||||
{ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"},
|
{ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID"},
|
||||||
@ -682,7 +774,7 @@ void applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt)
|
MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt)
|
||||||
{
|
{
|
||||||
const auto world = MWBase::Environment::get().getWorld();
|
const auto world = MWBase::Environment::get().getWorld();
|
||||||
bool invalid = false;
|
bool invalid = false;
|
||||||
@ -698,13 +790,13 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
|||||||
}
|
}
|
||||||
if(target == getPlayer())
|
if(target == getPlayer())
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCorprusWorsens}");
|
||||||
return false;
|
return MagicApplicationResult::APPLIED;
|
||||||
}
|
}
|
||||||
else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled())
|
else if(effect.mEffectId == ESM::MagicEffect::Levitate && !world->isLevitationEnabled())
|
||||||
{
|
{
|
||||||
if(target == getPlayer())
|
if(target == getPlayer())
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}");
|
MWBase::Environment::get().getWindowManager()->messageBox ("#{sLevitateDisabled}");
|
||||||
return true;
|
return MagicApplicationResult::REMOVED;
|
||||||
}
|
}
|
||||||
const auto* magicEffect = world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
|
const auto* magicEffect = world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId);
|
||||||
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
|
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
|
||||||
@ -712,10 +804,10 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
|||||||
if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce)
|
if(magicEffect->mData.mFlags & ESM::MagicEffect::Flags::AppliedOnce)
|
||||||
{
|
{
|
||||||
effect.mTimeLeft -= dt;
|
effect.mTimeLeft -= dt;
|
||||||
return false;
|
return MagicApplicationResult::APPLIED;
|
||||||
}
|
}
|
||||||
else if(!dt)
|
else if(!dt)
|
||||||
return false;
|
return MagicApplicationResult::APPLIED;
|
||||||
}
|
}
|
||||||
if(effect.mEffectId == ESM::MagicEffect::Lock)
|
if(effect.mEffectId == ESM::MagicEffect::Lock)
|
||||||
{
|
{
|
||||||
@ -771,28 +863,19 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects();
|
auto& stats = target.getClass().getCreatureStats(target);
|
||||||
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & (ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Ignore_Resistances)))
|
auto& magnitudes = stats.getMagicEffects();
|
||||||
|
if(spellParams.getType() != ESM::ActiveSpells::Type_Ability && !(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||||
{
|
{
|
||||||
const ESM::Spell* spell = nullptr;
|
MagicApplicationResult result = applyProtections(target, caster, spellParams, effect, magicEffect);
|
||||||
if(spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
if(result != MagicApplicationResult::APPLIED)
|
||||||
spell = world->getStore().get<ESM::Spell>().search(spellParams.getId());
|
return result;
|
||||||
float magnitudeMult = getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes);
|
|
||||||
if (magnitudeMult == 0)
|
|
||||||
{
|
|
||||||
// Fully resisted, show message
|
|
||||||
if (target == getPlayer())
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
|
||||||
else if (caster == getPlayer())
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
effect.mMinMagnitude *= magnitudeMult;
|
|
||||||
effect.mMaxMagnitude *= magnitudeMult;
|
|
||||||
}
|
}
|
||||||
float oldMagnitude = 0.f;
|
float oldMagnitude = 0.f;
|
||||||
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
|
if(effect.mFlags & ESM::ActiveEffect::Flag_Applied)
|
||||||
oldMagnitude = effect.mMagnitude;
|
oldMagnitude = effect.mMagnitude;
|
||||||
|
else if(spellParams.getType() == ESM::ActiveSpells::Type_Consumable || spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
||||||
|
playEffects(target, *magicEffect);
|
||||||
float magnitude = roll(effect);
|
float magnitude = roll(effect);
|
||||||
//Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here
|
//Note that there's an early out for Flag_Applied AppliedOnce effects so we don't have to exclude them here
|
||||||
effect.mMagnitude = magnitude;
|
effect.mMagnitude = magnitude;
|
||||||
@ -810,7 +893,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
|||||||
effect.mMagnitude = oldMagnitude;
|
effect.mMagnitude = oldMagnitude;
|
||||||
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
|
effect.mFlags |= ESM::ActiveEffect::Flag_Applied | ESM::ActiveEffect::Flag_Remove;
|
||||||
effect.mTimeLeft -= dt;
|
effect.mTimeLeft -= dt;
|
||||||
return false;
|
return MagicApplicationResult::APPLIED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(effect.mEffectId == ESM::MagicEffect::Corprus)
|
if(effect.mEffectId == ESM::MagicEffect::Corprus)
|
||||||
@ -835,7 +918,7 @@ bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, Ac
|
|||||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
||||||
if(recalculateMagicka)
|
if(recalculateMagicka)
|
||||||
target.getClass().getCreatureStats(target).recalculateMagicka();
|
target.getClass().getCreatureStats(target).recalculateMagicka();
|
||||||
return false;
|
return MagicApplicationResult::APPLIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)
|
void removeMagicEffect(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spellParams, const ESM::ActiveEffect& effect)
|
||||||
|
@ -10,8 +10,13 @@
|
|||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
enum class MagicApplicationResult
|
||||||
|
{
|
||||||
|
APPLIED, REMOVED, REFLECTED
|
||||||
|
};
|
||||||
|
|
||||||
// Applies a tick of a single effect. Returns true if the effect should be removed immediately
|
// Applies a tick of a single effect. Returns true if the effect should be removed immediately
|
||||||
bool applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt);
|
MagicApplicationResult applyMagicEffect(const MWWorld::Ptr& target, const MWWorld::Ptr& caster, ActiveSpells::ActiveSpellParams& spellParams, ESM::ActiveEffect& effect, float dt);
|
||||||
|
|
||||||
// Undoes permanent effects created by ESM::MagicEffect::AppliedOnce
|
// Undoes permanent effects created by ESM::MagicEffect::AppliedOnce
|
||||||
void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect);
|
void onMagicEffectRemoved(const MWWorld::Ptr& target, ActiveSpells::ActiveSpellParams& spell, const ESM::ActiveEffect& effect);
|
||||||
|
@ -104,8 +104,8 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index)
|
|||||||
effect.mMagnitude = magnitude;
|
effect.mMagnitude = magnitude;
|
||||||
effect.mMinMagnitude = magnitude;
|
effect.mMinMagnitude = magnitude;
|
||||||
effect.mMaxMagnitude = magnitude;
|
effect.mMaxMagnitude = magnitude;
|
||||||
// Prevent recalculation of resistances
|
// Prevent recalculation of resistances and don't reflect or absorb the effect
|
||||||
effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances;
|
effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -172,8 +172,8 @@ void EsmLoader::load(const boost::filesystem::path& filepath, int& index)
|
|||||||
effect.mDuration = -1;
|
effect.mDuration = -1;
|
||||||
effect.mTimeLeft = -1;
|
effect.mTimeLeft = -1;
|
||||||
effect.mEffectIndex = static_cast<int>(effectIndex);
|
effect.mEffectIndex = static_cast<int>(effectIndex);
|
||||||
// Prevent recalculation of resistances
|
// Prevent recalculation of resistances and don't reflect or absorb the effect
|
||||||
effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances;
|
effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect | ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
|
||||||
params.mEffects.emplace_back(effect);
|
params.mEffects.emplace_back(effect);
|
||||||
}
|
}
|
||||||
auto [begin, end] = equippedItems.equal_range(id);
|
auto [begin, end] = equippedItems.equal_range(id);
|
||||||
|
@ -546,7 +546,7 @@ namespace MWWorld
|
|||||||
cast.mId = magicBoltState.mSpellId;
|
cast.mId = magicBoltState.mSpellId;
|
||||||
cast.mSourceName = magicBoltState.mSourceName;
|
cast.mSourceName = magicBoltState.mSourceName;
|
||||||
cast.mSlot = magicBoltState.mSlot;
|
cast.mSlot = magicBoltState.mSlot;
|
||||||
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
|
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, true);
|
||||||
|
|
||||||
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName, false, magicBoltState.mSlot);
|
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName, false, magicBoltState.mSlot);
|
||||||
magicBoltState.mToDelete = true;
|
magicBoltState.mToDelete = true;
|
||||||
|
@ -3791,7 +3791,7 @@ namespace MWWorld
|
|||||||
cast.mSlot = slot;
|
cast.mSlot = slot;
|
||||||
ESM::EffectList effectsToApply;
|
ESM::EffectList effectsToApply;
|
||||||
effectsToApply.mList = applyPair.second;
|
effectsToApply.mList = applyPair.second;
|
||||||
cast.inflict(applyPair.first, caster, effectsToApply, rangeType, false, true);
|
cast.inflict(applyPair.first, caster, effectsToApply, rangeType, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,9 @@ namespace ESM
|
|||||||
Flag_None = 0,
|
Flag_None = 0,
|
||||||
Flag_Applied = 1 << 0,
|
Flag_Applied = 1 << 0,
|
||||||
Flag_Remove = 1 << 1,
|
Flag_Remove = 1 << 1,
|
||||||
Flag_Ignore_Resistances = 1 << 2
|
Flag_Ignore_Resistances = 1 << 2,
|
||||||
|
Flag_Ignore_Reflect = 1 << 3,
|
||||||
|
Flag_Ignore_SpellAbsorption = 1 << 4
|
||||||
};
|
};
|
||||||
|
|
||||||
int mEffectId;
|
int mEffectId;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user