mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-25 15:35:23 +00:00
Merge pull request #2809 from Capostrophic/spellcasting
Refactor spellcasting header and implementation (task #5339)
This commit is contained in:
commit
b4aeb2711c
@ -83,9 +83,10 @@ add_openmw_dir (mwclass
|
||||
add_openmw_dir (mwmechanics
|
||||
mechanicsmanagerimp stat creaturestats magiceffects movement actorutil
|
||||
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
|
||||
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
|
||||
|
@ -19,7 +19,7 @@
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
|
@ -15,10 +15,10 @@
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spells.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
|
||||
#include "tooltips.hpp"
|
||||
#include "class.hpp"
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
#include "../mwmechanics/spells.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
@ -17,7 +17,7 @@
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include "mapwindow.hpp"
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "summoning.hpp"
|
||||
#include "combat.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "tickableeffects.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -14,7 +14,6 @@
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "npcstats.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "combat.hpp"
|
||||
#include "weaponpriority.hpp"
|
||||
#include "spellpriority.hpp"
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include "autocalcspell.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
|
||||
#include <limits>
|
||||
|
||||
@ -8,6 +7,7 @@
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "spellutil.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "npcstats.hpp"
|
||||
#include "movement.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellresistance.hpp"
|
||||
#include "difficultyscaling.hpp"
|
||||
#include "actorutil.hpp"
|
||||
#include "pathfinding.hpp"
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellutil.hpp"
|
||||
#include "actorutil.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 "aipursue.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellutil.hpp"
|
||||
#include "autocalcspell.hpp"
|
||||
#include "npcstats.hpp"
|
||||
#include "actorutil.hpp"
|
||||
@ -376,7 +376,7 @@ namespace MWMechanics
|
||||
{
|
||||
const std::string& spell = winMgr->getSelectedSpell();
|
||||
if (!spell.empty())
|
||||
winMgr->setSelectedSpell(spell, int(MWMechanics::getSpellSuccessChance(spell, mWatched)));
|
||||
winMgr->setSelectedSpell(spell, int(getSpellSuccessChance(spell, mWatched)));
|
||||
else
|
||||
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 <limits>
|
||||
#include <iomanip>
|
||||
|
||||
#include <components/misc/constants.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
@ -23,293 +19,19 @@
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "npcstats.hpp"
|
||||
#include "actorutil.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"
|
||||
|
||||
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)
|
||||
: mCaster(caster)
|
||||
, mTarget(target)
|
||||
@ -391,6 +113,9 @@ namespace MWMechanics
|
||||
// throughout the iteration of this spell's
|
||||
// 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());
|
||||
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt)
|
||||
{
|
||||
@ -418,89 +143,30 @@ namespace MWMechanics
|
||||
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
||||
continue;
|
||||
|
||||
// If player is healing someone, show the target's HP bar
|
||||
if (castByPlayer && target != caster
|
||||
&& effectIt->mEffectID == ESM::MagicEffect::RestoreHealth
|
||||
&& target.getClass().isActor())
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||
// Notify the target actor they've been hit
|
||||
bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful;
|
||||
if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful)
|
||||
target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true);
|
||||
|
||||
// Try absorbing if it's a spell
|
||||
// Unlike Reflect, this is done once per spell absorption effect source
|
||||
bool absorbed = false;
|
||||
if (spell && caster != target && target.getClass().isActor())
|
||||
// Avoid proceeding further for absorbed spells.
|
||||
if (absorbed)
|
||||
continue;
|
||||
|
||||
// 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);
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f)
|
||||
{
|
||||
GetAbsorptionProbability check;
|
||||
stats.getActiveSpells().visitEffectSources(check);
|
||||
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);
|
||||
}
|
||||
}
|
||||
// Fully resisted, show message
|
||||
if (target == getPlayer())
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||
else if (castByPlayer)
|
||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||
}
|
||||
|
||||
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)
|
||||
else
|
||||
{
|
||||
float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1);
|
||||
magnitude *= magnitudeMult;
|
||||
@ -527,6 +193,19 @@ namespace MWMechanics
|
||||
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);
|
||||
if (hasDuration && effectIt->mDuration == 0)
|
||||
{
|
||||
@ -536,7 +215,7 @@ namespace MWMechanics
|
||||
|
||||
// duration 0 means apply full magnitude instantly
|
||||
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();
|
||||
|
||||
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
|
||||
if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc())
|
||||
|| (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);
|
||||
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
|
||||
// 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)
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
|
||||
absorbStat(*effectIt, effect, caster, target, reflected, mSourceName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -767,16 +431,14 @@ namespace MWMechanics
|
||||
|
||||
bool CastSpell::cast(const std::string &id)
|
||||
{
|
||||
if (const ESM::Spell *spell =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
if (const auto spell = store.get<ESM::Spell>().search(id))
|
||||
return cast(spell);
|
||||
|
||||
if (const ESM::Potion *potion =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
||||
if (const auto potion = store.get<ESM::Potion>().search(id))
|
||||
return cast(potion);
|
||||
|
||||
if (const ESM::Ingredient *ingredient =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
||||
if (const auto ingredient = store.get<ESM::Ingredient>().search(id))
|
||||
return cast(ingredient);
|
||||
|
||||
throw std::runtime_error("ID type cannot be casted");
|
||||
@ -939,10 +601,9 @@ namespace MWMechanics
|
||||
stats.getSpells().usePower(spell);
|
||||
}
|
||||
|
||||
if (mCaster == getPlayer() && spellIncreasesSkill())
|
||||
mCaster.getClass().skillUsageSucceeded(mCaster,
|
||||
spellSchoolToSkill(school), 0);
|
||||
|
||||
if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
|
||||
mCaster.getClass().skillUsageSucceeded(mCaster, spellSchoolToSkill(school), 0);
|
||||
|
||||
// A non-actor doesn't play its spell cast effects from a character controller, so play them here
|
||||
if (!mCaster.getClass().isActor())
|
||||
playSpellCastingEffects(spell->mEffects.mList);
|
||||
@ -970,10 +631,8 @@ namespace MWMechanics
|
||||
effect.mRange = ESM::RT_Self;
|
||||
effect.mArea = 0;
|
||||
|
||||
const ESM::MagicEffect *magicEffect =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||
effect.mEffectID);
|
||||
|
||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
const auto magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
|
||||
|
||||
float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy) +
|
||||
@ -985,7 +644,7 @@ namespace MWMechanics
|
||||
if (roll > x)
|
||||
{
|
||||
// "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);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||
return false;
|
||||
@ -1086,280 +745,4 @@ namespace MWMechanics
|
||||
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
|
||||
#define MWMECHANICS_SPELLSUCCESS_H
|
||||
#ifndef MWMECHANICS_SPELLCASTING_H
|
||||
#define MWMECHANICS_SPELLCASTING_H
|
||||
|
||||
#include <components/esm/effectlist.hpp>
|
||||
#include <components/esm/loadskil.hpp>
|
||||
#include <components/esm/loadmgef.hpp>
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include "magiceffects.hpp"
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct Spell;
|
||||
@ -20,62 +17,6 @@ namespace ESM
|
||||
namespace MWMechanics
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -113,8 +54,6 @@ namespace MWMechanics
|
||||
|
||||
void playSpellCastingEffects(const std::string &spellid, bool enchantment);
|
||||
|
||||
bool spellIncreasesSkill();
|
||||
|
||||
/// Launch a bolt with the given effects.
|
||||
void launchMagicBolt ();
|
||||
|
||||
@ -127,22 +66,6 @@ namespace MWMechanics
|
||||
/// @return was the target suitable for the effect?
|
||||
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
|
||||
|
@ -15,9 +15,11 @@
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "creaturestats.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellresistance.hpp"
|
||||
#include "weapontype.hpp"
|
||||
#include "combat.hpp"
|
||||
#include "summoning.hpp"
|
||||
#include "spellutil.hpp"
|
||||
|
||||
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 "spellcasting.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
#include "aifollow.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor)
|
||||
: mActor(actor)
|
||||
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));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
class CreatureStats;
|
||||
|
||||
bool isSummoningEffect(int effectId);
|
||||
|
||||
std::string getSummonedCreature(int effectId);
|
||||
|
||||
struct UpdateSummonedCreatures : public EffectSourceVisitor
|
||||
{
|
||||
UpdateSummonedCreatures(const MWWorld::Ptr& actor);
|
||||
virtual ~UpdateSummonedCreatures();
|
||||
virtual ~UpdateSummonedCreatures() = default;
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
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 "aicombataction.hpp"
|
||||
#include "spellpriority.hpp"
|
||||
#include "spellcasting.hpp"
|
||||
#include "spellutil.hpp"
|
||||
#include "weapontype.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
|
@ -13,7 +13,8 @@
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellresistance.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/weapontype.hpp"
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwmechanics/npcstats.hpp"
|
||||
#include "../mwmechanics/spellcasting.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
|
||||
#include "class.hpp"
|
||||
#include "ptr.hpp"
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "../mwmechanics/levelledlist.hpp"
|
||||
#include "../mwmechanics/combat.hpp"
|
||||
#include "../mwmechanics/aiavoiddoor.hpp" //Used to tell actors to avoid doors
|
||||
#include "../mwmechanics/summoning.hpp"
|
||||
|
||||
#include "../mwrender/animation.hpp"
|
||||
#include "../mwrender/npcanimation.hpp"
|
||||
@ -3160,12 +3161,42 @@ namespace MWWorld
|
||||
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)
|
||||
{
|
||||
const MWWorld::Class &cls = ptr.getClass();
|
||||
if (cls.isActor())
|
||||
{
|
||||
MWMechanics::ApplyLoopingParticlesVisitor visitor(ptr);
|
||||
ApplyLoopingParticlesVisitor visitor(ptr);
|
||||
cls.getCreatureStats(ptr).getActiveSpells().visitEffectSources(visitor);
|
||||
cls.getCreatureStats(ptr).getSpells().visitEffectSources(visitor);
|
||||
if (cls.hasInventoryStore(ptr))
|
||||
|
Loading…
x
Reference in New Issue
Block a user