mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-01-31 06:32:39 +00:00
a914d7a9b0
Play spellcasting animation and VFX (but not hand VFX) if spellcasting failed due to insufficient magicka Apply spellcasting fatigue loss when the spellcasting starts instead of when the spell is applied
568 lines
23 KiB
C++
568 lines
23 KiB
C++
#include "spellcasting.hpp"
|
|
|
|
#include <components/misc/constants.hpp>
|
|
#include <components/misc/rng.hpp>
|
|
#include <components/misc/resourcehelpers.hpp>
|
|
|
|
#include "../mwbase/windowmanager.hpp"
|
|
#include "../mwbase/soundmanager.hpp"
|
|
#include "../mwbase/mechanicsmanager.hpp"
|
|
#include "../mwbase/environment.hpp"
|
|
#include "../mwbase/world.hpp"
|
|
|
|
#include "../mwworld/containerstore.hpp"
|
|
#include "../mwworld/actionteleport.hpp"
|
|
#include "../mwworld/player.hpp"
|
|
#include "../mwworld/class.hpp"
|
|
#include "../mwworld/cellstore.hpp"
|
|
#include "../mwworld/esmstore.hpp"
|
|
#include "../mwworld/inventorystore.hpp"
|
|
|
|
#include "../mwrender/animation.hpp"
|
|
|
|
#include "actorutil.hpp"
|
|
#include "aifollow.hpp"
|
|
#include "creaturestats.hpp"
|
|
#include "spelleffects.hpp"
|
|
#include "spellutil.hpp"
|
|
#include "summoning.hpp"
|
|
#include "weapontype.hpp"
|
|
|
|
namespace MWMechanics
|
|
{
|
|
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
|
|
: mCaster(caster)
|
|
, mTarget(target)
|
|
, mFromProjectile(fromProjectile)
|
|
, mManualSpell(manualSpell)
|
|
{
|
|
}
|
|
|
|
void CastSpell::launchMagicBolt ()
|
|
{
|
|
osg::Vec3f fallbackDirection(0, 1, 0);
|
|
osg::Vec3f offset(0, 0, 0);
|
|
if (!mTarget.isEmpty() && mTarget.getClass().isActor())
|
|
offset.z() = MWBase::Environment::get().getWorld()->getHalfExtents(mTarget).z();
|
|
|
|
// Fall back to a "caster to target" direction if we have no other means of determining it
|
|
// (e.g. when cast by a non-actor)
|
|
if (!mTarget.isEmpty())
|
|
fallbackDirection =
|
|
(mTarget.getRefData().getPosition().asVec3() + offset) -
|
|
(mCaster.getRefData().getPosition().asVec3());
|
|
|
|
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot);
|
|
}
|
|
|
|
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
|
const ESM::EffectList &effects, ESM::RangeType range, bool exploded)
|
|
{
|
|
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
|
|
if (targetIsActor)
|
|
{
|
|
// Early-out for characters that have departed.
|
|
const auto& stats = target.getClass().getCreatureStats(target);
|
|
if (stats.isDead() && stats.isDeathAnimationFinished())
|
|
return;
|
|
}
|
|
|
|
// If none of the effects need to apply, we can early-out
|
|
bool found = false;
|
|
for (const ESM::ENAMstruct& effect : effects.mList)
|
|
{
|
|
if (effect.mRange == range)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
return;
|
|
|
|
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
|
|
if (spell && targetIsActor && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
|
|
{
|
|
int requiredResistance = (spell->mData.mType == ESM::Spell::ST_Disease) ?
|
|
ESM::MagicEffect::ResistCommonDisease
|
|
: ESM::MagicEffect::ResistBlightDisease;
|
|
float x = target.getClass().getCreatureStats(target).getMagicEffects().get(requiredResistance).getMagnitude();
|
|
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
if (Misc::Rng::roll0to99(prng) <= x)
|
|
{
|
|
// Fully resisted, show message
|
|
if (target == getPlayer())
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
|
return;
|
|
}
|
|
}
|
|
|
|
ActiveSpells::ActiveSpellParams params(*this, caster);
|
|
|
|
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
|
|
|
|
const ActiveSpells* targetSpells = nullptr;
|
|
if (targetIsActor)
|
|
targetSpells = &target.getClass().getCreatureStats(target).getActiveSpells();
|
|
|
|
bool canCastAnEffect = false; // For bound equipment.If this remains false
|
|
// throughout the iteration of this spell's
|
|
// effects, we display a "can't re-cast" message
|
|
|
|
int currentEffectIndex = 0;
|
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
|
!target.isEmpty() && effectIt != effects.mList.end(); ++effectIt, ++currentEffectIndex)
|
|
{
|
|
if (effectIt->mRange != range)
|
|
continue;
|
|
|
|
const ESM::MagicEffect *magicEffect =
|
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
|
effectIt->mEffectID);
|
|
|
|
// Re-casting a bound equipment effect has no effect if the spell is still active
|
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable && targetSpells && targetSpells->isSpellActive(mId))
|
|
{
|
|
if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer)
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}");
|
|
continue;
|
|
}
|
|
canCastAnEffect = true;
|
|
|
|
// caster needs to be an actor for linked effects (e.g. Absorb)
|
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
|
|
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
|
continue;
|
|
|
|
ActiveSpells::ActiveEffect effect;
|
|
effect.mEffectId = effectIt->mEffectID;
|
|
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
|
|
effect.mMagnitude = 0.f;
|
|
effect.mMinMagnitude = effectIt->mMagnMin;
|
|
effect.mMaxMagnitude = effectIt->mMagnMax;
|
|
effect.mTimeLeft = 0.f;
|
|
effect.mEffectIndex = currentEffectIndex;
|
|
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
|
if(mManualSpell)
|
|
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect;
|
|
|
|
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
|
effect.mDuration = hasDuration ? static_cast<float>(effectIt->mDuration) : 1.f;
|
|
|
|
bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce;
|
|
if (!appliedOnce)
|
|
effect.mDuration = std::max(1.f, effect.mDuration);
|
|
|
|
effect.mTimeLeft = effect.mDuration;
|
|
|
|
// add to list of active effects, to apply in next frame
|
|
params.getEffects().emplace_back(effect);
|
|
|
|
bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
|
|
if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth)
|
|
{
|
|
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
|
|
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
|
}
|
|
|
|
if (!targetIsActor && magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
|
{
|
|
playEffects(target, *magicEffect);
|
|
}
|
|
}
|
|
|
|
if (!exploded)
|
|
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile, mSlot);
|
|
|
|
if (!target.isEmpty())
|
|
{
|
|
if (!params.getEffects().empty())
|
|
{
|
|
if(targetIsActor)
|
|
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params);
|
|
else
|
|
{
|
|
// Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway
|
|
// and we can ignore reflection since non-actors cannot reflect spells
|
|
for(auto& effect : params.getEffects())
|
|
applyMagicEffect(target, caster, params, effect, 0.f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CastSpell::cast(const std::string &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 auto potion = store.get<ESM::Potion>().search(id))
|
|
return cast(potion);
|
|
|
|
if (const auto ingredient = store.get<ESM::Ingredient>().search(id))
|
|
return cast(ingredient);
|
|
|
|
throw std::runtime_error("ID type cannot be casted");
|
|
}
|
|
|
|
bool CastSpell::cast(const MWWorld::Ptr &item, int slot, bool launchProjectile)
|
|
{
|
|
std::string enchantmentName = item.getClass().getEnchantment(item);
|
|
if (enchantmentName.empty())
|
|
throw std::runtime_error("can't cast an item without an enchantment");
|
|
|
|
mSourceName = item.getClass().getName(item);
|
|
mId = item.getCellRef().getRefId();
|
|
|
|
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
|
|
|
|
mSlot = slot;
|
|
|
|
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
|
bool isProjectile = false;
|
|
if (item.getType() == ESM::Weapon::sRecordId)
|
|
{
|
|
int type = item.get<ESM::Weapon>()->mBase->mData.mType;
|
|
ESM::WeaponType::Class weapclass = MWMechanics::getWeaponType(type)->mWeaponClass;
|
|
isProjectile = (weapclass == ESM::WeaponType::Thrown || weapclass == ESM::WeaponType::Ammo);
|
|
}
|
|
int type = enchantment->mData.mType;
|
|
|
|
// Check if there's enough charge left
|
|
if (!godmode && (type == ESM::Enchantment::WhenUsed || (!isProjectile && type == ESM::Enchantment::WhenStrikes)))
|
|
{
|
|
int castCost = getEffectiveEnchantmentCastCost(static_cast<float>(enchantment->mData.mCost), mCaster);
|
|
|
|
if (item.getCellRef().getEnchantmentCharge() == -1)
|
|
item.getCellRef().setEnchantmentCharge(static_cast<float>(enchantment->mData.mCharge));
|
|
|
|
if (item.getCellRef().getEnchantmentCharge() < castCost)
|
|
{
|
|
if (mCaster == getPlayer())
|
|
{
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
|
|
|
|
// Failure sound
|
|
int school = 0;
|
|
if (!enchantment->mEffects.mList.empty())
|
|
{
|
|
short effectId = enchantment->mEffects.mList.front().mEffectID;
|
|
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(effectId);
|
|
school = magicEffect->mData.mSchool;
|
|
}
|
|
|
|
static const std::string schools[] = {
|
|
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
|
};
|
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
|
sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f);
|
|
}
|
|
return false;
|
|
}
|
|
// Reduce charge
|
|
item.getCellRef().setEnchantmentCharge(item.getCellRef().getEnchantmentCharge() - castCost);
|
|
}
|
|
|
|
if (type == ESM::Enchantment::WhenUsed)
|
|
{
|
|
if (mCaster == getPlayer())
|
|
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
|
|
}
|
|
else if (type == ESM::Enchantment::CastOnce)
|
|
{
|
|
if (!godmode)
|
|
item.getContainerStore()->remove(item, 1, mCaster);
|
|
}
|
|
else if (type == ESM::Enchantment::WhenStrikes)
|
|
{
|
|
if (mCaster == getPlayer())
|
|
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3);
|
|
}
|
|
|
|
if (isProjectile)
|
|
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Self);
|
|
else
|
|
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
|
|
|
if (isProjectile || !mTarget.isEmpty())
|
|
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
|
|
|
|
if (launchProjectile)
|
|
launchMagicBolt();
|
|
else if (isProjectile || !mTarget.isEmpty())
|
|
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CastSpell::cast(const ESM::Potion* potion)
|
|
{
|
|
mSourceName = potion->mName;
|
|
mId = potion->mId;
|
|
mType = ESM::ActiveSpells::Type_Consumable;
|
|
|
|
inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CastSpell::cast(const ESM::Spell* spell)
|
|
{
|
|
mSourceName = spell->mName;
|
|
mId = spell->mId;
|
|
|
|
int school = 0;
|
|
|
|
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
|
|
|
if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell)
|
|
{
|
|
school = getSpellSchool(spell, mCaster);
|
|
|
|
CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster);
|
|
|
|
if (!godmode)
|
|
{
|
|
bool fail = false;
|
|
|
|
// Check success
|
|
float successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false);
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
if (Misc::Rng::roll0to99(prng) >= successChance)
|
|
{
|
|
if (mCaster == getPlayer())
|
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
|
|
fail = true;
|
|
}
|
|
|
|
if (fail)
|
|
{
|
|
// Failure sound
|
|
static const std::string schools[] = {
|
|
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
|
};
|
|
|
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
|
sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// A power can be used once per 24h
|
|
if (spell->mData.mType == ESM::Spell::ST_Power)
|
|
stats.getSpells().usePower(spell);
|
|
}
|
|
|
|
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);
|
|
|
|
inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self);
|
|
|
|
if (!mTarget.isEmpty())
|
|
inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch);
|
|
|
|
launchMagicBolt();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CastSpell::cast (const ESM::Ingredient* ingredient)
|
|
{
|
|
mId = ingredient->mId;
|
|
mType = ESM::ActiveSpells::Type_Consumable;
|
|
mSourceName = ingredient->mName;
|
|
|
|
ESM::ENAMstruct effect;
|
|
effect.mEffectID = ingredient->mData.mEffectID[0];
|
|
effect.mSkill = ingredient->mData.mSkills[0];
|
|
effect.mAttribute = ingredient->mData.mAttributes[0];
|
|
effect.mRange = ESM::RT_Self;
|
|
effect.mArea = 0;
|
|
|
|
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) +
|
|
0.2f * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified()
|
|
+ 0.1f * creatureStats.getAttribute (ESM::Attribute::Luck).getModified())
|
|
* creatureStats.getFatigueTerm();
|
|
|
|
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
|
int roll = Misc::Rng::roll0to99(prng);
|
|
if (roll > x)
|
|
{
|
|
// "X has no effect on you"
|
|
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;
|
|
}
|
|
|
|
float magnitude = 0;
|
|
float y = roll / std::min(x, 100.f);
|
|
y *= 0.25f * x;
|
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
|
effect.mDuration = 1;
|
|
else
|
|
effect.mDuration = static_cast<int>(y);
|
|
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
|
{
|
|
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
|
magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost));
|
|
else
|
|
magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost));
|
|
magnitude = std::max(1.f, magnitude);
|
|
}
|
|
else
|
|
magnitude = 1;
|
|
|
|
effect.mMagnMax = static_cast<int>(magnitude);
|
|
effect.mMagnMin = static_cast<int>(magnitude);
|
|
|
|
ESM::EffectList effects;
|
|
effects.mList.push_back(effect);
|
|
|
|
inflict(mCaster, mCaster, effects, ESM::RT_Self);
|
|
|
|
return true;
|
|
}
|
|
|
|
void CastSpell::playSpellCastingEffects(const std::string &spellid, bool enchantment)
|
|
{
|
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
|
if (enchantment)
|
|
{
|
|
if (const auto spell = store.get<ESM::Enchantment>().search(spellid))
|
|
playSpellCastingEffects(spell->mEffects.mList);
|
|
}
|
|
else
|
|
{
|
|
if (const auto spell = store.get<ESM::Spell>().search(spellid))
|
|
playSpellCastingEffects(spell->mEffects.mList);
|
|
}
|
|
}
|
|
|
|
void CastSpell::playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects)
|
|
{
|
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
|
std::vector<std::string> addedEffects;
|
|
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
|
|
|
for (const ESM::ENAMstruct& effectData : effects)
|
|
{
|
|
const auto effect = store.get<ESM::MagicEffect>().find(effectData.mEffectID);
|
|
|
|
const ESM::Static* castStatic;
|
|
|
|
if (!effect->mCasting.empty())
|
|
castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
|
else
|
|
castStatic = store.get<ESM::Static>().find ("VFX_DefaultCast");
|
|
|
|
// check if the effect was already added
|
|
if (std::find(addedEffects.begin(), addedEffects.end(),
|
|
Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs))
|
|
!= addedEffects.end())
|
|
continue;
|
|
|
|
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
|
|
if (animation)
|
|
{
|
|
animation->addEffect(
|
|
Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs),
|
|
effect->mIndex, false, "", effect->mParticle);
|
|
}
|
|
else
|
|
{
|
|
// If the caster has no animation, add the effect directly to the effectManager
|
|
// We must scale and position it manually
|
|
float scale = mCaster.getCellRef().getScale();
|
|
osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3());
|
|
if (!mCaster.getClass().isNpc())
|
|
{
|
|
osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mCaster) * 2.f);
|
|
scale *= std::max({bounds.x(), bounds.y(), bounds.z() / 2.f}) / 64.f;
|
|
float offset = 0.f;
|
|
if (bounds.z() < 128.f)
|
|
offset = bounds.z() - 128.f;
|
|
else if (bounds.z() < bounds.x() + bounds.y())
|
|
offset = 128.f - bounds.z();
|
|
if (MWBase::Environment::get().getWorld()->isFlying(mCaster))
|
|
offset /= 20.f;
|
|
pos.z() += offset * scale;
|
|
}
|
|
else
|
|
{
|
|
// Additionally use the NPC's height
|
|
osg::Vec3f npcScaleVec (1.f, 1.f, 1.f);
|
|
mCaster.getClass().adjustScale(mCaster, npcScaleVec, true);
|
|
scale *= npcScaleVec.z();
|
|
}
|
|
scale = std::max(scale, 1.f);
|
|
MWBase::Environment::get().getWorld()->spawnEffect(
|
|
Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs),
|
|
effect->mParticle, pos, scale);
|
|
}
|
|
|
|
if (animation && !mCaster.getClass().isActor())
|
|
animation->addSpellCastGlow(effect);
|
|
|
|
static const std::string schools[] = {
|
|
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
|
};
|
|
|
|
addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs));
|
|
|
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
|
if(!effect->mCastSound.empty())
|
|
sndMgr->playSound3D(mCaster, effect->mCastSound, 1.0f, 1.0f);
|
|
else
|
|
sndMgr->playSound3D(mCaster, schools[effect->mData.mSchool]+" cast", 1.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping)
|
|
{
|
|
if (playNonLooping)
|
|
{
|
|
static const std::string schools[] = {
|
|
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
|
};
|
|
|
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
|
if(!magicEffect.mHitSound.empty())
|
|
sndMgr->playSound3D(target, magicEffect.mHitSound, 1.0f, 1.0f);
|
|
else
|
|
sndMgr->playSound3D(target, schools[magicEffect.mData.mSchool]+" hit", 1.0f, 1.0f);
|
|
}
|
|
|
|
// Add VFX
|
|
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");
|
|
|
|
bool loop = (magicEffect.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
|
|
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
|
|
if(anim && !castStatic->mModel.empty())
|
|
{
|
|
// Don't play particle VFX unless the effect is new or it should be looping.
|
|
if (playNonLooping || loop)
|
|
{
|
|
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
|
anim->addEffect(
|
|
Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs),
|
|
magicEffect.mIndex, loop, "", magicEffect.mParticle);
|
|
}
|
|
}
|
|
}
|
|
}
|