diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 3ce98c0bf..92a1a4143 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -466,6 +466,9 @@ namespace MWBase /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0; + + virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, + const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; }; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4a244f6a9..b0caccc73 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -316,23 +316,9 @@ namespace MWClass enchantmentName); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { - // Check if we have enough charges - const float enchantCost = enchantment->mData.mCost; - int eSkill = getSkill(ptr, ESM::Skill::Enchant); - const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); - - if (weapon.getCellRef().mEnchantmentCharge == -1) - weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; - if (weapon.getCellRef().mEnchantmentCharge < castCost) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); - } - else - { - weapon.getCellRef().mEnchantmentCharge -= castCost; - MWMechanics::CastSpell cast(ptr, victim); - cast.cast(weapon); - } + MWMechanics::CastSpell cast(ptr, victim); + cast.mHitPosition = hitPosition; + cast.cast(weapon); } } } diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 7f75d910b..d7cd42489 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -576,28 +576,12 @@ namespace MWClass enchantmentName); if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { - // Check if we have enough charges - const float enchantCost = enchantment->mData.mCost; - int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); + MWMechanics::CastSpell cast(ptr, victim); + cast.mHitPosition = hitPosition; + bool success = cast.cast(weapon); - if (weapon.getCellRef().mEnchantmentCharge == -1) - weapon.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge; - if (weapon.getCellRef().mEnchantmentCharge < castCost) - { - if (ptr.getRefData().getHandle() == "player") - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); - } - else - { - weapon.getCellRef().mEnchantmentCharge -= castCost; - - MWMechanics::CastSpell cast(ptr, victim); - cast.cast(weapon); - - if (ptr.getRefData().getHandle() == "player") - skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3); - } + if (ptr.getRefData().getHandle() == "player" && success) + skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3); } } diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index f70050628..749a5d7b1 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -181,11 +181,12 @@ namespace MWMechanics : mCaster(caster) , mTarget(target) , mStack(false) + , mHitPosition(0,0,0) { } void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, - const ESM::EffectList &effects, ESM::RangeType range, bool reflected) + const ESM::EffectList &effects, ESM::RangeType range, bool reflected, bool exploded) { // If none of the effects need to apply, we can early-out bool found = false; @@ -375,11 +376,12 @@ namespace MWMechanics if (anim) anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); } - - // TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World. } } + if (!exploded) + MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, mTarget, effects, caster, mId, mSourceName); + if (reflectedEffects.mList.size()) inflict(caster, target, reflectedEffects, range, true); @@ -521,12 +523,11 @@ namespace MWMechanics mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce); - if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) + // Check if there's enough charge left + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { - // Check if there's enough charge left const float enchantCost = enchantment->mData.mCost; - MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster); - int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); + int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant); const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); if (item.getCellRef().mEnchantmentCharge == -1) @@ -539,10 +540,15 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); return false; } - // Reduce charge item.getCellRef().mEnchantmentCharge -= castCost; } + + if (enchantment->mData.mType == ESM::Enchantment::WhenUsed) + { + if (mCaster.getRefData().getHandle() == "player") + mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); + } if (enchantment->mData.mType == ESM::Enchantment::CastOnce) item.getContainerStore()->remove(item, 1, mCaster); else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes) @@ -551,9 +557,6 @@ namespace MWMechanics MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge } - if (mCaster.getRefData().getHandle() == "player") - mCaster.getClass().skillUsageSucceeded (mCaster, ESM::Skill::Enchant, 1); - inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self); if (!mTarget.isEmpty()) diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index b58e7bb09..74dc490ea 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -3,6 +3,8 @@ #include "../mwworld/ptr.hpp" +#include + namespace MWMechanics { class EffectKey; @@ -36,6 +38,7 @@ namespace MWMechanics bool mStack; std::string mId; // ID of spell, potion, item etc std::string mSourceName; // Display name for spell, potion, etc + Ogre::Vector3 mHitPosition; // Used for spawning area orb public: CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); @@ -49,7 +52,7 @@ namespace MWMechanics bool cast (const std::string& id); void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, - const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false); + const ESM::EffectList& effects, ESM::RangeType range, bool reflected=false, bool exploded=false); void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 637159475..27730767f 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -745,6 +745,7 @@ namespace MWScript MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWMechanics::CastSpell cast(ptr, target); + cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos); cast.cast(spell); } }; @@ -761,6 +762,7 @@ namespace MWScript runtime.pop(); MWMechanics::CastSpell cast(ptr, ptr); + cast.mHitPosition = Ogre::Vector3(ptr.getRefData().getPosition().pos); cast.cast(spell); } }; diff --git a/apps/openmw/mwworld/actiontrap.cpp b/apps/openmw/mwworld/actiontrap.cpp index d723b9823..bcefb0181 100644 --- a/apps/openmw/mwworld/actiontrap.cpp +++ b/apps/openmw/mwworld/actiontrap.cpp @@ -8,6 +8,7 @@ namespace MWWorld void ActionTrap::executeImp(const Ptr &actor) { MWMechanics::CastSpell cast(mTrapSource, actor); + cast.mHitPosition = Ogre::Vector3(actor.getRefData().getPosition().pos); cast.cast(mSpellId); mTrapSource.getCellRef().mTrap = ""; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 419558387..57c85317f 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -2106,6 +2106,8 @@ namespace MWWorld std::string selectedSpell = stats.getSpells().getSelectedSpell(); MWMechanics::CastSpell cast(actor, target); + if (!target.isEmpty()) + cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos); if (!selectedSpell.empty()) { @@ -2240,10 +2242,11 @@ namespace MWWorld else { MWMechanics::CastSpell cast(caster, obstacle); + cast.mHitPosition = pos; cast.mId = it->second.mId; cast.mSourceName = it->second.mSourceName; cast.mStack = it->second.mStack; - cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target); + cast.inflict(obstacle, caster, it->second.mEffects, ESM::RT_Target, false, true); } explode = true; @@ -2251,64 +2254,8 @@ namespace MWWorld if (explode) { - std::map > toApply; - for (std::vector::const_iterator effectIt = it->second.mEffects.mList.begin(); - effectIt != it->second.mEffects.mList.end(); ++effectIt) - { - const ESM::MagicEffect* effect = getStore().get().find(effectIt->mEffectID); - - if (effectIt->mArea <= 0) - continue; // Not an area effect - - // Spawn the explosion orb effect - const ESM::Static* areaStatic; - if (!effect->mCasting.empty()) - areaStatic = getStore().get().find (effect->mArea); - else - areaStatic = getStore().get().find ("VFX_DefaultArea"); - - mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", Ogre::Vector3(ptr.getRefData().getPosition().pos), effectIt->mArea); - - // 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(ptr, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); - else - sndMgr->playSound3D(ptr, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); - - // Get the actors in range of the effect - std::vector objects; - MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( - Ogre::Vector3(ptr.getRefData().getPosition().pos), feetToGameUnits(effectIt->mArea), objects); - - for (std::vector::iterator affected = objects.begin(); affected != objects.end(); ++affected) - toApply[*affected].push_back(*effectIt); - } - - // Now apply the appropriate effects to each actor in range - for (std::map >::iterator apply = toApply.begin(); apply != toApply.end(); ++apply) - { - MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); - - // Vanilla-compatible behaviour of never applying the spell to the caster - // (could be changed by mods later) - if (apply->first == caster) - continue; - - if (caster.isEmpty()) - caster = apply->first; - - MWMechanics::CastSpell cast(caster, apply->first); - cast.mId = it->second.mId; - cast.mSourceName = it->second.mSourceName; - cast.mStack = it->second.mStack; - ESM::EffectList effects; - effects.mList = apply->second; - cast.inflict(apply->first, caster, effects, ESM::RT_Target); - } + MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle); + explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName); deleteObject(ptr); mProjectiles.erase(it++); @@ -2707,4 +2654,67 @@ namespace MWWorld mRendering->spawnEffect(model, texture, worldPosition); } + + void World::explodeSpell(const Vector3 &origin, const MWWorld::Ptr& object, const ESM::EffectList &effects, const Ptr &caster, + const std::string& id, const std::string& sourceName) + { + std::map > toApply; + for (std::vector::const_iterator effectIt = effects.mList.begin(); + effectIt != effects.mList.end(); ++effectIt) + { + const ESM::MagicEffect* effect = getStore().get().find(effectIt->mEffectID); + + if (effectIt->mArea <= 0) + continue; // Not an area effect + + // Spawn the explosion orb effect + const ESM::Static* areaStatic; + if (!effect->mCasting.empty()) + areaStatic = getStore().get().find (effect->mArea); + else + areaStatic = getStore().get().find ("VFX_DefaultArea"); + + mRendering->spawnEffect("meshes\\" + areaStatic->mModel, "", origin, effectIt->mArea); + + // 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(object, effect->mAreaSound, 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + else + sndMgr->playSound3D(object, schools[effect->mData.mSchool]+" area", 1.0f, 1.0f, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_NoTrack); + + // Get the actors in range of the effect + std::vector objects; + MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( + origin, feetToGameUnits(effectIt->mArea), objects); + + for (std::vector::iterator affected = objects.begin(); affected != objects.end(); ++affected) + toApply[*affected].push_back(*effectIt); + } + + // Now apply the appropriate effects to each actor in range + for (std::map >::iterator apply = toApply.begin(); apply != toApply.end(); ++apply) + { + MWWorld::Ptr source = caster; + // Vanilla-compatible behaviour of never applying the spell to the caster + // (could be changed by mods later) + if (apply->first == caster) + continue; + + if (source.isEmpty()) + source = apply->first; + + MWMechanics::CastSpell cast(source, apply->first); + cast.mHitPosition = origin; + cast.mId = id; + cast.mSourceName = sourceName; + cast.mStack = false; + ESM::EffectList effects; + effects.mList = apply->second; + cast.inflict(apply->first, caster, effects, ESM::RT_Target, false, true); + } + } } diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 38766e74f..8b1bd9538 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -552,6 +552,9 @@ namespace MWWorld /// Spawn a blood effect for \a ptr at \a worldPosition virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition); + + virtual void explodeSpell (const Ogre::Vector3& origin, const MWWorld::Ptr& object, const ESM::EffectList& effects, + const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); }; }