Merge branch 'cast_spell' into 'master'

Minor refactor of CastSpell

See merge request OpenMW/openmw!2365
crashfix_debugdraw
psi29a 2 years ago
commit 52ca14d881

@ -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 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;
/// @see MWWorld::WeatherManager::isInStorm

@ -1426,30 +1426,31 @@ bool CharacterController::updateWeaponState()
else if (!spellid.empty() && spellCastResult != MWWorld::SpellCastState::PowerAlreadyUsed)
{
world->breakInvisibility(mPtr);
MWMechanics::CastSpell cast(mPtr, nullptr, false, mCastingManualSpell);
cast.playSpellCastingEffects(spellid, isMagicItem);
MWMechanics::CastSpell cast(mPtr, {}, false, mCastingManualSpell);
std::vector<ESM::ENAMstruct> effects;
const std::vector<ESM::ENAMstruct>* effects{nullptr};
const MWWorld::ESMStore &store = world->getStore();
if (isMagicItem)
{
const ESM::Enchantment *enchantment = store.get<ESM::Enchantment>().find(spellid);
effects = enchantment->mEffects.mList;
effects = &enchantment->mEffects.mList;
cast.playSpellCastingEffects(enchantment);
}
else
{
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
effects = spell->mEffects.mList;
effects = &spell->mEffects.mList;
cast.playSpellCastingEffects(spell);
}
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 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"))
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 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 offset(0, 0, 0);
@ -56,8 +145,7 @@ namespace MWMechanics
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)
void CastSpell::inflict(const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded) const
{
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
if (targetIsActor)
@ -81,27 +169,9 @@ namespace MWMechanics
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);
ActiveSpells::ActiveSpellParams params(*this, mCaster);
bool castByPlayer = (!caster.isEmpty() && caster == getPlayer());
bool castByPlayer = (!mCaster.isEmpty() && mCaster == getPlayer());
const ActiveSpells* targetSpells = nullptr;
if (targetIsActor)
@ -132,8 +202,7 @@ namespace MWMechanics
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()))
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked && (mCaster.isEmpty() || !mCaster.getClass().isActor()))
continue;
ActiveSpells::ActiveEffect effect;
@ -161,7 +230,7 @@ namespace MWMechanics
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 (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.
MWBase::Environment::get().getWindowManager()->setEnemy(target);
@ -174,7 +243,7 @@ namespace MWMechanics
}
if (!exploded)
MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, target, range, mId, mSourceName, mFromProjectile, mSlot);
explodeSpell(effects, target, range);
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
// and we can ignore reflection since non-actors cannot reflect spells
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();
if (const auto spell = store.get<ESM::Spell>().search(id))
@ -283,17 +352,17 @@ namespace MWMechanics
}
if (isProjectile)
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Self);
inflict(mTarget, enchantment->mEffects, ESM::RT_Self);
else
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
inflict(mCaster, enchantment->mEffects, ESM::RT_Self);
if (isProjectile || !mTarget.isEmpty())
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
inflict(mTarget, enchantment->mEffects, ESM::RT_Touch);
if (launchProjectile)
launchMagicBolt();
else if (isProjectile || !mTarget.isEmpty())
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Target);
inflict(mTarget, enchantment->mEffects, ESM::RT_Target);
return true;
}
@ -304,7 +373,7 @@ namespace MWMechanics
mId = potion->mId;
mType = ESM::ActiveSpells::Type_Consumable;
inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self);
inflict(mCaster, potion->mEffects, ESM::RT_Self);
return true;
}
@ -363,10 +432,10 @@ namespace MWMechanics
if (!mCaster.getClass().isActor())
playSpellCastingEffects(spell->mEffects.mList);
inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self);
inflict(mCaster, spell->mEffects, ESM::RT_Self);
if (!mTarget.isEmpty())
inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch);
inflict(mTarget, spell->mEffects, ESM::RT_Touch);
launchMagicBolt();
@ -430,27 +499,22 @@ namespace MWMechanics
ESM::EffectList effects;
effects.mList.push_back(effect);
inflict(mCaster, mCaster, effects, ESM::RT_Self);
inflict(mCaster, effects, ESM::RT_Self);
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();
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);
}
playSpellCastingEffects(enchantment->mEffects.mList);
}
void CastSpell::playSpellCastingEffects(const ESM::Spell* spell) const
{
playSpellCastingEffects(spell->mEffects.mList);
}
void CastSpell::playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects)
void CastSpell::playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects) const
{
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
std::vector<std::string> addedEffects;

@ -12,6 +12,7 @@ namespace ESM
struct Ingredient;
struct Potion;
struct EffectList;
struct Enchantment;
struct MagicEffect;
}
@ -25,7 +26,12 @@ namespace MWMechanics
MWWorld::Ptr mCaster; // 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:
std::string mId; // ID of spell, potion, item etc
@ -37,7 +43,6 @@ namespace MWMechanics
int mSlot{0};
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);
bool cast (const ESM::Spell* spell);
@ -54,15 +59,12 @@ namespace MWMechanics
/// @note Auto detects if spell, ingredient or potion
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 launchMagicBolt ();
void playSpellCastingEffects(const ESM::Spell* spell) const;
/// @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 MWWorld::Ptr& caster,
const ESM::EffectList& effects, ESM::RangeType range, bool exploded=false);
void inflict(const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded = false) const;
};
void playEffects(const MWWorld::Ptr& target, const ESM::MagicEffect& magicEffect, bool playNonLooping = true);

@ -1263,7 +1263,7 @@ namespace MWScript
return;
MWMechanics::CastSpell cast(ptr, target, false, true);
cast.playSpellCastingEffects(spell->mId, false);
cast.playSpellCastingEffects(spell);
cast.mHitPosition = target.getRefData().getPosition().asVec3();
cast.mAlwaysSucceed = true;
cast.cast(spell);

@ -575,7 +575,7 @@ namespace MWWorld
const MWWorld::Ptr& ptr = ref.getPtr();
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;
}

@ -3736,107 +3736,6 @@ namespace MWWorld
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)
{
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 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;
/// @see MWWorld::WeatherManager::isInStorm

Loading…
Cancel
Save