1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-15 22:19:54 +00:00
openmw-tes3mp/apps/openmw/mwmechanics/spellcasting.cpp

794 lines
36 KiB
C++
Raw Normal View History

#include "spellcasting.hpp"
2019-04-21 15:04:27 +00:00
#include <components/misc/constants.hpp>
2015-04-22 15:58:55 +00:00
#include <components/misc/rng.hpp>
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"
2014-01-10 20:26:24 +00:00
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/containerstore.hpp"
2014-01-01 16:06:21 +00:00
#include "../mwworld/actionteleport.hpp"
#include "../mwworld/player.hpp"
#include "../mwworld/class.hpp"
2014-02-23 19:11:05 +00:00
#include "../mwworld/cellstore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/inventorystore.hpp"
2015-05-29 23:00:24 +00:00
#include "../mwrender/animation.hpp"
2015-08-21 09:12:39 +00:00
#include "actorutil.hpp"
#include "aifollow.hpp"
#include "creaturestats.hpp"
#include "linkedeffects.hpp"
#include "spellabsorption.hpp"
2020-04-04 15:28:53 +00:00
#include "spellresistance.hpp"
#include "spellutil.hpp"
#include "summoning.hpp"
#include "tickableeffects.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 ()
2018-07-15 08:44:25 +00:00
{
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 =
2018-07-15 08:44:25 +00:00
(mTarget.getRefData().getPosition().asVec3() + offset) -
(mCaster.getRefData().getPosition().asVec3());
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection);
}
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded)
{
if (!target.isEmpty() && target.getClass().isActor())
{
// 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;
2020-04-25 20:56:05 +00:00
for (const ESM::ENAMstruct& effect : effects.mList)
{
2020-04-25 20:56:05 +00:00
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 && !target.isEmpty() && (spell->mData.mType == ESM::Spell::ST_Disease || spell->mData.mType == ESM::Spell::ST_Blight))
{
2015-08-20 06:17:02 +00:00
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();
2015-04-22 15:58:55 +00:00
if (Misc::Rng::roll0to99() <= x)
{
// Fully resisted, show message
2015-08-21 09:12:39 +00:00
if (target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
return;
}
}
ESM::EffectList reflectedEffects;
2014-05-14 20:16:39 +00:00
std::vector<ActiveSpells::ActiveEffect> appliedLastingEffects;
// HACK: cache target's magic effects here, and add any applied effects to it. Use the cached effects for determining resistance.
// This is required for Weakness effects in a spell to apply to any subsequent effects in the spell.
// Otherwise, they'd only apply after the whole spell was added.
MagicEffects targetEffects;
if (!target.isEmpty() && target.getClass().isActor())
targetEffects += target.getClass().getCreatureStats(target).getMagicEffects();
2015-08-21 09:12:39 +00:00
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
ActiveSpells targetSpells;
if (!target.isEmpty() && target.getClass().isActor())
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
2020-04-27 09:43:34 +00:00
// Try absorbing the spell. Some handling must still happen for absorbed effects.
2020-09-21 21:33:27 +00:00
bool absorbed = absorbSpell(mId, caster, target);
2020-04-27 09:43:34 +00:00
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.isSpellActive(mId))
{
if (effectIt == (effects.mList.end() - 1) && !canCastAnEffect && castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicCannotRecast}");
continue;
}
2020-04-25 20:56:05 +00:00
canCastAnEffect = true;
if (!checkEffectTarget(effectIt->mEffectID, target, caster, castByPlayer))
continue;
// 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;
2020-04-27 09:43:34 +00:00
// 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);
2020-04-27 09:43:34 +00:00
// Avoid proceeding further for absorbed spells.
if (absorbed)
continue;
// Reflect harmful effects
if (!reflected && reflectEffect(*effectIt, magicEffect, caster, target, reflectedEffects))
continue;
2020-04-27 09:43:34 +00:00
// Try resisting.
float magnitudeMult = getEffectMultiplier(effectIt->mEffectID, target, caster, spell, &targetEffects);
if (magnitudeMult == 0)
{
2020-04-27 09:43:34 +00:00
// Fully resisted, show message
if (target == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
else if (castByPlayer)
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
}
2020-04-27 09:43:34 +00:00
else
{
float magnitude = effectIt->mMagnMin + Misc::Rng::rollDice(effectIt->mMagnMax - effectIt->mMagnMin + 1);
magnitude *= magnitudeMult;
if (!target.getClass().isActor())
{
// non-actor objects have no list of active magic effects, so have to apply instantly
if (!applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude))
continue;
}
else // target.getClass().isActor() == true
{
ActiveSpells::ActiveEffect effect;
effect.mEffectId = effectIt->mEffectID;
effect.mArg = MWMechanics::EffectKey(*effectIt).mArg;
effect.mMagnitude = magnitude;
effect.mTimeLeft = 0.f;
effect.mEffectIndex = currentEffectIndex;
// Avoid applying absorb effects if the caster is the target
// We still need the spell to be added
if (caster == target
&& effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute
&& effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
{
effect.mMagnitude = 0;
}
2020-04-27 09:43:34 +00:00
// 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 && !target.getClass().getCreatureStats(target).isDead() && effectAffectsHealth)
2020-04-27 09:43:34 +00:00
{
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
2020-04-27 09:43:34 +00:00
MWBase::Environment::get().getWindowManager()->setEnemy(target);
}
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);
if (effect.mDuration == 0)
{
// We still should add effect to list to allow GetSpellEffects to detect this spell
appliedLastingEffects.push_back(effect);
// duration 0 means apply full magnitude instantly
bool wasDead = target.getClass().getCreatureStats(target).isDead();
2020-04-27 09:43:34 +00:00
effectTick(target.getClass().getCreatureStats(target), target, EffectKey(*effectIt), effect.mMagnitude);
bool isDead = target.getClass().getCreatureStats(target).isDead();
if (!wasDead && isDead)
MWBase::Environment::get().getMechanicsManager()->actorKilled(target, caster);
}
else
{
effect.mTimeLeft = effect.mDuration;
targetEffects.add(MWMechanics::EffectKey(*effectIt), MWMechanics::EffectParam(effect.mMagnitude));
// add to list of active effects, to apply in next frame
appliedLastingEffects.push_back(effect);
// Unequip all items, if a spell with the ExtraSpell effect was casted
if (effectIt->mEffectID == ESM::MagicEffect::ExtraSpell && target.getClass().hasInventoryStore(target))
{
MWWorld::InventoryStore& store = target.getClass().getInventoryStore(target);
store.unequipAll(target);
}
// Command spells should have their effect, including taking the target out of combat, each time the spell successfully affects the target
if (((effectIt->mEffectID == ESM::MagicEffect::CommandHumanoid && target.getClass().isNpc())
|| (effectIt->mEffectID == ESM::MagicEffect::CommandCreature && target.getTypeName() == typeid(ESM::Creature).name()))
2020-04-27 09:43:34 +00:00
&& !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);
}
// For absorb effects, also apply the effect to the caster - but with a negative
2016-12-14 15:39:33 +00:00
// magnitude, since we're transferring stats from the target to the caster
if (effectIt->mEffectID >= ESM::MagicEffect::AbsorbAttribute && effectIt->mEffectID <= ESM::MagicEffect::AbsorbSkill)
absorbStat(*effectIt, effect, caster, target, reflected, mSourceName);
}
}
// Re-casting a summon effect will remove the creature from previous castings of that effect.
if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor())
{
CreatureStats& targetStats = target.getClass().getCreatureStats(target);
2020-08-28 18:43:22 +00:00
ESM::SummonKey key(effectIt->mEffectID, mId, currentEffectIndex);
auto findCreature = targetStats.getSummonedCreatureMap().find(key);
2016-10-05 16:12:06 +00:00
if (findCreature != targetStats.getSummonedCreatureMap().end())
{
2016-10-05 16:12:06 +00:00
MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(target, findCreature->second);
targetStats.getSummonedCreatureMap().erase(findCreature);
}
}
if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
{
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
2015-05-29 23:00:24 +00:00
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;
// Note: in case of non actor, a free effect should be fine as well
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
2020-04-25 20:56:05 +00:00
if (anim && !castStatic->mModel.empty())
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "", magicEffect->mParticle);
}
}
}
if (!exploded)
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile);
if (!target.isEmpty()) {
if (!reflectedEffects.mList.empty())
inflict(caster, target, reflectedEffects, range, true, exploded);
if (!appliedLastingEffects.empty())
{
int casterActorId = -1;
if (!caster.isEmpty() && caster.getClass().isActor())
casterActorId = caster.getClass().getCreatureStats(caster).getActorId();
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects,
mSourceName, casterActorId);
}
}
}
bool CastSpell::applyInstantEffect(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, const MWMechanics::EffectKey& effect, float magnitude)
{
short effectId = effect.mId;
if (target.getClass().canLock(target))
2016-08-09 15:43:14 +00:00
{
if (effectId == ESM::MagicEffect::Lock)
{
2016-08-09 14:50:24 +00:00
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
const ESM::MagicEffect *magiceffect = store.get<ESM::MagicEffect>().find(effectId);
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation)
animation->addSpellCastGlow(magiceffect);
if (target.getCellRef().getLockLevel() < magnitude) //If the door is not already locked to a higher value, lock it to spell magnitude
{
2015-08-21 09:12:39 +00:00
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}");
2019-09-17 18:30:37 +00:00
target.getCellRef().lock(static_cast<int>(magnitude));
}
return true;
}
else if (effectId == ESM::MagicEffect::Open)
{
if (!caster.isEmpty())
{
MWBase::Environment::get().getMechanicsManager()->unlockAttempted(getPlayer(), target);
// Use the player instead of the caster for vanilla crime compatibility
}
2016-08-09 14:50:24 +00:00
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
const ESM::MagicEffect *magiceffect = store.get<ESM::MagicEffect>().find(effectId);
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target);
if (animation)
animation->addSpellCastGlow(magiceffect);
if (target.getCellRef().getLockLevel() <= magnitude)
{
if (target.getCellRef().getLockLevel() > 0)
2014-01-10 20:26:24 +00:00
{
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);
2015-08-21 09:12:39 +00:00
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicOpenSuccess}");
2014-01-10 20:26:24 +00:00
}
2019-09-17 18:30:37 +00:00
target.getCellRef().unlock();
}
else
{
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f);
}
return true;
}
}
2017-01-03 21:02:23 +00:00
else if (target.getClass().isActor() && effectId == ESM::MagicEffect::Dispel)
{
target.getClass().getCreatureStats(target).getActiveSpells().purgeAll(magnitude, true);
2017-01-03 21:02:23 +00:00
return true;
}
else if (target.getClass().isActor() && effectId == ESM::MagicEffect::CurePoison)
{
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
return true;
}
else if (target.getClass().isActor() && effectId == ESM::MagicEffect::CureParalyzation)
{
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
return true;
}
else if (target.getClass().isActor() && effectId == ESM::MagicEffect::CureCommonDisease)
{
target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease();
return true;
}
else if (target.getClass().isActor() && effectId == ESM::MagicEffect::CureBlightDisease)
{
target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease();
return true;
}
else if (target.getClass().isActor() && effectId == ESM::MagicEffect::CureCorprusDisease)
{
target.getClass().getCreatureStats(target).getActiveSpells().purgeCorprusDisease();
target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease();
return true;
}
else if (target.getClass().isActor() && effectId == ESM::MagicEffect::RemoveCurse)
{
target.getClass().getCreatureStats(target).getSpells().purgeCurses();
return true;
}
else if (target.getClass().isActor() && target == getPlayer())
{
2016-09-19 09:12:30 +00:00
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
bool teleportingEnabled = MWBase::Environment::get().getWorld()->isTeleportingEnabled();
2016-09-19 09:12:30 +00:00
if (effectId == ESM::MagicEffect::DivineIntervention || effectId == ESM::MagicEffect::AlmsiviIntervention)
{
if (!teleportingEnabled)
{
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
return true;
}
std::string marker = (effectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker";
MWBase::Environment::get().getWorld()->teleportToClosestMarker(target, marker);
anim->removeEffect(effectId);
2016-09-19 09:12:30 +00:00
const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>()
.search("VFX_Summon_end");
if (fx)
anim->addEffect("meshes\\" + fx->mModel, -1);
return true;
}
else if (effectId == ESM::MagicEffect::Mark)
{
if (teleportingEnabled)
{
MWBase::Environment::get().getWorld()->getPlayer().markPosition(
target.getCell(), target.getRefData().getPosition());
}
else if (caster == getPlayer())
{
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
}
return true;
}
else if (effectId == ESM::MagicEffect::Recall)
{
if (!teleportingEnabled)
{
if (caster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}");
return true;
}
2018-10-09 06:21:12 +00:00
MWWorld::CellStore* markedCell = nullptr;
2014-01-01 16:06:21 +00:00
ESM::Position markedPosition;
2014-01-01 16:06:21 +00:00
MWBase::Environment::get().getWorld()->getPlayer().getMarkedPosition(markedCell, markedPosition);
if (markedCell)
{
MWWorld::ActionTeleport action(markedCell->isExterior() ? "" : markedCell->getCell()->mName,
markedPosition, false);
2014-01-01 16:06:21 +00:00
action.execute(target);
anim->removeEffect(effectId);
2014-01-01 16:06:21 +00:00
}
return true;
}
}
return false;
}
bool CastSpell::cast(const std::string &id)
{
2020-04-26 18:37:19 +00:00
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
if (const auto spell = store.get<ESM::Spell>().search(id))
return cast(spell);
2020-04-26 18:37:19 +00:00
if (const auto potion = store.get<ESM::Potion>().search(id))
return cast(potion);
2020-04-26 18:37:19 +00:00
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, 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);
mStack = false;
2017-03-25 18:40:11 +00:00
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
bool isProjectile = false;
if (item.getTypeName() == typeid(ESM::Weapon).name())
{
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;
2017-03-25 18:40:11 +00:00
// Check if there's enough charge left
if (!godmode && (type == ESM::Enchantment::WhenUsed || (!isProjectile && type == ESM::Enchantment::WhenStrikes)))
{
2017-03-25 18:40:11 +00:00
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)
{
2015-08-21 09:12:39 +00:00
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)
{
2015-08-21 09:12:39 +00:00
if (mCaster == getPlayer())
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1);
}
else if (type == ESM::Enchantment::CastOnce)
2017-03-25 18:40:11 +00:00
{
if (!godmode)
item.getContainerStore()->remove(item, 1, mCaster);
2017-03-25 18:40:11 +00:00
}
else if (type == ESM::Enchantment::WhenStrikes)
{
2015-08-21 09:12:39 +00:00
if (mCaster == getPlayer())
mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 3);
}
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;
mStack = true;
inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self);
return true;
}
bool CastSpell::cast(const ESM::Spell* spell)
{
mSourceName = spell->mName;
mId = spell->mId;
mStack = false;
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
int school = 0;
2017-03-25 18:40:11 +00:00
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);
2017-03-25 18:40:11 +00:00
if (!godmode)
{
// Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss)
2018-08-29 15:38:12 +00:00
static const float fFatigueSpellBase = store.get<ESM::GameSetting>().find("fFatigueSpellBase")->mValue.getFloat();
static const float fFatigueSpellMult = store.get<ESM::GameSetting>().find("fFatigueSpellMult")->mValue.getFloat();
2017-03-25 18:40:11 +00:00
DynamicStat<float> fatigue = stats.getFatigue();
const float normalizedEncumbrance = mCaster.getClass().getNormalizedEncumbrance(mCaster);
2017-03-25 18:40:11 +00:00
float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult);
fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss);
stats.setFatigue(fatigue);
2017-03-25 18:40:11 +00:00
bool fail = false;
// Check success
float successChance = getSpellSuccessChance(spell, mCaster, nullptr, true, false);
if (Misc::Rng::roll0to99() >= successChance)
{
if (mCaster == getPlayer())
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
fail = true;
}
2017-03-25 18:40:11 +00:00
if (fail)
{
// Failure sound
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
2017-03-25 18:40:11 +00:00
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);
}
2020-04-26 18:37:19 +00:00
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())
2020-04-25 20:56:05 +00:00
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;
mStack = true;
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;
2020-04-26 18:37:19 +00:00
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);
2018-10-08 14:06:30 +00:00
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();
2015-04-22 15:58:55 +00:00
int roll = Misc::Rng::roll0to99();
if (roll > x)
{
// "X has no effect on you"
2020-04-26 18:37:19 +00:00
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))
{
2014-04-18 09:43:37 +00:00
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)
2018-07-15 08:44:25 +00:00
{
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
if (enchantment)
{
2020-04-25 20:56:05 +00:00
if (const auto spell = store.get<ESM::Enchantment>().search(spellid))
playSpellCastingEffects(spell->mEffects.mList);
}
else
{
2020-04-25 20:56:05 +00:00
if (const auto spell = store.get<ESM::Spell>().search(spellid))
playSpellCastingEffects(spell->mEffects.mList);
}
}
2018-07-15 08:44:25 +00:00
void CastSpell::playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects)
{
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
std::vector<std::string> addedEffects;
2020-04-25 20:56:05 +00:00
for (const ESM::ENAMstruct& effectData : effects)
2016-08-31 09:25:59 +00:00
{
2020-04-25 20:56:05 +00:00
const auto effect = store.get<ESM::MagicEffect>().find(effectData.mEffectID);
2018-07-15 08:44:25 +00:00
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(), "meshes\\" + castStatic->mModel) != addedEffects.end())
continue;
2020-04-25 20:56:05 +00:00
MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster);
2018-07-15 08:44:25 +00:00
if (animation)
{
2020-04-25 20:56:05 +00:00
animation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex, false, "", effect->mParticle);
2018-07-15 08:44:25 +00:00
}
else
{
2019-04-21 15:04:27 +00:00
// If the caster has no animation, add the effect directly to the effectManager
// We should scale it manually
osg::Vec3f bounds (MWBase::Environment::get().getWorld()->getHalfExtents(mCaster) * 2.f / Constants::UnitsPerFoot);
float scale = std::max({ bounds.x()/3.f, bounds.y()/3.f, bounds.z()/6.f });
2018-07-15 08:44:25 +00:00
float meshScale = !mCaster.getClass().isActor() ? mCaster.getCellRef().getScale() : 1.0f;
2020-04-25 20:56:05 +00:00
osg::Vec3f pos (mCaster.getRefData().getPosition().asVec3());
2018-07-15 08:44:25 +00:00
MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + castStatic->mModel, effect->mParticle, pos, scale * meshScale);
2016-08-31 09:25:59 +00:00
}
if (animation && !mCaster.getClass().isActor())
2016-08-31 09:25:59 +00:00
animation->addSpellCastGlow(effect);
2016-08-31 09:25:59 +00:00
static const std::string schools[] = {
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
};
2018-07-15 08:44:25 +00:00
addedEffects.push_back("meshes\\" + castStatic->mModel);
2016-08-31 09:25:59 +00:00
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);
}
}
}