mirror of
https://github.com/OpenMW/openmw.git
synced 2025-11-02 15:26:39 +00:00
Merge branch 'cast_spell' into 'master'
Minor refactor of CastSpell See merge request OpenMW/openmw!2365
This commit is contained in:
commit
52ca14d881
8 changed files with 135 additions and 177 deletions
|
|
@ -599,10 +599,6 @@ namespace MWBase
|
||||||
|
|
||||||
virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0;
|
virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) = 0;
|
||||||
|
|
||||||
virtual void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster,
|
|
||||||
const MWWorld::Ptr& ignore, ESM::RangeType rangeType, const std::string& id,
|
|
||||||
const std::string& sourceName, const bool fromProjectile=false, int slot = 0) = 0;
|
|
||||||
|
|
||||||
virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
|
virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0;
|
||||||
|
|
||||||
/// @see MWWorld::WeatherManager::isInStorm
|
/// @see MWWorld::WeatherManager::isInStorm
|
||||||
|
|
|
||||||
|
|
@ -1426,30 +1426,31 @@ bool CharacterController::updateWeaponState()
|
||||||
else if (!spellid.empty() && spellCastResult != MWWorld::SpellCastState::PowerAlreadyUsed)
|
else if (!spellid.empty() && spellCastResult != MWWorld::SpellCastState::PowerAlreadyUsed)
|
||||||
{
|
{
|
||||||
world->breakInvisibility(mPtr);
|
world->breakInvisibility(mPtr);
|
||||||
MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell);
|
MWMechanics::CastSpell cast(mPtr, {}, false, mCastingManualSpell);
|
||||||
cast.playSpellCastingEffects(spellid, isMagicItem);
|
|
||||||
|
|
||||||
std::vector<ESM::ENAMstruct> effects;
|
const std::vector<ESM::ENAMstruct>* effects{nullptr};
|
||||||
const MWWorld::ESMStore &store = world->getStore();
|
const MWWorld::ESMStore &store = world->getStore();
|
||||||
if (isMagicItem)
|
if (isMagicItem)
|
||||||
{
|
{
|
||||||
const ESM::Enchantment *enchantment = store.get<ESM::Enchantment>().find(spellid);
|
const ESM::Enchantment *enchantment = store.get<ESM::Enchantment>().find(spellid);
|
||||||
effects = enchantment->mEffects.mList;
|
effects = &enchantment->mEffects.mList;
|
||||||
|
cast.playSpellCastingEffects(enchantment);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
|
||||||
effects = spell->mEffects.mList;
|
effects = &spell->mEffects.mList;
|
||||||
|
cast.playSpellCastingEffects(spell);
|
||||||
}
|
}
|
||||||
if (mCanCast)
|
if (mCanCast)
|
||||||
{
|
{
|
||||||
const ESM::MagicEffect *effect = store.get<ESM::MagicEffect>().find(effects.back().mEffectID); // use last effect of list for color of VFX_Hands
|
const ESM::MagicEffect *effect = store.get<ESM::MagicEffect>().find(effects->back().mEffectID); // use last effect of list for color of VFX_Hands
|
||||||
|
|
||||||
const ESM::Static* castStatic = world->getStore().get<ESM::Static>().find ("VFX_Hands");
|
const ESM::Static* castStatic = world->getStore().get<ESM::Static>().find ("VFX_Hands");
|
||||||
|
|
||||||
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
||||||
|
|
||||||
for (size_t iter = 0; iter < effects.size(); ++iter) // play hands vfx for each effect
|
if (!effects->empty())
|
||||||
{
|
{
|
||||||
if (mAnimation->getNode("Bip01 L Hand"))
|
if (mAnimation->getNode("Bip01 L Hand"))
|
||||||
mAnimation->addEffect(
|
mAnimation->addEffect(
|
||||||
|
|
@ -1463,7 +1464,7 @@ bool CharacterController::updateWeaponState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ESM::ENAMstruct &firstEffect = effects.at(0); // first effect used for casting animation
|
const ESM::ENAMstruct& firstEffect = effects->at(0); // first effect used for casting animation
|
||||||
|
|
||||||
std::string startKey;
|
std::string startKey;
|
||||||
std::string stopKey;
|
std::string stopKey;
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,96 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void CastSpell::launchMagicBolt ()
|
void CastSpell::explodeSpell(const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const
|
||||||
|
{
|
||||||
|
const auto world = MWBase::Environment::get().getWorld();
|
||||||
|
const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
|
||||||
|
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
|
||||||
|
int index = -1;
|
||||||
|
for (const ESM::ENAMstruct& effectInfo : effects.mList)
|
||||||
|
{
|
||||||
|
++index;
|
||||||
|
const ESM::MagicEffect* effect = world->getStore().get<ESM::MagicEffect>().find(effectInfo.mEffectID);
|
||||||
|
|
||||||
|
if (effectInfo.mRange != rangeType || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
|
||||||
|
continue; // Not right range type, or not area effect and hit an actor
|
||||||
|
|
||||||
|
if (mFromProjectile && effectInfo.mArea <= 0)
|
||||||
|
continue; // Don't play explosion for projectiles with 0-area effects
|
||||||
|
|
||||||
|
if (!mFromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty() && !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore)
|
||||||
|
&& (mCaster.isEmpty() || mCaster.getClass().isActor()))
|
||||||
|
continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from a projectile enchantment or ExplodeSpell
|
||||||
|
|
||||||
|
// Spawn the explosion orb effect
|
||||||
|
const ESM::Static* areaStatic;
|
||||||
|
if (!effect->mArea.empty())
|
||||||
|
areaStatic = world->getStore().get<ESM::Static>().find(effect->mArea);
|
||||||
|
else
|
||||||
|
areaStatic = world->getStore().get<ESM::Static>().find("VFX_DefaultArea");
|
||||||
|
|
||||||
|
const std::string& texture = effect->mParticle;
|
||||||
|
|
||||||
|
if (effectInfo.mArea <= 0)
|
||||||
|
{
|
||||||
|
if (effectInfo.mRange == ESM::RT_Target)
|
||||||
|
world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture, mHitPosition, 1.0f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture, mHitPosition, static_cast<float>(effectInfo.mArea * 2));
|
||||||
|
|
||||||
|
// Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
|
||||||
|
static const std::string schools[] = {
|
||||||
|
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||||
|
};
|
||||||
|
{
|
||||||
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
|
if(!effect->mAreaSound.empty())
|
||||||
|
sndMgr->playSound3D(mHitPosition, effect->mAreaSound, 1.0f, 1.0f);
|
||||||
|
else
|
||||||
|
sndMgr->playSound3D(mHitPosition, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
// Get the actors in range of the effect
|
||||||
|
std::vector<MWWorld::Ptr> objects;
|
||||||
|
static const int unitsPerFoot = ceil(Constants::UnitsPerFoot);
|
||||||
|
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(mHitPosition, static_cast<float>(effectInfo.mArea * unitsPerFoot), objects);
|
||||||
|
for (const MWWorld::Ptr& affected : objects)
|
||||||
|
{
|
||||||
|
// Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range.
|
||||||
|
if (affected.getClass().isActor() && !world->isActorCollisionEnabled(affected))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto& list = toApply[affected];
|
||||||
|
while (list.size() < static_cast<std::size_t>(index))
|
||||||
|
{
|
||||||
|
// Insert dummy effects to preserve indices
|
||||||
|
auto& dummy = list.emplace_back(effectInfo);
|
||||||
|
dummy.mRange = ESM::RT_Self;
|
||||||
|
assert(dummy.mRange != rangeType);
|
||||||
|
}
|
||||||
|
list.push_back(effectInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now apply the appropriate effects to each actor in range
|
||||||
|
for (auto& applyPair : toApply)
|
||||||
|
{
|
||||||
|
// Vanilla-compatible behaviour of never applying the spell to the caster
|
||||||
|
// (could be changed by mods later)
|
||||||
|
if (applyPair.first == mCaster)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (applyPair.first == ignore)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ESM::EffectList effectsToApply;
|
||||||
|
effectsToApply.mList = applyPair.second;
|
||||||
|
inflict(applyPair.first, effectsToApply, rangeType, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CastSpell::launchMagicBolt() const
|
||||||
{
|
{
|
||||||
osg::Vec3f fallbackDirection(0, 1, 0);
|
osg::Vec3f fallbackDirection(0, 1, 0);
|
||||||
osg::Vec3f offset(0, 0, 0);
|
osg::Vec3f offset(0, 0, 0);
|
||||||
|
|
@ -56,8 +145,7 @@ namespace MWMechanics
|
||||||
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot);
|
MWBase::Environment::get().getWorld()->launchMagicBolt(mId, mCaster, fallbackDirection, mSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
void CastSpell::inflict(const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded) const
|
||||||
const ESM::EffectList &effects, ESM::RangeType range, bool exploded)
|
|
||||||
{
|
{
|
||||||
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
|
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
|
||||||
if (targetIsActor)
|
if (targetIsActor)
|
||||||
|
|
@ -81,27 +169,9 @@ namespace MWMechanics
|
||||||
if (!found)
|
if (!found)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
|
ActiveSpells::ActiveSpellParams params(*this, mCaster);
|
||||||
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();
|
bool castByPlayer = (!mCaster.isEmpty() && mCaster == getPlayer());
|
||||||
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;
|
const ActiveSpells* targetSpells = nullptr;
|
||||||
if (targetIsActor)
|
if (targetIsActor)
|
||||||
|
|
@ -132,8 +202,7 @@ namespace MWMechanics
|
||||||
canCastAnEffect = true;
|
canCastAnEffect = true;
|
||||||
|
|
||||||
// caster needs to be an actor for linked effects (e.g. Absorb)
|
// caster needs to be an actor for linked effects (e.g. Absorb)
|
||||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked && (mCaster.isEmpty() || !mCaster.getClass().isActor()))
|
||||||
&& (caster.isEmpty() || !caster.getClass().isActor()))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ActiveSpells::ActiveEffect effect;
|
ActiveSpells::ActiveEffect effect;
|
||||||
|
|
@ -161,7 +230,7 @@ namespace MWMechanics
|
||||||
params.getEffects().emplace_back(effect);
|
params.getEffects().emplace_back(effect);
|
||||||
|
|
||||||
bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
|
bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful || effectIt->mEffectID == ESM::MagicEffect::RestoreHealth;
|
||||||
if (castByPlayer && target != caster && targetIsActor && effectAffectsHealth)
|
if (castByPlayer && target != mCaster && targetIsActor && effectAffectsHealth)
|
||||||
{
|
{
|
||||||
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
|
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's HP bar.
|
||||||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||||
|
|
@ -174,7 +243,7 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exploded)
|
if (!exploded)
|
||||||
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile, mSlot);
|
explodeSpell(effects, target, range);
|
||||||
|
|
||||||
if (!target.isEmpty())
|
if (!target.isEmpty())
|
||||||
{
|
{
|
||||||
|
|
@ -187,13 +256,13 @@ namespace MWMechanics
|
||||||
// Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway
|
// Apply effects instantly. We can ignore effect deletion since the entire params object gets deleted afterwards anyway
|
||||||
// and we can ignore reflection since non-actors cannot reflect spells
|
// and we can ignore reflection since non-actors cannot reflect spells
|
||||||
for(auto& effect : params.getEffects())
|
for(auto& effect : params.getEffects())
|
||||||
applyMagicEffect(target, caster, params, effect, 0.f);
|
applyMagicEffect(target, mCaster, params, effect, 0.f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CastSpell::cast(const std::string &id)
|
bool CastSpell::cast(const std::string& id)
|
||||||
{
|
{
|
||||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
if (const auto spell = store.get<ESM::Spell>().search(id))
|
if (const auto spell = store.get<ESM::Spell>().search(id))
|
||||||
|
|
@ -283,17 +352,17 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isProjectile)
|
if (isProjectile)
|
||||||
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Self);
|
inflict(mTarget, enchantment->mEffects, ESM::RT_Self);
|
||||||
else
|
else
|
||||||
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
inflict(mCaster, enchantment->mEffects, ESM::RT_Self);
|
||||||
|
|
||||||
if (isProjectile || !mTarget.isEmpty())
|
if (isProjectile || !mTarget.isEmpty())
|
||||||
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
|
inflict(mTarget, enchantment->mEffects, ESM::RT_Touch);
|
||||||
|
|
||||||
if (launchProjectile)
|
if (launchProjectile)
|
||||||
launchMagicBolt();
|
launchMagicBolt();
|
||||||
else if (isProjectile || !mTarget.isEmpty())
|
else if (isProjectile || !mTarget.isEmpty())
|
||||||
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target);
|
inflict(mTarget, enchantment->mEffects, ESM::RT_Target);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -304,7 +373,7 @@ namespace MWMechanics
|
||||||
mId = potion->mId;
|
mId = potion->mId;
|
||||||
mType = ESM::ActiveSpells::Type_Consumable;
|
mType = ESM::ActiveSpells::Type_Consumable;
|
||||||
|
|
||||||
inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self);
|
inflict(mCaster, potion->mEffects, ESM::RT_Self);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -363,10 +432,10 @@ namespace MWMechanics
|
||||||
if (!mCaster.getClass().isActor())
|
if (!mCaster.getClass().isActor())
|
||||||
playSpellCastingEffects(spell->mEffects.mList);
|
playSpellCastingEffects(spell->mEffects.mList);
|
||||||
|
|
||||||
inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self);
|
inflict(mCaster, spell->mEffects, ESM::RT_Self);
|
||||||
|
|
||||||
if (!mTarget.isEmpty())
|
if (!mTarget.isEmpty())
|
||||||
inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch);
|
inflict(mTarget, spell->mEffects, ESM::RT_Touch);
|
||||||
|
|
||||||
launchMagicBolt();
|
launchMagicBolt();
|
||||||
|
|
||||||
|
|
@ -430,27 +499,22 @@ namespace MWMechanics
|
||||||
ESM::EffectList effects;
|
ESM::EffectList effects;
|
||||||
effects.mList.push_back(effect);
|
effects.mList.push_back(effect);
|
||||||
|
|
||||||
inflict(mCaster, mCaster, effects, ESM::RT_Self);
|
inflict(mCaster, effects, ESM::RT_Self);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CastSpell::playSpellCastingEffects(std::string_view spellid, bool enchantment)
|
void CastSpell::playSpellCastingEffects(const ESM::Enchantment* enchantment) const
|
||||||
{
|
{
|
||||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
playSpellCastingEffects(enchantment->mEffects.mList);
|
||||||
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)
|
void CastSpell::playSpellCastingEffects(const ESM::Spell* spell) const
|
||||||
|
{
|
||||||
|
playSpellCastingEffects(spell->mEffects.mList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CastSpell::playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects) const
|
||||||
{
|
{
|
||||||
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
std::vector<std::string> addedEffects;
|
std::vector<std::string> addedEffects;
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ namespace ESM
|
||||||
struct Ingredient;
|
struct Ingredient;
|
||||||
struct Potion;
|
struct Potion;
|
||||||
struct EffectList;
|
struct EffectList;
|
||||||
|
struct Enchantment;
|
||||||
struct MagicEffect;
|
struct MagicEffect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,7 +26,12 @@ namespace MWMechanics
|
||||||
MWWorld::Ptr mCaster; // May be empty
|
MWWorld::Ptr mCaster; // May be empty
|
||||||
MWWorld::Ptr mTarget; // May be empty
|
MWWorld::Ptr mTarget; // May be empty
|
||||||
|
|
||||||
void playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects);
|
void playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects) const;
|
||||||
|
|
||||||
|
void explodeSpell(const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const;
|
||||||
|
|
||||||
|
/// Launch a bolt with the given effects.
|
||||||
|
void launchMagicBolt() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string mId; // ID of spell, potion, item etc
|
std::string mId; // ID of spell, potion, item etc
|
||||||
|
|
@ -37,7 +43,6 @@ namespace MWMechanics
|
||||||
int mSlot{0};
|
int mSlot{0};
|
||||||
ESM::ActiveSpells::EffectType mType{ESM::ActiveSpells::Type_Temporary};
|
ESM::ActiveSpells::EffectType mType{ESM::ActiveSpells::Type_Temporary};
|
||||||
|
|
||||||
public:
|
|
||||||
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool manualSpell=false);
|
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile=false, const bool manualSpell=false);
|
||||||
|
|
||||||
bool cast (const ESM::Spell* spell);
|
bool cast (const ESM::Spell* spell);
|
||||||
|
|
@ -54,15 +59,12 @@ namespace MWMechanics
|
||||||
/// @note Auto detects if spell, ingredient or potion
|
/// @note Auto detects if spell, ingredient or potion
|
||||||
bool cast (const std::string& id);
|
bool cast (const std::string& id);
|
||||||
|
|
||||||
void playSpellCastingEffects(std::string_view spellid, bool enchantment);
|
void playSpellCastingEffects(const ESM::Enchantment* enchantment) const;
|
||||||
|
|
||||||
/// Launch a bolt with the given effects.
|
void playSpellCastingEffects(const ESM::Spell* spell) const;
|
||||||
void launchMagicBolt ();
|
|
||||||
|
|
||||||
/// @note \a target can be any type of object, not just actors.
|
/// @note \a target can be any type of object, not just actors.
|
||||||
/// @note \a caster can be any type of object, or even an empty object.
|
void inflict(const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded = false) const;
|
||||||
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster,
|
|
||||||
const ESM::EffectList& effects, ESM::RangeType range, bool exploded=false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true);
|
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true);
|
||||||
|
|
|
||||||
|
|
@ -1263,7 +1263,7 @@ namespace MWScript
|
||||||
return;
|
return;
|
||||||
|
|
||||||
MWMechanics::CastSpell cast(ptr, target, false, true);
|
MWMechanics::CastSpell cast(ptr, target, false, true);
|
||||||
cast.playSpellCastingEffects(spell->mId, false);
|
cast.playSpellCastingEffects(spell);
|
||||||
cast.mHitPosition = target.getRefData().getPosition().asVec3();
|
cast.mHitPosition = target.getRefData().getPosition().asVec3();
|
||||||
cast.mAlwaysSucceed = true;
|
cast.mAlwaysSucceed = true;
|
||||||
cast.cast(spell);
|
cast.cast(spell);
|
||||||
|
|
|
||||||
|
|
@ -575,7 +575,7 @@ namespace MWWorld
|
||||||
const MWWorld::Ptr& ptr = ref.getPtr();
|
const MWWorld::Ptr& ptr = ref.getPtr();
|
||||||
effects = &esmStore.get<ESM::Enchantment>().find(ptr.getClass().getEnchantment(ptr))->mEffects;
|
effects = &esmStore.get<ESM::Enchantment>().find(ptr.getClass().getEnchantment(ptr))->mEffects;
|
||||||
}
|
}
|
||||||
cast.inflict(target, caster, *effects, ESM::RT_Target);
|
cast.inflict(target, *effects, ESM::RT_Target);
|
||||||
|
|
||||||
magicBoltState.mToDelete = true;
|
magicBoltState.mToDelete = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3736,107 +3736,6 @@ namespace MWWorld
|
||||||
mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX);
|
mRendering->spawnEffect(model, textureOverride, worldPos, scale, isMagicVFX);
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const Ptr& caster, const Ptr& ignore, ESM::RangeType rangeType,
|
|
||||||
const std::string& id, const std::string& sourceName, const bool fromProjectile, int slot)
|
|
||||||
{
|
|
||||||
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
|
|
||||||
int index = -1;
|
|
||||||
for (const ESM::ENAMstruct& effectInfo : effects.mList)
|
|
||||||
{
|
|
||||||
++index;
|
|
||||||
const ESM::MagicEffect* effect = mStore.get<ESM::MagicEffect>().find(effectInfo.mEffectID);
|
|
||||||
|
|
||||||
if (effectInfo.mRange != rangeType || (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
|
|
||||||
continue; // Not right range type, or not area effect and hit an actor
|
|
||||||
|
|
||||||
if (fromProjectile && effectInfo.mArea <= 0)
|
|
||||||
continue; // Don't play explosion for projectiles with 0-area effects
|
|
||||||
|
|
||||||
if (!fromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty() && !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore)
|
|
||||||
&& (caster.isEmpty() || caster.getClass().isActor()))
|
|
||||||
continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from a projectile enchantment or ExplodeSpell
|
|
||||||
|
|
||||||
// Spawn the explosion orb effect
|
|
||||||
const ESM::Static* areaStatic;
|
|
||||||
if (!effect->mArea.empty())
|
|
||||||
areaStatic = mStore.get<ESM::Static>().find (effect->mArea);
|
|
||||||
else
|
|
||||||
areaStatic = mStore.get<ESM::Static>().find ("VFX_DefaultArea");
|
|
||||||
|
|
||||||
const std::string& texture = effect->mParticle;
|
|
||||||
|
|
||||||
if (effectInfo.mArea <= 0)
|
|
||||||
{
|
|
||||||
if (effectInfo.mRange == ESM::RT_Target)
|
|
||||||
mRendering->spawnEffect(
|
|
||||||
Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, mResourceSystem->getVFS()),
|
|
||||||
texture, origin, 1.0f);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mRendering->spawnEffect(
|
|
||||||
Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, mResourceSystem->getVFS()),
|
|
||||||
texture, origin, static_cast<float>(effectInfo.mArea * 2));
|
|
||||||
|
|
||||||
// Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
|
|
||||||
static const std::string schools[] = {
|
|
||||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
|
||||||
};
|
|
||||||
{
|
|
||||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
|
||||||
if(!effect->mAreaSound.empty())
|
|
||||||
sndMgr->playSound3D(origin, effect->mAreaSound, 1.0f, 1.0f);
|
|
||||||
else
|
|
||||||
sndMgr->playSound3D(origin, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
// Get the actors in range of the effect
|
|
||||||
std::vector<MWWorld::Ptr> objects;
|
|
||||||
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
|
|
||||||
origin, feetToGameUnits(static_cast<float>(effectInfo.mArea)), objects);
|
|
||||||
for (const Ptr& affected : objects)
|
|
||||||
{
|
|
||||||
// Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range.
|
|
||||||
if (affected.getClass().isActor() && !isActorCollisionEnabled(affected))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto& list = toApply[affected];
|
|
||||||
while (list.size() < static_cast<std::size_t>(index))
|
|
||||||
{
|
|
||||||
// Insert dummy effects to preserve indices
|
|
||||||
auto& dummy = list.emplace_back(effectInfo);
|
|
||||||
dummy.mRange = ESM::RT_Self;
|
|
||||||
assert(dummy.mRange != rangeType);
|
|
||||||
}
|
|
||||||
list.push_back(effectInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now apply the appropriate effects to each actor in range
|
|
||||||
for (auto& applyPair : toApply)
|
|
||||||
{
|
|
||||||
MWWorld::Ptr source = caster;
|
|
||||||
// Vanilla-compatible behaviour of never applying the spell to the caster
|
|
||||||
// (could be changed by mods later)
|
|
||||||
if (applyPair.first == caster)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (applyPair.first == ignore)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (source.isEmpty())
|
|
||||||
source = applyPair.first;
|
|
||||||
|
|
||||||
MWMechanics::CastSpell cast(source, applyPair.first);
|
|
||||||
cast.mHitPosition = origin;
|
|
||||||
cast.mId = id;
|
|
||||||
cast.mSourceName = sourceName;
|
|
||||||
cast.mSlot = slot;
|
|
||||||
ESM::EffectList effectsToApply;
|
|
||||||
effectsToApply.mList = applyPair.second;
|
|
||||||
cast.inflict(applyPair.first, caster, effectsToApply, rangeType, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void World::activate(const Ptr &object, const Ptr &actor)
|
void World::activate(const Ptr &object, const Ptr &actor)
|
||||||
{
|
{
|
||||||
breakInvisibility(actor);
|
breakInvisibility(actor);
|
||||||
|
|
|
||||||
|
|
@ -696,10 +696,6 @@ namespace MWWorld
|
||||||
|
|
||||||
void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override;
|
void spawnEffect (const std::string& model, const std::string& textureOverride, const osg::Vec3f& worldPos, float scale = 1.f, bool isMagicVFX = true) override;
|
||||||
|
|
||||||
void explodeSpell(const osg::Vec3f& origin, const ESM::EffectList& effects, const MWWorld::Ptr& caster, const MWWorld::Ptr& ignore,
|
|
||||||
ESM::RangeType rangeType, const std::string& id, const std::string& sourceName,
|
|
||||||
const bool fromProjectile=false, int slot = 0) override;
|
|
||||||
|
|
||||||
void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override;
|
void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override;
|
||||||
|
|
||||||
/// @see MWWorld::WeatherManager::isInStorm
|
/// @see MWWorld::WeatherManager::isInStorm
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue