mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-04-01 10:06:42 +00:00
Merge pull request #2809 from Capostrophic/spellcasting
Refactor spellcasting header and implementation (task #5339)
This commit is contained in:
commit
b4aeb2711c
31 changed files with 984 additions and 778 deletions
|
@ -83,9 +83,10 @@ add_openmw_dir (mwclass
|
||||||
add_openmw_dir (mwmechanics
|
add_openmw_dir (mwmechanics
|
||||||
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
|
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
|
||||||
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
|
drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aibreathe
|
||||||
aicast aiescort aiface aiactivate aicombat recharge repair enchanting pathfinding pathgrid security spellsuccess spellcasting
|
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 coordinateconverter trading weaponpriority spellpriority weapontype
|
character actors objects aistate coordinateconverter trading weaponpriority spellpriority weapontype spellutil tickableeffects
|
||||||
|
spellabsorption linkedeffects
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwstate
|
add_openmw_dir (mwstate
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellutil.hpp"
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,10 @@
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
|
||||||
#include "../mwmechanics/spells.hpp"
|
#include "../mwmechanics/spells.hpp"
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
#include "../mwmechanics/spellutil.hpp"
|
||||||
|
|
||||||
#include "tooltips.hpp"
|
#include "tooltips.hpp"
|
||||||
#include "class.hpp"
|
#include "class.hpp"
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellutil.hpp"
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/inventorystore.hpp"
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/player.hpp"
|
#include "../mwworld/player.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellutil.hpp"
|
||||||
#include "../mwmechanics/spells.hpp"
|
#include "../mwmechanics/spells.hpp"
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellutil.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
|
||||||
#include "mapwindow.hpp"
|
#include "mapwindow.hpp"
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#include "summoning.hpp"
|
#include "summoning.hpp"
|
||||||
#include "combat.hpp"
|
#include "combat.hpp"
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
|
#include "tickableeffects.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
|
||||||
#include "npcstats.hpp"
|
#include "npcstats.hpp"
|
||||||
#include "spellcasting.hpp"
|
|
||||||
#include "combat.hpp"
|
#include "combat.hpp"
|
||||||
#include "weaponpriority.hpp"
|
#include "weaponpriority.hpp"
|
||||||
#include "spellpriority.hpp"
|
#include "spellpriority.hpp"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#include "autocalcspell.hpp"
|
#include "autocalcspell.hpp"
|
||||||
#include "spellcasting.hpp"
|
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
|
||||||
|
@ -8,6 +7,7 @@
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
|
#include "spellutil.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "npcstats.hpp"
|
#include "npcstats.hpp"
|
||||||
#include "movement.hpp"
|
#include "movement.hpp"
|
||||||
#include "spellcasting.hpp"
|
#include "spellcasting.hpp"
|
||||||
|
#include "spellresistance.hpp"
|
||||||
#include "difficultyscaling.hpp"
|
#include "difficultyscaling.hpp"
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
#include "pathfinding.hpp"
|
#include "pathfinding.hpp"
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
#include "spellcasting.hpp"
|
#include "spellutil.hpp"
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
#include "weapontype.hpp"
|
#include "weapontype.hpp"
|
||||||
|
|
||||||
|
|
74
apps/openmw/mwmechanics/linkedeffects.cpp
Normal file
74
apps/openmw/mwmechanics/linkedeffects.cpp
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
#include "linkedeffects.hpp"
|
||||||
|
|
||||||
|
#include <components/misc/rng.hpp>
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
|
#include "creaturestats.hpp"
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
|
||||||
|
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source)
|
||||||
|
{
|
||||||
|
if (caster.isEmpty() || caster == target)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!target.getClass().isActor() || !caster.getClass().isActor())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Make sure callers don't do something weird
|
||||||
|
if (effect.mEffectID < ESM::MagicEffect::AbsorbAttribute || effect.mEffectID > ESM::MagicEffect::AbsorbSkill)
|
||||||
|
throw std::runtime_error("invalid absorb stat effect");
|
||||||
|
|
||||||
|
if (appliedEffect.mMagnitude == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
||||||
|
ActiveSpells::ActiveEffect absorbEffect = appliedEffect;
|
||||||
|
absorbEffect.mMagnitude *= -1;
|
||||||
|
absorbEffects.emplace_back(absorbEffect);
|
||||||
|
|
||||||
|
// Morrowind negates reflected Absorb spells so the original caster won't be harmed.
|
||||||
|
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
||||||
|
{
|
||||||
|
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(std::string(), true,
|
||||||
|
absorbEffects, source, caster.getClass().getCreatureStats(caster).getActorId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(std::string(), true,
|
||||||
|
absorbEffects, source, target.getClass().getCreatureStats(target).getActorId());
|
||||||
|
}
|
||||||
|
}
|
32
apps/openmw/mwmechanics/linkedeffects.hpp
Normal file
32
apps/openmw/mwmechanics/linkedeffects.hpp
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef MWMECHANICS_LINKEDEFFECTS_H
|
||||||
|
#define MWMECHANICS_LINKEDEFFECTS_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace ESM
|
||||||
|
{
|
||||||
|
struct ActiveEffect;
|
||||||
|
struct EffectList;
|
||||||
|
struct ENAMstruct;
|
||||||
|
struct MagicEffect;
|
||||||
|
struct Spell;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWWorld
|
||||||
|
{
|
||||||
|
class Ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
|
||||||
|
// Try to reflect a spell effect. If it's reflected, it's also put into the passed reflected effects list.
|
||||||
|
bool reflectEffect(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect,
|
||||||
|
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, ESM::EffectList& reflectedEffects);
|
||||||
|
|
||||||
|
// Try to absorb a stat (skill, attribute, etc.) from the target and transfer it to the caster.
|
||||||
|
void absorbStat(const ESM::ENAMstruct& effect, const ESM::ActiveEffect& appliedEffect,
|
||||||
|
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, bool reflected, const std::string& source);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
#include "aicombat.hpp"
|
#include "aicombat.hpp"
|
||||||
#include "aipursue.hpp"
|
#include "aipursue.hpp"
|
||||||
#include "spellcasting.hpp"
|
#include "spellutil.hpp"
|
||||||
#include "autocalcspell.hpp"
|
#include "autocalcspell.hpp"
|
||||||
#include "npcstats.hpp"
|
#include "npcstats.hpp"
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
|
@ -376,7 +376,7 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
const std::string& spell = winMgr->getSelectedSpell();
|
const std::string& spell = winMgr->getSelectedSpell();
|
||||||
if (!spell.empty())
|
if (!spell.empty())
|
||||||
winMgr->setSelectedSpell(spell, int(MWMechanics::getSpellSuccessChance(spell, mWatched)));
|
winMgr->setSelectedSpell(spell, int(getSpellSuccessChance(spell, mWatched)));
|
||||||
else
|
else
|
||||||
winMgr->unsetSelectedSpell();
|
winMgr->unsetSelectedSpell();
|
||||||
}
|
}
|
||||||
|
|
76
apps/openmw/mwmechanics/spellabsorption.cpp
Normal file
76
apps/openmw/mwmechanics/spellabsorption.cpp
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#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"
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
|
||||||
|
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
float mProbability{0.f};
|
||||||
|
|
||||||
|
GetAbsorptionProbability() = default;
|
||||||
|
|
||||||
|
virtual void visit (MWMechanics::EffectKey key,
|
||||||
|
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||||
|
float magnitude, float /*remainingTime*/, float /*totalTime*/)
|
||||||
|
{
|
||||||
|
if (key.mId == ESM::MagicEffect::SpellAbsorption)
|
||||||
|
{
|
||||||
|
if (mProbability == 0.f)
|
||||||
|
mProbability = magnitude / 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 - mProbability;
|
||||||
|
failProbability *= 1.f - magnitude / 100;
|
||||||
|
mProbability = 1.f - failProbability;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool absorbSpell (const ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target)
|
||||||
|
{
|
||||||
|
if (!spell || 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;
|
||||||
|
|
||||||
|
GetAbsorptionProbability check;
|
||||||
|
stats.getActiveSpells().visitEffectSources(check);
|
||||||
|
stats.getSpells().visitEffectSources(check);
|
||||||
|
if (target.getClass().hasInventoryStore(target))
|
||||||
|
target.getClass().getInventoryStore(target).visitEffectSources(check);
|
||||||
|
|
||||||
|
int chance = check.mProbability * 100;
|
||||||
|
if (Misc::Rng::roll0to99() >= chance)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().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());
|
||||||
|
// Magicka is increased by the cost of the spell
|
||||||
|
DynamicStat<float> magicka = stats.getMagicka();
|
||||||
|
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||||
|
stats.setMagicka(magicka);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
apps/openmw/mwmechanics/spellabsorption.hpp
Normal file
20
apps/openmw/mwmechanics/spellabsorption.hpp
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#ifndef MWMECHANICS_SPELLABSORPTION_H
|
||||||
|
#define MWMECHANICS_SPELLABSORPTION_H
|
||||||
|
|
||||||
|
namespace ESM
|
||||||
|
{
|
||||||
|
struct Spell;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ESM::Spell* spell, const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,11 +1,7 @@
|
||||||
#include "spellcasting.hpp"
|
#include "spellcasting.hpp"
|
||||||
|
|
||||||
#include <limits>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
#include <components/misc/constants.hpp>
|
#include <components/misc/constants.hpp>
|
||||||
#include <components/misc/rng.hpp>
|
#include <components/misc/rng.hpp>
|
||||||
#include <components/settings/settings.hpp>
|
|
||||||
|
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
#include "../mwbase/soundmanager.hpp"
|
||||||
|
@ -23,293 +19,19 @@
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
#include "npcstats.hpp"
|
|
||||||
#include "actorutil.hpp"
|
#include "actorutil.hpp"
|
||||||
#include "aifollow.hpp"
|
#include "aifollow.hpp"
|
||||||
|
#include "creaturestats.hpp"
|
||||||
|
#include "linkedeffects.hpp"
|
||||||
|
#include "spellabsorption.hpp"
|
||||||
|
#include "spellresistance.hpp"
|
||||||
|
#include "spellutil.hpp"
|
||||||
|
#include "summoning.hpp"
|
||||||
|
#include "tickableeffects.hpp"
|
||||||
#include "weapontype.hpp"
|
#include "weapontype.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
ESM::Skill::SkillEnum spellSchoolToSkill(int school)
|
|
||||||
{
|
|
||||||
static const std::array<ESM::Skill::SkillEnum, 6> schoolSkillArray
|
|
||||||
{
|
|
||||||
ESM::Skill::Alteration, ESM::Skill::Conjuration, ESM::Skill::Destruction,
|
|
||||||
ESM::Skill::Illusion, ESM::Skill::Mysticism, ESM::Skill::Restoration
|
|
||||||
};
|
|
||||||
return schoolSkillArray.at(school);
|
|
||||||
}
|
|
||||||
|
|
||||||
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect)
|
|
||||||
{
|
|
||||||
if (!magicEffect)
|
|
||||||
magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
|
|
||||||
bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude);
|
|
||||||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
|
||||||
int minMagn = hasMagnitude ? effect.mMagnMin : 1;
|
|
||||||
int maxMagn = hasMagnitude ? effect.mMagnMax : 1;
|
|
||||||
int duration = hasDuration ? effect.mDuration : 1;
|
|
||||||
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore()
|
|
||||||
.get<ESM::GameSetting>().find("fEffectCostMult")->mValue.getFloat();
|
|
||||||
|
|
||||||
float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn));
|
|
||||||
x *= 0.1 * magicEffect->mData.mBaseCost;
|
|
||||||
x *= 1 + duration;
|
|
||||||
x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost;
|
|
||||||
|
|
||||||
return x * fEffectCostMult;
|
|
||||||
}
|
|
||||||
|
|
||||||
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool)
|
|
||||||
{
|
|
||||||
// Morrowind for some reason uses a formula slightly different from magicka cost calculation
|
|
||||||
float y = std::numeric_limits<float>::max();
|
|
||||||
float lowestSkill = 0;
|
|
||||||
|
|
||||||
for (const ESM::ENAMstruct& effect : spell->mEffects.mList)
|
|
||||||
{
|
|
||||||
float x = static_cast<float>(effect.mDuration);
|
|
||||||
const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
|
|
||||||
|
|
||||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce))
|
|
||||||
x = std::max(1.f, x);
|
|
||||||
|
|
||||||
x *= 0.1f * magicEffect->mData.mBaseCost;
|
|
||||||
x *= 0.5f * (effect.mMagnMin + effect.mMagnMax);
|
|
||||||
x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost;
|
|
||||||
if (effect.mRange == ESM::RT_Target)
|
|
||||||
x *= 1.5f;
|
|
||||||
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
|
||||||
"fEffectCostMult")->mValue.getFloat();
|
|
||||||
x *= fEffectCostMult;
|
|
||||||
|
|
||||||
float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
|
|
||||||
if (s - x < y)
|
|
||||||
{
|
|
||||||
y = s - x;
|
|
||||||
if (effectiveSchool)
|
|
||||||
*effectiveSchool = magicEffect->mData.mSchool;
|
|
||||||
lowestSkill = s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
||||||
|
|
||||||
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
|
||||||
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
|
||||||
|
|
||||||
float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck);
|
|
||||||
|
|
||||||
return castChance;
|
|
||||||
}
|
|
||||||
|
|
||||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
|
||||||
{
|
|
||||||
bool godmode = actor == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
|
||||||
|
|
||||||
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
||||||
|
|
||||||
float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude();
|
|
||||||
|
|
||||||
float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus;
|
|
||||||
castChance *= stats.getFatigueTerm();
|
|
||||||
|
|
||||||
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude()&& !godmode)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (spell->mData.mType == ESM::Spell::ST_Power)
|
|
||||||
return stats.getSpells().canUsePower(spell) ? 100 : 0;
|
|
||||||
|
|
||||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
|
||||||
return 100;
|
|
||||||
|
|
||||||
if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost && !godmode)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
|
||||||
return 100;
|
|
||||||
|
|
||||||
if (godmode)
|
|
||||||
return 100;
|
|
||||||
|
|
||||||
return std::max(0.f, cap ? std::min(100.f, castChance) : castChance);
|
|
||||||
}
|
|
||||||
|
|
||||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
|
||||||
{
|
|
||||||
if (const auto spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId))
|
|
||||||
return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka);
|
|
||||||
return 0.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
|
||||||
{
|
|
||||||
int school = 0;
|
|
||||||
getSpellSuccessChance(spellId, actor, &school);
|
|
||||||
return school;
|
|
||||||
}
|
|
||||||
|
|
||||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
|
||||||
{
|
|
||||||
int school = 0;
|
|
||||||
getSpellSuccessChance(spell, actor, &school);
|
|
||||||
return school;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool spellIncreasesSkill(const ESM::Spell *spell)
|
|
||||||
{
|
|
||||||
return spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool spellIncreasesSkill(const std::string &spellId)
|
|
||||||
{
|
|
||||||
const auto spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId);
|
|
||||||
return spell && spellIncreasesSkill(spell);
|
|
||||||
}
|
|
||||||
|
|
||||||
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects)
|
|
||||||
{
|
|
||||||
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
|
|
||||||
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
|
|
||||||
|
|
||||||
float resistance = 0;
|
|
||||||
if (resistanceEffect != -1)
|
|
||||||
resistance += actorEffects->get(resistanceEffect).getMagnitude();
|
|
||||||
if (weaknessEffect != -1)
|
|
||||||
resistance -= actorEffects->get(weaknessEffect).getMagnitude();
|
|
||||||
|
|
||||||
if (effectId == ESM::MagicEffect::FireDamage)
|
|
||||||
resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude();
|
|
||||||
if (effectId == ESM::MagicEffect::ShockDamage)
|
|
||||||
resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude();
|
|
||||||
if (effectId == ESM::MagicEffect::FrostDamage)
|
|
||||||
resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude();
|
|
||||||
|
|
||||||
return resistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
|
||||||
const ESM::Spell* spell, const MagicEffects* effects)
|
|
||||||
{
|
|
||||||
const ESM::MagicEffect *magicEffect =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
|
||||||
effectId);
|
|
||||||
|
|
||||||
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
|
||||||
const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects();
|
|
||||||
if (effects)
|
|
||||||
magicEffects = effects;
|
|
||||||
|
|
||||||
// Effects with no resistance attribute belonging to them can not be resisted
|
|
||||||
if (ESM::MagicEffect::getResistanceEffect(effectId) == -1)
|
|
||||||
return 0.f;
|
|
||||||
|
|
||||||
float resistance = getEffectResistanceAttribute(effectId, magicEffects);
|
|
||||||
|
|
||||||
int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
|
||||||
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
|
|
||||||
float x = (willpower + 0.1f * luck) * stats.getFatigueTerm();
|
|
||||||
|
|
||||||
// This makes spells that are easy to cast harder to resist and vice versa
|
|
||||||
float castChance = 100.f;
|
|
||||||
if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor())
|
|
||||||
{
|
|
||||||
castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance
|
|
||||||
}
|
|
||||||
if (castChance > 0)
|
|
||||||
x *= 50 / castChance;
|
|
||||||
|
|
||||||
float roll = Misc::Rng::rollClosedProbability() * 100;
|
|
||||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
|
||||||
roll -= resistance;
|
|
||||||
|
|
||||||
if (x <= roll)
|
|
||||||
x = 0;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
|
||||||
x = 100;
|
|
||||||
else
|
|
||||||
x = roll / std::min(x, 100.f);
|
|
||||||
}
|
|
||||||
|
|
||||||
x = std::min(x + resistance, 100.f);
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
|
||||||
const ESM::Spell* spell, const MagicEffects* effects)
|
|
||||||
{
|
|
||||||
float resistance = getEffectResistance(effectId, actor, caster, spell, effects);
|
|
||||||
return 1 - resistance / 100.f;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure.
|
|
||||||
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer)
|
|
||||||
{
|
|
||||||
switch (effectId)
|
|
||||||
{
|
|
||||||
case ESM::MagicEffect::Levitate:
|
|
||||||
if (!MWBase::Environment::get().getWorld()->isLevitationEnabled())
|
|
||||||
{
|
|
||||||
if (castByPlayer)
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ESM::MagicEffect::Soultrap:
|
|
||||||
if (!target.getClass().isNpc() // no messagebox for NPCs
|
|
||||||
&& (target.getTypeName() == typeid(ESM::Creature).name() && target.get<ESM::Creature>()->mBase->mData.mSoul == 0))
|
|
||||||
{
|
|
||||||
if (castByPlayer)
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}");
|
|
||||||
return true; // must still apply to get visual effect and have target regard it as attack
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ESM::MagicEffect::WaterWalking:
|
|
||||||
if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
|
||||||
|
|
||||||
if (!world->isWaterWalkingCastableOnTarget(target))
|
|
||||||
{
|
|
||||||
if (castByPlayer && caster == target)
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
float mProbability{0.f};
|
|
||||||
|
|
||||||
GetAbsorptionProbability() = default;
|
|
||||||
|
|
||||||
virtual void visit (MWMechanics::EffectKey key,
|
|
||||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
|
||||||
float magnitude, float remainingTime = -1, float totalTime = -1)
|
|
||||||
{
|
|
||||||
if (key.mId == ESM::MagicEffect::SpellAbsorption)
|
|
||||||
{
|
|
||||||
if (mProbability == 0.f)
|
|
||||||
mProbability = magnitude / 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 - mProbability;
|
|
||||||
failProbability *= 1.f - magnitude / 100;
|
|
||||||
mProbability = 1.f - failProbability;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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)
|
||||||
: mCaster(caster)
|
: mCaster(caster)
|
||||||
, mTarget(target)
|
, mTarget(target)
|
||||||
|
@ -391,6 +113,9 @@ 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(spell, caster, target);
|
||||||
|
|
||||||
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)
|
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt)
|
||||||
{
|
{
|
||||||
|
@ -418,89 +143,30 @@ namespace MWMechanics
|
||||||
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// If player is healing someone, show the target's HP bar
|
// Notify the target actor they've been hit
|
||||||
if (castByPlayer && target != caster
|
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||||
&& effectIt->mEffectID == ESM::MagicEffect::RestoreHealth
|
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
||||||
&& target.getClass().isActor())
|
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
||||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
|
||||||
|
|
||||||
// Try absorbing if it's a spell
|
// Avoid proceeding further for absorbed spells.
|
||||||
// Unlike Reflect, this is done once per spell absorption effect source
|
if (absorbed)
|
||||||
bool absorbed = false;
|
continue;
|
||||||
if (spell && caster != target && target.getClass().isActor())
|
|
||||||
|
// Reflect harmful effects
|
||||||
|
if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Try resisting.
|
||||||
|
float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects);
|
||||||
|
if (magnitudeMult == 0)
|
||||||
{
|
{
|
||||||
CreatureStats& stats = target.getClass().getCreatureStats(target);
|
// Fully resisted, show message
|
||||||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f)
|
if (target == getPlayer())
|
||||||
{
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||||
GetAbsorptionProbability check;
|
else if (castByPlayer)
|
||||||
stats.getActiveSpells().visitEffectSources(check);
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||||
stats.getSpells().visitEffectSources(check);
|
|
||||||
if (target.getClass().hasInventoryStore(target))
|
|
||||||
target.getClass().getInventoryStore(target).visitEffectSources(check);
|
|
||||||
|
|
||||||
int absorb = check.mProbability * 100;
|
|
||||||
absorbed = (Misc::Rng::roll0to99() < absorb);
|
|
||||||
if (absorbed)
|
|
||||||
{
|
|
||||||
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
|
|
||||||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
|
||||||
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, "");
|
|
||||||
// Magicka is increased by cost of spell
|
|
||||||
DynamicStat<float> magicka = stats.getMagicka();
|
|
||||||
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
|
||||||
stats.setMagicka(magicka);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
float magnitudeMult = 1;
|
|
||||||
|
|
||||||
if (target.getClass().isActor())
|
|
||||||
{
|
|
||||||
if (absorbed)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
|
||||||
// Reflect harmful effects
|
|
||||||
if (isHarmful && !reflected && !caster.isEmpty() && caster != target && !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable))
|
|
||||||
{
|
|
||||||
float reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).getMagnitude();
|
|
||||||
bool isReflected = (Misc::Rng::roll0to99() < reflect);
|
|
||||||
if (isReflected)
|
|
||||||
{
|
|
||||||
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
|
|
||||||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
|
||||||
"meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, "");
|
|
||||||
reflectedEffects.mList.push_back(*effectIt);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try resisting
|
|
||||||
magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects);
|
|
||||||
if (magnitudeMult == 0)
|
|
||||||
{
|
|
||||||
// Fully resisted, show message
|
|
||||||
if (target == getPlayer())
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
|
||||||
else if (castByPlayer)
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
|
||||||
}
|
|
||||||
else if (isHarmful && castByPlayer && target != caster)
|
|
||||||
{
|
|
||||||
// If player is attempting to cast a harmful spell and it wasn't fully resisted, show the target's HP bar
|
|
||||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
|
|
||||||
magnitudeMult = 0;
|
|
||||||
|
|
||||||
// Notify the target actor they've been hit
|
|
||||||
if (target != caster && !caster.isEmpty() && isHarmful)
|
|
||||||
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (magnitudeMult > 0 && !absorbed)
|
|
||||||
{
|
{
|
||||||
float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1);
|
float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1);
|
||||||
magnitude *= magnitudeMult;
|
magnitude *= magnitudeMult;
|
||||||
|
@ -527,6 +193,19 @@ namespace MWMechanics
|
||||||
effect.mMagnitude = 0;
|
effect.mMagnitude = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid applying harmful effects to the player in god mode
|
||||||
|
if (target == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState() && isHarmful)
|
||||||
|
{
|
||||||
|
effect.mMagnitude = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool effectAffectsHealth = isHarmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
|
||||||
|
if (castByPlayer && target != caster && effectAffectsHealth)
|
||||||
|
{
|
||||||
|
// If player is attempting to cast a harmful spell or is healing someone, show the target's HP bar.
|
||||||
|
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||||
|
}
|
||||||
|
|
||||||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||||
if (hasDuration && effectIt->mDuration == 0)
|
if (hasDuration && effectIt->mDuration == 0)
|
||||||
{
|
{
|
||||||
|
@ -536,7 +215,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
// duration 0 means apply full magnitude instantly
|
// duration 0 means apply full magnitude instantly
|
||||||
bool wasDead = target.getClass().getCreatureStats(target).isDead();
|
bool wasDead = target.getClass().getCreatureStats(target).isDead();
|
||||||
effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), magnitude);
|
effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), effect.mMagnitude);
|
||||||
bool isDead = target.getClass().getCreatureStats(target).isDead();
|
bool isDead = target.getClass().getCreatureStats(target).isDead();
|
||||||
|
|
||||||
if (!wasDead && isDead)
|
if (!wasDead && isDead)
|
||||||
|
@ -561,7 +240,7 @@ namespace MWMechanics
|
||||||
// Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target
|
// Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target
|
||||||
if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc())
|
if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc())
|
||||||
|| (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name()))
|
|| (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name()))
|
||||||
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && magnitude >= target.getClass().getCreatureStats(target).getLevel())
|
&& !caster.isEmpty() && caster.getClass().isActor() && target != getPlayer() && effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel())
|
||||||
{
|
{
|
||||||
MWMechanics::AiFollow package(caster, true);
|
MWMechanics::AiFollow package(caster, true);
|
||||||
target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
|
target.getClass().getCreatureStats(target).getAiSequence().stack(package, target);
|
||||||
|
@ -569,23 +248,8 @@ namespace MWMechanics
|
||||||
|
|
||||||
// For absorb effects, also apply the effect to the caster - but with a negative
|
// For absorb effects, also apply the effect to the caster - but with a negative
|
||||||
// magnitude, since we're transferring stats from the target to the caster
|
// magnitude, since we're transferring stats from the target to the caster
|
||||||
if (!caster.isEmpty() && caster != target && caster.getClass().isActor())
|
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
|
||||||
{
|
absorbStat(*effectIt, effect, caster, target, reflected, mSourceName);
|
||||||
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute &&
|
|
||||||
effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
|
|
||||||
{
|
|
||||||
std::vector<ActiveSpells::ActiveEffect> absorbEffects;
|
|
||||||
ActiveSpells::ActiveEffect effect_ = effect;
|
|
||||||
effect_.mMagnitude *= -1;
|
|
||||||
absorbEffects.push_back(effect_);
|
|
||||||
if (reflected && Settings::Manager::getBool("classic reflected absorb spells behavior", "Game"))
|
|
||||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell("", true,
|
|
||||||
absorbEffects, mSourceName, caster.getClass().getCreatureStats(caster).getActorId());
|
|
||||||
else
|
|
||||||
caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell("", true,
|
|
||||||
absorbEffects, mSourceName, target.getClass().getCreatureStats(target).getActorId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -767,16 +431,14 @@ namespace MWMechanics
|
||||||
|
|
||||||
bool CastSpell::cast(const std::string &id)
|
bool CastSpell::cast(const std::string &id)
|
||||||
{
|
{
|
||||||
if (const ESM::Spell *spell =
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
if (const auto spell = store.get<ESM::Spell>().search(id))
|
||||||
return cast(spell);
|
return cast(spell);
|
||||||
|
|
||||||
if (const ESM::Potion *potion =
|
if (const auto potion = store.get<ESM::Potion>().search(id))
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
|
||||||
return cast(potion);
|
return cast(potion);
|
||||||
|
|
||||||
if (const ESM::Ingredient *ingredient =
|
if (const auto ingredient = store.get<ESM::Ingredient>().search(id))
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
|
||||||
return cast(ingredient);
|
return cast(ingredient);
|
||||||
|
|
||||||
throw std::runtime_error("ID type cannot be casted");
|
throw std::runtime_error("ID type cannot be casted");
|
||||||
|
@ -939,10 +601,9 @@ namespace MWMechanics
|
||||||
stats.getSpells().usePower(spell);
|
stats.getSpells().usePower(spell);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mCaster == getPlayer() && spellIncreasesSkill())
|
if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
|
||||||
mCaster.getClass().skillUsageSucceeded(mCaster,
|
mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0);
|
||||||
spellSchoolToSkill(school), 0);
|
|
||||||
|
|
||||||
// A non-actor doesn't play its spell cast effects from a character controller, so play them here
|
// A non-actor doesn't play its spell cast effects from a character controller, so play them here
|
||||||
if (!mCaster.getClass().isActor())
|
if (!mCaster.getClass().isActor())
|
||||||
playSpellCastingEffects(spell->mEffects.mList);
|
playSpellCastingEffects(spell->mEffects.mList);
|
||||||
|
@ -970,10 +631,8 @@ namespace MWMechanics
|
||||||
effect.mRange = ESM::RT_Self;
|
effect.mRange = ESM::RT_Self;
|
||||||
effect.mArea = 0;
|
effect.mArea = 0;
|
||||||
|
|
||||||
const ESM::MagicEffect *magicEffect =
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
const auto magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||||
effect.mEffectID);
|
|
||||||
|
|
||||||
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
|
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
|
||||||
|
|
||||||
float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) +
|
float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) +
|
||||||
|
@ -985,7 +644,7 @@ namespace MWMechanics
|
||||||
if (roll > x)
|
if (roll > x)
|
||||||
{
|
{
|
||||||
// "X has no effect on you"
|
// "X has no effect on you"
|
||||||
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("sNotifyMessage50")->mValue.getString();
|
std::string message = store.get<ESM::GameSetting>().find("sNotifyMessage50")->mValue.getString();
|
||||||
message = Misc::StringUtils::format(message, ingredient->mName);
|
message = Misc::StringUtils::format(message, ingredient->mName);
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||||
return false;
|
return false;
|
||||||
|
@ -1086,280 +745,4 @@ namespace MWMechanics
|
||||||
sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f);
|
sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CastSpell::spellIncreasesSkill()
|
|
||||||
{
|
|
||||||
return !mManualSpell && MWMechanics::spellIncreasesSkill(mId);
|
|
||||||
}
|
|
||||||
|
|
||||||
int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Each point of enchant skill above/under 10 subtracts/adds
|
|
||||||
* one percent of enchantment cost while minimum is 1.
|
|
||||||
*/
|
|
||||||
int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant);
|
|
||||||
const float result = castCost - (castCost / 100) * (eSkill - 10);
|
|
||||||
|
|
||||||
return static_cast<int>((result < 1) ? 1 : result);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isSummoningEffect(int effectId)
|
|
||||||
{
|
|
||||||
return ((effectId >= ESM::MagicEffect::SummonScamp
|
|
||||||
&& effectId <= ESM::MagicEffect::SummonStormAtronach)
|
|
||||||
|| effectId == ESM::MagicEffect::SummonCenturionSphere
|
|
||||||
|| (effectId >= ESM::MagicEffect::SummonFabricant
|
|
||||||
&& effectId <= ESM::MagicEffect::SummonCreature05));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate)
|
|
||||||
{
|
|
||||||
if (ptr.getClass().hasInventoryStore(ptr))
|
|
||||||
{
|
|
||||||
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
|
||||||
MWWorld::ContainerStoreIterator item = inv.getSlot(slot);
|
|
||||||
|
|
||||||
if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon))
|
|
||||||
{
|
|
||||||
if (!item->getClass().hasItemHealth(*item))
|
|
||||||
return false;
|
|
||||||
int charge = item->getClass().getItemHealth(*item);
|
|
||||||
|
|
||||||
if (charge == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Store remainder of disintegrate amount (automatically subtracted if > 1)
|
|
||||||
item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));
|
|
||||||
|
|
||||||
charge = item->getClass().getItemHealth(*item);
|
|
||||||
charge -= std::min(static_cast<int>(disintegrate), charge);
|
|
||||||
item->getCellRef().setCharge(charge);
|
|
||||||
|
|
||||||
if (charge == 0)
|
|
||||||
{
|
|
||||||
// Will unequip the broken item and try to find a replacement
|
|
||||||
if (ptr != getPlayer())
|
|
||||||
inv.autoEquip(ptr);
|
|
||||||
else
|
|
||||||
inv.unequipItem(*item, ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false)
|
|
||||||
{
|
|
||||||
DynamicStat<float> stat = creatureStats.getDynamic(index);
|
|
||||||
stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero);
|
|
||||||
creatureStats.setDynamic(index, stat);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
|
|
||||||
{
|
|
||||||
if (magnitude == 0.f)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
bool receivedMagicDamage = false;
|
|
||||||
|
|
||||||
switch (effectKey.mId)
|
|
||||||
{
|
|
||||||
case ESM::MagicEffect::DamageAttribute:
|
|
||||||
{
|
|
||||||
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
|
||||||
attr.damage(magnitude);
|
|
||||||
creatureStats.setAttribute(effectKey.mArg, attr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ESM::MagicEffect::RestoreAttribute:
|
|
||||||
{
|
|
||||||
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
|
||||||
attr.restore(magnitude);
|
|
||||||
creatureStats.setAttribute(effectKey.mArg, attr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ESM::MagicEffect::RestoreHealth:
|
|
||||||
case ESM::MagicEffect::RestoreMagicka:
|
|
||||||
case ESM::MagicEffect::RestoreFatigue:
|
|
||||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
|
|
||||||
break;
|
|
||||||
case ESM::MagicEffect::DamageHealth:
|
|
||||||
receivedMagicDamage = true;
|
|
||||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ESM::MagicEffect::DamageMagicka:
|
|
||||||
case ESM::MagicEffect::DamageFatigue:
|
|
||||||
{
|
|
||||||
int index = effectKey.mId-ESM::MagicEffect::DamageHealth;
|
|
||||||
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
|
|
||||||
adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ESM::MagicEffect::AbsorbHealth:
|
|
||||||
if (magnitude > 0.f)
|
|
||||||
receivedMagicDamage = true;
|
|
||||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ESM::MagicEffect::AbsorbMagicka:
|
|
||||||
case ESM::MagicEffect::AbsorbFatigue:
|
|
||||||
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ESM::MagicEffect::DisintegrateArmor:
|
|
||||||
{
|
|
||||||
// According to UESP
|
|
||||||
int 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 (unsigned int i=0; i<sizeof(priorities)/sizeof(int); ++i)
|
|
||||||
{
|
|
||||||
if (disintegrateSlot(actor, priorities[i], magnitude))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ESM::MagicEffect::DisintegrateWeapon:
|
|
||||||
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ESM::MagicEffect::SunDamage:
|
|
||||||
{
|
|
||||||
// isInCell shouldn't be needed, but updateActor called during game start
|
|
||||||
if (!actor.isInCell() || !actor.getCell()->isExterior())
|
|
||||||
break;
|
|
||||||
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
|
|
||||||
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
|
|
||||||
float damageScale = 1.f - timeDiff / 7.f;
|
|
||||||
// When cloudy, the sun damage effect is halved
|
|
||||||
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
|
||||||
"fMagicSunBlockedMult")->mValue.getFloat();
|
|
||||||
|
|
||||||
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
|
|
||||||
if (weather > 1)
|
|
||||||
damageScale *= fMagicSunBlockedMult;
|
|
||||||
|
|
||||||
adjustDynamicStat(creatureStats, 0, -magnitude * damageScale);
|
|
||||||
if (magnitude * damageScale > 0.f)
|
|
||||||
receivedMagicDamage = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ESM::MagicEffect::FireDamage:
|
|
||||||
case ESM::MagicEffect::ShockDamage:
|
|
||||||
case ESM::MagicEffect::FrostDamage:
|
|
||||||
case ESM::MagicEffect::Poison:
|
|
||||||
{
|
|
||||||
adjustDynamicStat(creatureStats, 0, -magnitude);
|
|
||||||
receivedMagicDamage = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ESM::MagicEffect::DamageSkill:
|
|
||||||
case ESM::MagicEffect::RestoreSkill:
|
|
||||||
{
|
|
||||||
if (!actor.getClass().isNpc())
|
|
||||||
break;
|
|
||||||
NpcStats &npcStats = actor.getClass().getNpcStats(actor);
|
|
||||||
SkillValue& skill = npcStats.getSkill(effectKey.mArg);
|
|
||||||
if (effectKey.mId == ESM::MagicEffect::RestoreSkill)
|
|
||||||
skill.restore(magnitude);
|
|
||||||
else
|
|
||||||
skill.damage(magnitude);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case ESM::MagicEffect::CurePoison:
|
|
||||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
|
|
||||||
break;
|
|
||||||
case ESM::MagicEffect::CureParalyzation:
|
|
||||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
|
||||||
break;
|
|
||||||
case ESM::MagicEffect::CureCommonDisease:
|
|
||||||
actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease();
|
|
||||||
break;
|
|
||||||
case ESM::MagicEffect::CureBlightDisease:
|
|
||||||
actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease();
|
|
||||||
break;
|
|
||||||
case ESM::MagicEffect::CureCorprusDisease:
|
|
||||||
actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease();
|
|
||||||
break;
|
|
||||||
case ESM::MagicEffect::RemoveCurse:
|
|
||||||
actor.getClass().getCreatureStats(actor).getSpells().purgeCurses();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (receivedMagicDamage && actor == getPlayer())
|
|
||||||
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getSummonedCreature(int effectId)
|
|
||||||
{
|
|
||||||
static const std::map<int, std::string> summonMap
|
|
||||||
{
|
|
||||||
{ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"},
|
|
||||||
{ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"},
|
|
||||||
{ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"},
|
|
||||||
{ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"},
|
|
||||||
{ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"},
|
|
||||||
{ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"},
|
|
||||||
{ESM::MagicEffect::SummonDremora, "sMagicDremoraID"},
|
|
||||||
{ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"},
|
|
||||||
{ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"},
|
|
||||||
{ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"},
|
|
||||||
{ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"},
|
|
||||||
{ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"},
|
|
||||||
{ESM::MagicEffect::SummonHunger, "sMagicHungerID"},
|
|
||||||
{ESM::MagicEffect::SummonScamp, "sMagicScampID"},
|
|
||||||
{ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"},
|
|
||||||
{ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"},
|
|
||||||
{ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"},
|
|
||||||
{ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"},
|
|
||||||
{ESM::MagicEffect::SummonBear, "sMagicCreature02ID"},
|
|
||||||
{ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"},
|
|
||||||
{ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"},
|
|
||||||
{ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto it = summonMap.find(effectId);
|
|
||||||
if (it != summonMap.end())
|
|
||||||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(it->second)->mValue.getString();
|
|
||||||
return std::string();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ApplyLoopingParticlesVisitor::visit (MWMechanics::EffectKey key,
|
|
||||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
|
||||||
float /*magnitude*/, float /*remainingTime*/, float /*totalTime*/)
|
|
||||||
{
|
|
||||||
const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(key.mId);
|
|
||||||
if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0)
|
|
||||||
return;
|
|
||||||
const ESM::Static* castStatic;
|
|
||||||
if (!magicEffect->mHit.empty())
|
|
||||||
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
|
|
||||||
else
|
|
||||||
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_DefaultHit");
|
|
||||||
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor);
|
|
||||||
if (anim && !castStatic->mModel.empty())
|
|
||||||
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
#ifndef MWMECHANICS_SPELLSUCCESS_H
|
#ifndef MWMECHANICS_SPELLCASTING_H
|
||||||
#define MWMECHANICS_SPELLSUCCESS_H
|
#define MWMECHANICS_SPELLCASTING_H
|
||||||
|
|
||||||
#include <components/esm/effectlist.hpp>
|
#include <components/esm/effectlist.hpp>
|
||||||
#include <components/esm/loadskil.hpp>
|
|
||||||
#include <components/esm/loadmgef.hpp>
|
#include <components/esm/loadmgef.hpp>
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
#include "magiceffects.hpp"
|
|
||||||
|
|
||||||
namespace ESM
|
namespace ESM
|
||||||
{
|
{
|
||||||
struct Spell;
|
struct Spell;
|
||||||
|
@ -20,62 +17,6 @@ namespace ESM
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
struct EffectKey;
|
struct EffectKey;
|
||||||
class MagicEffects;
|
|
||||||
class CreatureStats;
|
|
||||||
|
|
||||||
ESM::Skill::SkillEnum spellSchoolToSkill(int school);
|
|
||||||
|
|
||||||
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr);
|
|
||||||
|
|
||||||
bool isSummoningEffect(int effectId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param spell spell to cast
|
|
||||||
* @param actor calculate spell success chance for this actor (depends on actor's skills)
|
|
||||||
* @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here
|
|
||||||
* @param cap cap the result to 100%?
|
|
||||||
* @param checkMagicka check magicka?
|
|
||||||
* @note actor can be an NPC or a creature
|
|
||||||
* @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned.
|
|
||||||
*/
|
|
||||||
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
|
||||||
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
|
||||||
|
|
||||||
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
|
|
||||||
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
|
||||||
|
|
||||||
/// Get whether or not the given spell contributes to skill progress.
|
|
||||||
bool spellIncreasesSkill(const ESM::Spell* spell);
|
|
||||||
bool spellIncreasesSkill(const std::string& spellId);
|
|
||||||
|
|
||||||
/// Get the resistance attribute against an effect for a given actor. This will add together
|
|
||||||
/// ResistX and Weakness to X effects relevant against the given effect.
|
|
||||||
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects);
|
|
||||||
|
|
||||||
/// Get the effective resistance against an effect casted by the given actor in the given spell (optional).
|
|
||||||
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
|
|
||||||
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
|
|
||||||
/// being applied (but not applied yet) that should also be considered.
|
|
||||||
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
|
||||||
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
|
|
||||||
|
|
||||||
/// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional).
|
|
||||||
/// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness)
|
|
||||||
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
|
|
||||||
/// being applied (but not applied yet) that should also be considered.
|
|
||||||
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
|
||||||
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
|
|
||||||
|
|
||||||
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer);
|
|
||||||
|
|
||||||
int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);
|
|
||||||
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool);
|
|
||||||
|
|
||||||
/// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
|
|
||||||
/// @return Was the effect a tickable effect with a magnitude?
|
|
||||||
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const MWMechanics::EffectKey& effectKey, float magnitude);
|
|
||||||
|
|
||||||
std::string getSummonedCreature(int effectId);
|
|
||||||
|
|
||||||
class CastSpell
|
class CastSpell
|
||||||
{
|
{
|
||||||
|
@ -113,8 +54,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
void playSpellCastingEffects(const std::string &spellid, bool enchantment);
|
void playSpellCastingEffects(const std::string &spellid, bool enchantment);
|
||||||
|
|
||||||
bool spellIncreasesSkill();
|
|
||||||
|
|
||||||
/// Launch a bolt with the given effects.
|
/// Launch a bolt with the given effects.
|
||||||
void launchMagicBolt ();
|
void launchMagicBolt ();
|
||||||
|
|
||||||
|
@ -127,22 +66,6 @@ namespace MWMechanics
|
||||||
/// @return was the target suitable for the effect?
|
/// @return was the target suitable for the effect?
|
||||||
bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude);
|
bool applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude);
|
||||||
};
|
};
|
||||||
|
|
||||||
class ApplyLoopingParticlesVisitor : public EffectSourceVisitor
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
MWWorld::Ptr mActor;
|
|
||||||
|
|
||||||
public:
|
|
||||||
ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor)
|
|
||||||
: mActor(actor)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void visit (MWMechanics::EffectKey key,
|
|
||||||
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
|
||||||
float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -15,9 +15,11 @@
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
#include "spellcasting.hpp"
|
#include "spellresistance.hpp"
|
||||||
#include "weapontype.hpp"
|
#include "weapontype.hpp"
|
||||||
#include "combat.hpp"
|
#include "combat.hpp"
|
||||||
|
#include "summoning.hpp"
|
||||||
|
#include "spellutil.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
93
apps/openmw/mwmechanics/spellresistance.cpp
Normal file
93
apps/openmw/mwmechanics/spellresistance.cpp
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#include "spellresistance.hpp"
|
||||||
|
|
||||||
|
#include <components/misc/rng.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
|
#include "creaturestats.hpp"
|
||||||
|
#include "spellutil.hpp"
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
|
||||||
|
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||||
|
const ESM::Spell* spell, const MagicEffects* effects)
|
||||||
|
{
|
||||||
|
if (!actor.getClass().isActor())
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
float resistance = getEffectResistance(effectId, actor, caster, spell, effects);
|
||||||
|
return 1 - resistance / 100.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||||
|
const ESM::Spell* spell, const MagicEffects* effects)
|
||||||
|
{
|
||||||
|
// Effects with no resistance attribute belonging to them can not be resisted
|
||||||
|
if (ESM::MagicEffect::getResistanceEffect(effectId) == -1)
|
||||||
|
return 0.f;
|
||||||
|
|
||||||
|
const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectId);
|
||||||
|
|
||||||
|
const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||||
|
const MWMechanics::MagicEffects* magicEffects = &stats.getMagicEffects();
|
||||||
|
if (effects)
|
||||||
|
magicEffects = effects;
|
||||||
|
|
||||||
|
float resistance = getEffectResistanceAttribute(effectId, magicEffects);
|
||||||
|
|
||||||
|
int willpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||||
|
float luck = static_cast<float>(stats.getAttribute(ESM::Attribute::Luck).getModified());
|
||||||
|
float x = (willpower + 0.1f * luck) * stats.getFatigueTerm();
|
||||||
|
|
||||||
|
// This makes spells that are easy to cast harder to resist and vice versa
|
||||||
|
float castChance = 100.f;
|
||||||
|
if (spell != nullptr && !caster.isEmpty() && caster.getClass().isActor())
|
||||||
|
castChance = getSpellSuccessChance(spell, caster, nullptr, false, false); // Uncapped casting chance
|
||||||
|
if (castChance > 0)
|
||||||
|
x *= 50 / castChance;
|
||||||
|
|
||||||
|
float roll = Misc::Rng::rollClosedProbability() * 100;
|
||||||
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||||
|
roll -= resistance;
|
||||||
|
|
||||||
|
if (x <= roll)
|
||||||
|
x = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)
|
||||||
|
x = 100;
|
||||||
|
else
|
||||||
|
x = roll / std::min(x, 100.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
x = std::min(x + resistance, 100.f);
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects)
|
||||||
|
{
|
||||||
|
short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId);
|
||||||
|
short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId);
|
||||||
|
|
||||||
|
float resistance = 0;
|
||||||
|
if (resistanceEffect != -1)
|
||||||
|
resistance += actorEffects->get(resistanceEffect).getMagnitude();
|
||||||
|
if (weaknessEffect != -1)
|
||||||
|
resistance -= actorEffects->get(weaknessEffect).getMagnitude();
|
||||||
|
|
||||||
|
if (effectId == ESM::MagicEffect::FireDamage)
|
||||||
|
resistance += actorEffects->get(ESM::MagicEffect::FireShield).getMagnitude();
|
||||||
|
if (effectId == ESM::MagicEffect::ShockDamage)
|
||||||
|
resistance += actorEffects->get(ESM::MagicEffect::LightningShield).getMagnitude();
|
||||||
|
if (effectId == ESM::MagicEffect::FrostDamage)
|
||||||
|
resistance += actorEffects->get(ESM::MagicEffect::FrostShield).getMagnitude();
|
||||||
|
|
||||||
|
return resistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
apps/openmw/mwmechanics/spellresistance.hpp
Normal file
37
apps/openmw/mwmechanics/spellresistance.hpp
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef MWMECHANICS_SPELLRESISTANCE_H
|
||||||
|
#define MWMECHANICS_SPELLRESISTANCE_H
|
||||||
|
|
||||||
|
namespace ESM
|
||||||
|
{
|
||||||
|
struct Spell;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWWorld
|
||||||
|
{
|
||||||
|
class Ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
class MagicEffects;
|
||||||
|
|
||||||
|
/// Get an effect multiplier for applying an effect cast by the given actor in the given spell (optional).
|
||||||
|
/// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness)
|
||||||
|
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
|
||||||
|
/// being applied (but not applied yet) that should also be considered.
|
||||||
|
float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||||
|
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
|
||||||
|
|
||||||
|
/// Get the effective resistance against an effect casted by the given actor in the given spell (optional).
|
||||||
|
/// @return >=100 for fully resisted. can also return negative value for damage amplification.
|
||||||
|
/// @param effects Override the actor's current magicEffects. Useful if there are effects currently
|
||||||
|
/// being applied (but not applied yet) that should also be considered.
|
||||||
|
float getEffectResistance (short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster,
|
||||||
|
const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr);
|
||||||
|
|
||||||
|
/// Get the resistance attribute against an effect for a given actor. This will add together
|
||||||
|
/// ResistX and Weakness to X effects relevant against the given effect.
|
||||||
|
float getEffectResistanceAttribute (short effectId, const MagicEffects* actorEffects);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
208
apps/openmw/mwmechanics/spellutil.cpp
Normal file
208
apps/openmw/mwmechanics/spellutil.cpp
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
#include "spellutil.hpp"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
|
#include "actorutil.hpp"
|
||||||
|
#include "creaturestats.hpp"
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
ESM::Skill::SkillEnum spellSchoolToSkill(int school)
|
||||||
|
{
|
||||||
|
static const std::array<ESM::Skill::SkillEnum, 6> schoolSkillArray
|
||||||
|
{
|
||||||
|
ESM::Skill::Alteration, ESM::Skill::Conjuration, ESM::Skill::Destruction,
|
||||||
|
ESM::Skill::Illusion, ESM::Skill::Mysticism, ESM::Skill::Restoration
|
||||||
|
};
|
||||||
|
return schoolSkillArray.at(school);
|
||||||
|
}
|
||||||
|
|
||||||
|
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect)
|
||||||
|
{
|
||||||
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
if (!magicEffect)
|
||||||
|
magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||||
|
bool hasMagnitude = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude);
|
||||||
|
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||||
|
int minMagn = hasMagnitude ? effect.mMagnMin : 1;
|
||||||
|
int maxMagn = hasMagnitude ? effect.mMagnMax : 1;
|
||||||
|
int duration = hasDuration ? effect.mDuration : 1;
|
||||||
|
static const float fEffectCostMult = store.get<ESM::GameSetting>().find("fEffectCostMult")->mValue.getFloat();
|
||||||
|
|
||||||
|
float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn));
|
||||||
|
x *= 0.1 * magicEffect->mData.mBaseCost;
|
||||||
|
x *= 1 + duration;
|
||||||
|
x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost;
|
||||||
|
|
||||||
|
return x * fEffectCostMult;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Each point of enchant skill above/under 10 subtracts/adds
|
||||||
|
* one percent of enchantment cost while minimum is 1.
|
||||||
|
*/
|
||||||
|
int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant);
|
||||||
|
const float result = castCost - (castCost / 100) * (eSkill - 10);
|
||||||
|
|
||||||
|
return static_cast<int>((result < 1) ? 1 : result);
|
||||||
|
}
|
||||||
|
|
||||||
|
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool)
|
||||||
|
{
|
||||||
|
// Morrowind for some reason uses a formula slightly different from magicka cost calculation
|
||||||
|
float y = std::numeric_limits<float>::max();
|
||||||
|
float lowestSkill = 0;
|
||||||
|
|
||||||
|
for (const ESM::ENAMstruct& effect : spell->mEffects.mList)
|
||||||
|
{
|
||||||
|
float x = static_cast<float>(effect.mDuration);
|
||||||
|
const auto magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||||
|
|
||||||
|
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce))
|
||||||
|
x = std::max(1.f, x);
|
||||||
|
|
||||||
|
x *= 0.1f * magicEffect->mData.mBaseCost;
|
||||||
|
x *= 0.5f * (effect.mMagnMin + effect.mMagnMax);
|
||||||
|
x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost;
|
||||||
|
if (effect.mRange == ESM::RT_Target)
|
||||||
|
x *= 1.5f;
|
||||||
|
static const float fEffectCostMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||||
|
"fEffectCostMult")->mValue.getFloat();
|
||||||
|
x *= fEffectCostMult;
|
||||||
|
|
||||||
|
float s = 2.0f * actor.getClass().getSkill(actor, spellSchoolToSkill(magicEffect->mData.mSchool));
|
||||||
|
if (s - x < y)
|
||||||
|
{
|
||||||
|
y = s - x;
|
||||||
|
if (effectiveSchool)
|
||||||
|
*effectiveSchool = magicEffect->mData.mSchool;
|
||||||
|
lowestSkill = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||||
|
|
||||||
|
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||||
|
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||||
|
|
||||||
|
float castChance = (lowestSkill - spell->mData.mCost + 0.2f * actorWillpower + 0.1f * actorLuck);
|
||||||
|
|
||||||
|
return castChance;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
||||||
|
{
|
||||||
|
bool godmode = actor == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||||
|
|
||||||
|
CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||||
|
|
||||||
|
if (stats.getMagicEffects().get(ESM::MagicEffect::Silence).getMagnitude() && !godmode)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (spell->mData.mType == ESM::Spell::ST_Power)
|
||||||
|
return stats.getSpells().canUsePower(spell) ? 100 : 0;
|
||||||
|
|
||||||
|
if (godmode)
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
if (checkMagicka && stats.getMagicka().getCurrent() < spell->mData.mCost)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
float castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).getMagnitude();
|
||||||
|
float castChance = calcSpellBaseSuccessChance(spell, actor, effectiveSchool) + castBonus;
|
||||||
|
castChance *= stats.getFatigueTerm();
|
||||||
|
|
||||||
|
return std::max(0.f, cap ? std::min(100.f, castChance) : castChance);
|
||||||
|
}
|
||||||
|
|
||||||
|
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool, bool cap, bool checkMagicka)
|
||||||
|
{
|
||||||
|
if (const auto spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId))
|
||||||
|
return getSpellSuccessChance(spell, actor, effectiveSchool, cap, checkMagicka);
|
||||||
|
return 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
||||||
|
{
|
||||||
|
int school = 0;
|
||||||
|
getSpellSuccessChance(spellId, actor, &school);
|
||||||
|
return school;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
||||||
|
{
|
||||||
|
int school = 0;
|
||||||
|
getSpellSuccessChance(spell, actor, &school);
|
||||||
|
return school;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool spellIncreasesSkill(const ESM::Spell *spell)
|
||||||
|
{
|
||||||
|
return spell->mData.mType == ESM::Spell::ST_Spell && !(spell->mData.mFlags & ESM::Spell::F_Always);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool spellIncreasesSkill(const std::string &spellId)
|
||||||
|
{
|
||||||
|
const auto spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search(spellId);
|
||||||
|
return spell && spellIncreasesSkill(spell);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer)
|
||||||
|
{
|
||||||
|
switch (effectId)
|
||||||
|
{
|
||||||
|
case ESM::MagicEffect::Levitate:
|
||||||
|
{
|
||||||
|
if (!MWBase::Environment::get().getWorld()->isLevitationEnabled())
|
||||||
|
{
|
||||||
|
if (castByPlayer)
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESM::MagicEffect::Soultrap:
|
||||||
|
{
|
||||||
|
if (!target.getClass().isNpc() // no messagebox for NPCs
|
||||||
|
&& (target.getTypeName() == typeid(ESM::Creature).name() && target.get<ESM::Creature>()->mBase->mData.mSoul == 0))
|
||||||
|
{
|
||||||
|
if (castByPlayer)
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidTarget}");
|
||||||
|
return true; // must still apply to get visual effect and have target regard it as attack
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESM::MagicEffect::WaterWalking:
|
||||||
|
{
|
||||||
|
if (target.getClass().isPureWaterCreature(target) && MWBase::Environment::get().getWorld()->isSwimming(target))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||||
|
|
||||||
|
if (!world->isWaterWalkingCastableOnTarget(target))
|
||||||
|
{
|
||||||
|
if (castByPlayer && caster == target)
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox ("#{sMagicInvalidEffect}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
50
apps/openmw/mwmechanics/spellutil.hpp
Normal file
50
apps/openmw/mwmechanics/spellutil.hpp
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#ifndef MWMECHANICS_SPELLUTIL_H
|
||||||
|
#define MWMECHANICS_SPELLUTIL_H
|
||||||
|
|
||||||
|
#include <components/esm/loadskil.hpp>
|
||||||
|
|
||||||
|
namespace ESM
|
||||||
|
{
|
||||||
|
struct ENAMstruct;
|
||||||
|
struct MagicEffect;
|
||||||
|
struct Spell;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWWorld
|
||||||
|
{
|
||||||
|
class Ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
ESM::Skill::SkillEnum spellSchoolToSkill(int school);
|
||||||
|
|
||||||
|
float calcEffectCost(const ESM::ENAMstruct& effect, const ESM::MagicEffect* magicEffect = nullptr);
|
||||||
|
|
||||||
|
int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param spell spell to cast
|
||||||
|
* @param actor calculate spell success chance for this actor (depends on actor's skills)
|
||||||
|
* @param effectiveSchool the spell's effective school (relevant for skill progress) will be written here
|
||||||
|
* @param cap cap the result to 100%?
|
||||||
|
* @param checkMagicka check magicka?
|
||||||
|
* @note actor can be an NPC or a creature
|
||||||
|
* @return success chance from 0 to 100 (in percent), if cap=false then chance above 100 may be returned.
|
||||||
|
*/
|
||||||
|
float calcSpellBaseSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool);
|
||||||
|
float getSpellSuccessChance (const ESM::Spell* spell, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
||||||
|
float getSpellSuccessChance (const std::string& spellId, const MWWorld::Ptr& actor, int* effectiveSchool = nullptr, bool cap=true, bool checkMagicka=true);
|
||||||
|
|
||||||
|
int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor);
|
||||||
|
int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||||
|
|
||||||
|
/// Get whether or not the given spell contributes to skill progress.
|
||||||
|
bool spellIncreasesSkill(const ESM::Spell* spell);
|
||||||
|
bool spellIncreasesSkill(const std::string& spellId);
|
||||||
|
|
||||||
|
/// Check if the given effect can be applied to the target. If \a castByPlayer, emits a message box on failure.
|
||||||
|
bool checkEffectTarget (int effectId, const MWWorld::Ptr& target, const MWWorld::Ptr& caster, bool castByPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -13,20 +13,55 @@
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
#include "spellcasting.hpp"
|
|
||||||
#include "creaturestats.hpp"
|
#include "creaturestats.hpp"
|
||||||
#include "aifollow.hpp"
|
#include "aifollow.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
|
||||||
UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor)
|
bool isSummoningEffect(int effectId)
|
||||||
: mActor(actor)
|
|
||||||
{
|
{
|
||||||
|
return ((effectId >= ESM::MagicEffect::SummonScamp && effectId <= ESM::MagicEffect::SummonStormAtronach)
|
||||||
|
|| (effectId == ESM::MagicEffect::SummonCenturionSphere)
|
||||||
|
|| (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05));
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateSummonedCreatures::~UpdateSummonedCreatures()
|
std::string getSummonedCreature(int effectId)
|
||||||
|
{
|
||||||
|
static const std::map<int, std::string> summonMap
|
||||||
|
{
|
||||||
|
{ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID"},
|
||||||
|
{ESM::MagicEffect::SummonBonelord, "sMagicBonelordID"},
|
||||||
|
{ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID"},
|
||||||
|
{ESM::MagicEffect::SummonCenturionSphere, "sMagicCenturionSphereID"},
|
||||||
|
{ESM::MagicEffect::SummonClannfear, "sMagicClannfearID"},
|
||||||
|
{ESM::MagicEffect::SummonDaedroth, "sMagicDaedrothID"},
|
||||||
|
{ESM::MagicEffect::SummonDremora, "sMagicDremoraID"},
|
||||||
|
{ESM::MagicEffect::SummonFabricant, "sMagicFabricantID"},
|
||||||
|
{ESM::MagicEffect::SummonFlameAtronach, "sMagicFlameAtronachID"},
|
||||||
|
{ESM::MagicEffect::SummonFrostAtronach, "sMagicFrostAtronachID"},
|
||||||
|
{ESM::MagicEffect::SummonGoldenSaint, "sMagicGoldenSaintID"},
|
||||||
|
{ESM::MagicEffect::SummonGreaterBonewalker, "sMagicGreaterBonewalkerID"},
|
||||||
|
{ESM::MagicEffect::SummonHunger, "sMagicHungerID"},
|
||||||
|
{ESM::MagicEffect::SummonScamp, "sMagicScampID"},
|
||||||
|
{ESM::MagicEffect::SummonSkeletalMinion, "sMagicSkeletalMinionID"},
|
||||||
|
{ESM::MagicEffect::SummonStormAtronach, "sMagicStormAtronachID"},
|
||||||
|
{ESM::MagicEffect::SummonWingedTwilight, "sMagicWingedTwilightID"},
|
||||||
|
{ESM::MagicEffect::SummonWolf, "sMagicCreature01ID"},
|
||||||
|
{ESM::MagicEffect::SummonBear, "sMagicCreature02ID"},
|
||||||
|
{ESM::MagicEffect::SummonBonewolf, "sMagicCreature03ID"},
|
||||||
|
{ESM::MagicEffect::SummonCreature04, "sMagicCreature04ID"},
|
||||||
|
{ESM::MagicEffect::SummonCreature05, "sMagicCreature05ID"}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto it = summonMap.find(effectId);
|
||||||
|
if (it != summonMap.end())
|
||||||
|
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(it->second)->mValue.getString();
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor)
|
||||||
|
: mActor(actor)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,13 +9,16 @@
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
|
||||||
class CreatureStats;
|
class CreatureStats;
|
||||||
|
|
||||||
|
bool isSummoningEffect(int effectId);
|
||||||
|
|
||||||
|
std::string getSummonedCreature(int effectId);
|
||||||
|
|
||||||
struct UpdateSummonedCreatures : public EffectSourceVisitor
|
struct UpdateSummonedCreatures : public EffectSourceVisitor
|
||||||
{
|
{
|
||||||
UpdateSummonedCreatures(const MWWorld::Ptr& actor);
|
UpdateSummonedCreatures(const MWWorld::Ptr& actor);
|
||||||
virtual ~UpdateSummonedCreatures();
|
virtual ~UpdateSummonedCreatures() = default;
|
||||||
|
|
||||||
virtual void visit (MWMechanics::EffectKey key,
|
virtual void visit (MWMechanics::EffectKey key,
|
||||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||||
|
|
217
apps/openmw/mwmechanics/tickableeffects.cpp
Normal file
217
apps/openmw/mwmechanics/tickableeffects.cpp
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
#include "tickableeffects.hpp"
|
||||||
|
|
||||||
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/cellstore.hpp"
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/containerstore.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
#include "../mwworld/inventorystore.hpp"
|
||||||
|
|
||||||
|
#include "actorutil.hpp"
|
||||||
|
#include "npcstats.hpp"
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
void adjustDynamicStat(CreatureStats& creatureStats, int index, float magnitude, bool allowDecreaseBelowZero = false)
|
||||||
|
{
|
||||||
|
DynamicStat<float> stat = creatureStats.getDynamic(index);
|
||||||
|
stat.setCurrent(stat.getCurrent() + magnitude, allowDecreaseBelowZero);
|
||||||
|
creatureStats.setDynamic(index, stat);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool disintegrateSlot (const MWWorld::Ptr& ptr, int slot, float disintegrate)
|
||||||
|
{
|
||||||
|
if (!ptr.getClass().hasInventoryStore(ptr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
MWWorld::InventoryStore& inv = ptr.getClass().getInventoryStore(ptr);
|
||||||
|
MWWorld::ContainerStoreIterator item = inv.getSlot(slot);
|
||||||
|
|
||||||
|
if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor || item.getType() == MWWorld::ContainerStore::Type_Weapon))
|
||||||
|
{
|
||||||
|
if (!item->getClass().hasItemHealth(*item))
|
||||||
|
return false;
|
||||||
|
int charge = item->getClass().getItemHealth(*item);
|
||||||
|
if (charge == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Store remainder of disintegrate amount (automatically subtracted if > 1)
|
||||||
|
item->getCellRef().applyChargeRemainderToBeSubtracted(disintegrate - std::floor(disintegrate));
|
||||||
|
|
||||||
|
charge = item->getClass().getItemHealth(*item);
|
||||||
|
charge -= std::min(static_cast<int>(disintegrate), charge);
|
||||||
|
item->getCellRef().setCharge(charge);
|
||||||
|
|
||||||
|
if (charge == 0)
|
||||||
|
{
|
||||||
|
// Will unequip the broken item and try to find a replacement
|
||||||
|
if (ptr != getPlayer())
|
||||||
|
inv.autoEquip(ptr);
|
||||||
|
else
|
||||||
|
inv.unequipItem(*item, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey &effectKey, float magnitude)
|
||||||
|
{
|
||||||
|
if (magnitude == 0.f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool receivedMagicDamage = false;
|
||||||
|
|
||||||
|
switch (effectKey.mId)
|
||||||
|
{
|
||||||
|
case ESM::MagicEffect::DamageAttribute:
|
||||||
|
{
|
||||||
|
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
||||||
|
attr.damage(magnitude);
|
||||||
|
creatureStats.setAttribute(effectKey.mArg, attr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESM::MagicEffect::RestoreAttribute:
|
||||||
|
{
|
||||||
|
AttributeValue attr = creatureStats.getAttribute(effectKey.mArg);
|
||||||
|
attr.restore(magnitude);
|
||||||
|
creatureStats.setAttribute(effectKey.mArg, attr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESM::MagicEffect::RestoreHealth:
|
||||||
|
case ESM::MagicEffect::RestoreMagicka:
|
||||||
|
case ESM::MagicEffect::RestoreFatigue:
|
||||||
|
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::RestoreHealth, magnitude);
|
||||||
|
break;
|
||||||
|
case ESM::MagicEffect::DamageHealth:
|
||||||
|
receivedMagicDamage = true;
|
||||||
|
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::DamageHealth, -magnitude);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESM::MagicEffect::DamageMagicka:
|
||||||
|
case ESM::MagicEffect::DamageFatigue:
|
||||||
|
{
|
||||||
|
int index = effectKey.mId-ESM::MagicEffect::DamageHealth;
|
||||||
|
static const bool uncappedDamageFatigue = Settings::Manager::getBool("uncapped damage fatigue", "Game");
|
||||||
|
adjustDynamicStat(creatureStats, index, -magnitude, index == 2 && uncappedDamageFatigue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESM::MagicEffect::AbsorbHealth:
|
||||||
|
if (magnitude > 0.f)
|
||||||
|
receivedMagicDamage = true;
|
||||||
|
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESM::MagicEffect::AbsorbMagicka:
|
||||||
|
case ESM::MagicEffect::AbsorbFatigue:
|
||||||
|
adjustDynamicStat(creatureStats, effectKey.mId-ESM::MagicEffect::AbsorbHealth, -magnitude);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESM::MagicEffect::DisintegrateArmor:
|
||||||
|
{
|
||||||
|
static const std::array<int, 9> priorities
|
||||||
|
{
|
||||||
|
MWWorld::InventoryStore::Slot_CarriedLeft,
|
||||||
|
MWWorld::InventoryStore::Slot_Cuirass,
|
||||||
|
MWWorld::InventoryStore::Slot_LeftPauldron,
|
||||||
|
MWWorld::InventoryStore::Slot_RightPauldron,
|
||||||
|
MWWorld::InventoryStore::Slot_LeftGauntlet,
|
||||||
|
MWWorld::InventoryStore::Slot_RightGauntlet,
|
||||||
|
MWWorld::InventoryStore::Slot_Helmet,
|
||||||
|
MWWorld::InventoryStore::Slot_Greaves,
|
||||||
|
MWWorld::InventoryStore::Slot_Boots
|
||||||
|
};
|
||||||
|
for (const int priority : priorities)
|
||||||
|
{
|
||||||
|
if (disintegrateSlot(actor, priority, magnitude))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ESM::MagicEffect::DisintegrateWeapon:
|
||||||
|
disintegrateSlot(actor, MWWorld::InventoryStore::Slot_CarriedRight, magnitude);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ESM::MagicEffect::SunDamage:
|
||||||
|
{
|
||||||
|
// isInCell shouldn't be needed, but updateActor called during game start
|
||||||
|
if (!actor.isInCell() || !actor.getCell()->isExterior())
|
||||||
|
break;
|
||||||
|
float time = MWBase::Environment::get().getWorld()->getTimeStamp().getHour();
|
||||||
|
float timeDiff = std::min(7.f, std::max(0.f, std::abs(time - 13)));
|
||||||
|
float damageScale = 1.f - timeDiff / 7.f;
|
||||||
|
// When cloudy, the sun damage effect is halved
|
||||||
|
static float fMagicSunBlockedMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||||
|
"fMagicSunBlockedMult")->mValue.getFloat();
|
||||||
|
|
||||||
|
int weather = MWBase::Environment::get().getWorld()->getCurrentWeather();
|
||||||
|
if (weather > 1)
|
||||||
|
damageScale *= fMagicSunBlockedMult;
|
||||||
|
|
||||||
|
adjustDynamicStat(creatureStats, 0, -magnitude * damageScale);
|
||||||
|
if (magnitude * damageScale > 0.f)
|
||||||
|
receivedMagicDamage = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ESM::MagicEffect::FireDamage:
|
||||||
|
case ESM::MagicEffect::ShockDamage:
|
||||||
|
case ESM::MagicEffect::FrostDamage:
|
||||||
|
case ESM::MagicEffect::Poison:
|
||||||
|
{
|
||||||
|
adjustDynamicStat(creatureStats, 0, -magnitude);
|
||||||
|
receivedMagicDamage = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ESM::MagicEffect::DamageSkill:
|
||||||
|
case ESM::MagicEffect::RestoreSkill:
|
||||||
|
{
|
||||||
|
if (!actor.getClass().isNpc())
|
||||||
|
break;
|
||||||
|
NpcStats &npcStats = actor.getClass().getNpcStats(actor);
|
||||||
|
SkillValue& skill = npcStats.getSkill(effectKey.mArg);
|
||||||
|
if (effectKey.mId == ESM::MagicEffect::RestoreSkill)
|
||||||
|
skill.restore(magnitude);
|
||||||
|
else
|
||||||
|
skill.damage(magnitude);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ESM::MagicEffect::CurePoison:
|
||||||
|
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
|
||||||
|
break;
|
||||||
|
case ESM::MagicEffect::CureParalyzation:
|
||||||
|
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||||
|
break;
|
||||||
|
case ESM::MagicEffect::CureCommonDisease:
|
||||||
|
actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease();
|
||||||
|
break;
|
||||||
|
case ESM::MagicEffect::CureBlightDisease:
|
||||||
|
actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease();
|
||||||
|
break;
|
||||||
|
case ESM::MagicEffect::CureCorprusDisease:
|
||||||
|
actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease();
|
||||||
|
break;
|
||||||
|
case ESM::MagicEffect::RemoveCurse:
|
||||||
|
actor.getClass().getCreatureStats(actor).getSpells().purgeCurses();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivedMagicDamage && actor == getPlayer())
|
||||||
|
MWBase::Environment::get().getWindowManager()->activateHitOverlay(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
19
apps/openmw/mwmechanics/tickableeffects.hpp
Normal file
19
apps/openmw/mwmechanics/tickableeffects.hpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef MWMECHANICS_TICKABLEEFFECTS_H
|
||||||
|
#define MWMECHANICS_TICKABLEEFFECTS_H
|
||||||
|
|
||||||
|
namespace MWWorld
|
||||||
|
{
|
||||||
|
class Ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
class CreatureStats;
|
||||||
|
struct EffectKey;
|
||||||
|
|
||||||
|
/// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
|
||||||
|
/// @return Was the effect a tickable effect with a magnitude?
|
||||||
|
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -13,7 +13,7 @@
|
||||||
#include "combat.hpp"
|
#include "combat.hpp"
|
||||||
#include "aicombataction.hpp"
|
#include "aicombataction.hpp"
|
||||||
#include "spellpriority.hpp"
|
#include "spellpriority.hpp"
|
||||||
#include "spellcasting.hpp"
|
#include "spellutil.hpp"
|
||||||
#include "weapontype.hpp"
|
#include "weapontype.hpp"
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
#include "../mwbase/mechanicsmanager.hpp"
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellresistance.hpp"
|
||||||
|
#include "../mwmechanics/spellutil.hpp"
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
#include "../mwmechanics/weapontype.hpp"
|
#include "../mwmechanics/weapontype.hpp"
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
#include "../mwmechanics/movement.hpp"
|
#include "../mwmechanics/movement.hpp"
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellutil.hpp"
|
||||||
|
|
||||||
#include "class.hpp"
|
#include "class.hpp"
|
||||||
#include "ptr.hpp"
|
#include "ptr.hpp"
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
#include "../mwmechanics/levelledlist.hpp"
|
#include "../mwmechanics/levelledlist.hpp"
|
||||||
#include "../mwmechanics/combat.hpp"
|
#include "../mwmechanics/combat.hpp"
|
||||||
#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
|
#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
|
||||||
|
#include "../mwmechanics/summoning.hpp"
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
#include "../mwrender/animation.hpp"
|
||||||
#include "../mwrender/npcanimation.hpp"
|
#include "../mwrender/npcanimation.hpp"
|
||||||
|
@ -3160,12 +3161,42 @@ namespace MWWorld
|
||||||
mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection);
|
mProjectileManager->launchMagicBolt(spellId, caster, fallbackDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ApplyLoopingParticlesVisitor : public MWMechanics::EffectSourceVisitor
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
MWWorld::Ptr mActor;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ApplyLoopingParticlesVisitor(const MWWorld::Ptr& actor)
|
||||||
|
: mActor(actor)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void visit (MWMechanics::EffectKey key,
|
||||||
|
const std::string& /*sourceName*/, const std::string& /*sourceId*/, int /*casterActorId*/,
|
||||||
|
float /*magnitude*/, float /*remainingTime*/ = -1, float /*totalTime*/ = -1)
|
||||||
|
{
|
||||||
|
const ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
const auto magicEffect = store.get<ESM::MagicEffect>().find(key.mId);
|
||||||
|
if ((magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx) == 0)
|
||||||
|
return;
|
||||||
|
const ESM::Static* castStatic;
|
||||||
|
if (!magicEffect->mHit.empty())
|
||||||
|
castStatic = store.get<ESM::Static>().find (magicEffect->mHit);
|
||||||
|
else
|
||||||
|
castStatic = store.get<ESM::Static>().find ("VFX_DefaultHit");
|
||||||
|
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mActor);
|
||||||
|
if (anim && !castStatic->mModel.empty())
|
||||||
|
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, /*loop*/true, "", magicEffect->mParticle);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void World::applyLoopingParticles(const MWWorld::Ptr& ptr)
|
void World::applyLoopingParticles(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
const MWWorld::Class &cls = ptr.getClass();
|
const MWWorld::Class &cls = ptr.getClass();
|
||||||
if (cls.isActor())
|
if (cls.isActor())
|
||||||
{
|
{
|
||||||
MWMechanics::ApplyLoopingParticlesVisitor visitor(ptr);
|
ApplyLoopingParticlesVisitor visitor(ptr);
|
||||||
cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor);
|
cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor);
|
||||||
cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor);
|
cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor);
|
||||||
if (cls.hasInventoryStore(ptr))
|
if (cls.hasInventoryStore(ptr))
|
||||||
|
|
Loading…
Reference in a new issue