Commit 851a7d50 authored by scrawl's avatar scrawl

Feature #957: Handle area effects for "on touch" range

parent c8a9e9f7
...@@ -466,6 +466,9 @@ namespace MWBase ...@@ -466,6 +466,9 @@ namespace MWBase
/// Spawn a blood effect for \a ptr at \a worldPosition /// Spawn a blood effect for \a ptr at \a worldPosition
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& worldPosition) = 0; 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;
}; };
} }
......
...@@ -316,23 +316,9 @@ namespace MWClass ...@@ -316,23 +316,9 @@ namespace MWClass
enchantmentName); enchantmentName);
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
{ {
// Check if we have enough charges MWMechanics::CastSpell cast(ptr, victim);
const float enchantCost = enchantment->mData.mCost; cast.mHitPosition = hitPosition;
int eSkill = getSkill(ptr, ESM::Skill::Enchant); cast.cast(weapon);
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);
}
} }
} }
} }
......
...@@ -576,28 +576,12 @@ namespace MWClass ...@@ -576,28 +576,12 @@ namespace MWClass
enchantmentName); enchantmentName);
if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes) if (enchantment->mData.mType == ESM::Enchantment::WhenStrikes)
{ {
// Check if we have enough charges MWMechanics::CastSpell cast(ptr, victim);
const float enchantCost = enchantment->mData.mCost; cast.mHitPosition = hitPosition;
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); bool success = cast.cast(weapon);
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)
{
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") if (ptr.getRefData().getHandle() == "player" && success)
skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3); skillUsageSucceeded (ptr, ESM::Skill::Enchant, 3);
}
} }
} }
......
...@@ -181,11 +181,12 @@ namespace MWMechanics ...@@ -181,11 +181,12 @@ namespace MWMechanics
: mCaster(caster) : mCaster(caster)
, mTarget(target) , mTarget(target)
, mStack(false) , mStack(false)
, mHitPosition(0,0,0)
{ {
} }
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster, 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 // If none of the effects need to apply, we can early-out
bool found = false; bool found = false;
...@@ -375,11 +376,12 @@ namespace MWMechanics ...@@ -375,11 +376,12 @@ namespace MWMechanics
if (anim) if (anim)
anim->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, ""); 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()) if (reflectedEffects.mList.size())
inflict(caster, target, reflectedEffects, range, true); inflict(caster, target, reflectedEffects, range, true);
...@@ -521,12 +523,11 @@ namespace MWMechanics ...@@ -521,12 +523,11 @@ namespace MWMechanics
mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce); 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; const float enchantCost = enchantment->mData.mCost;
MWMechanics::NpcStats &stats = MWWorld::Class::get(mCaster).getNpcStats(mCaster); int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant);
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10));
if (item.getCellRef().mEnchantmentCharge == -1) if (item.getCellRef().mEnchantmentCharge == -1)
...@@ -539,10 +540,15 @@ namespace MWMechanics ...@@ -539,10 +540,15 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}"); MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
return false; return false;
} }
// Reduce charge // Reduce charge
item.getCellRef().mEnchantmentCharge -= castCost; 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) if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
item.getContainerStore()->remove(item, 1, mCaster); item.getContainerStore()->remove(item, 1, mCaster);
else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes) else if (enchantment->mData.mType != ESM::Enchantment::WhenStrikes)
...@@ -551,9 +557,6 @@ namespace MWMechanics ...@@ -551,9 +557,6 @@ namespace MWMechanics
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge 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); inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
if (!mTarget.isEmpty()) if (!mTarget.isEmpty())
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
#include <OgreVector3.h>
namespace MWMechanics namespace MWMechanics
{ {
class EffectKey; class EffectKey;
...@@ -36,6 +38,7 @@ namespace MWMechanics ...@@ -36,6 +38,7 @@ namespace MWMechanics
bool mStack; bool mStack;
std::string mId; // ID of spell, potion, item etc std::string mId; // ID of spell, potion, item etc
std::string mSourceName; // Display name for spell, potion, etc std::string mSourceName; // Display name for spell, potion, etc
Ogre::Vector3 mHitPosition; // Used for spawning area orb
public: public:
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target); CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
...@@ -49,7 +52,7 @@ namespace MWMechanics ...@@ -49,7 +52,7 @@ namespace MWMechanics
bool cast (const std::string& id); bool cast (const std::string& id);
void inflict (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, 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); void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude);
}; };
......
...@@ -745,6 +745,7 @@ namespace MWScript ...@@ -745,6 +745,7 @@ namespace MWScript
MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false); MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getPtr (targetId, false);
MWMechanics::CastSpell cast(ptr, target); MWMechanics::CastSpell cast(ptr, target);
cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
cast.cast(spell); cast.cast(spell);
} }
}; };
...@@ -761,6 +762,7 @@ namespace MWScript ...@@ -761,6 +762,7 @@ namespace MWScript
runtime.pop(); runtime.pop();
MWMechanics::CastSpell cast(ptr, ptr); MWMechanics::CastSpell cast(ptr, ptr);
cast.mHitPosition = Ogre::Vector3(ptr.getRefData().getPosition().pos);
cast.cast(spell); cast.cast(spell);
} }
}; };
......
...@@ -8,6 +8,7 @@ namespace MWWorld ...@@ -8,6 +8,7 @@ namespace MWWorld
void ActionTrap::executeImp(const Ptr &actor) void ActionTrap::executeImp(const Ptr &actor)
{ {
MWMechanics::CastSpell cast(mTrapSource, actor); MWMechanics::CastSpell cast(mTrapSource, actor);
cast.mHitPosition = Ogre::Vector3(actor.getRefData().getPosition().pos);
cast.cast(mSpellId); cast.cast(mSpellId);
mTrapSource.getCellRef().mTrap = ""; mTrapSource.getCellRef().mTrap = "";
......
...@@ -2106,6 +2106,8 @@ namespace MWWorld ...@@ -2106,6 +2106,8 @@ namespace MWWorld
std::string selectedSpell = stats.getSpells().getSelectedSpell(); std::string selectedSpell = stats.getSpells().getSelectedSpell();
MWMechanics::CastSpell cast(actor, target); MWMechanics::CastSpell cast(actor, target);
if (!target.isEmpty())
cast.mHitPosition = Ogre::Vector3(target.getRefData().getPosition().pos);
if (!selectedSpell.empty()) if (!selectedSpell.empty())
{ {
...@@ -2240,10 +2242,11 @@ namespace MWWorld ...@@ -2240,10 +2242,11 @@ namespace MWWorld
else else
{ {
MWMechanics::CastSpell cast(caster, obstacle); MWMechanics::CastSpell cast(caster, obstacle);
cast.mHitPosition = pos;
cast.mId = it->second.mId; cast.mId = it->second.mId;
cast.mSourceName = it->second.mSourceName; cast.mSourceName = it->second.mSourceName;
cast.mStack = it->second.mStack; 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; explode = true;
...@@ -2251,64 +2254,8 @@ namespace MWWorld ...@@ -2251,64 +2254,8 @@ namespace MWWorld
if (explode) if (explode)
{ {
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply; MWWorld::Ptr caster = searchPtrViaHandle(it->second.mActorHandle);
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = it->second.mEffects.mList.begin(); explodeSpell(Ogre::Vector3(ptr.getRefData().getPosition().pos), ptr, it->second.mEffects, caster, it->second.mId, it->second.mSourceName);
effectIt != it->second.mEffects.mList.end(); ++effectIt)
{
const ESM::MagicEffect* effect = getStore().get<ESM::MagicEffect>().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<ESM::Static>().find (effect->mArea);
else
areaStatic = getStore().get<ESM::Static>().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<MWWorld::Ptr> objects;
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
Ogre::Vector3(ptr.getRefData().getPosition().pos), feetToGameUnits(effectIt->mArea), objects);
for (std::vector<MWWorld::Ptr>::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<MWWorld::Ptr, std::vector<ESM::ENAMstruct> >::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);
}
deleteObject(ptr); deleteObject(ptr);
mProjectiles.erase(it++); mProjectiles.erase(it++);
...@@ -2707,4 +2654,67 @@ namespace MWWorld ...@@ -2707,4 +2654,67 @@ namespace MWWorld
mRendering->spawnEffect(model, texture, worldPosition); 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<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = effects.mList.begin();
effectIt != effects.mList.end(); ++effectIt)
{
const ESM::MagicEffect* effect = getStore().get<ESM::MagicEffect>().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<ESM::Static>().find (effect->mArea);
else
areaStatic = getStore().get<ESM::Static>().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<MWWorld::Ptr> objects;
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
origin, feetToGameUnits(effectIt->mArea), objects);
for (std::vector<MWWorld::Ptr>::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<MWWorld::Ptr, std::vector<ESM::ENAMstruct> >::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);
}
}
} }
...@@ -552,6 +552,9 @@ namespace MWWorld ...@@ -552,6 +552,9 @@ namespace MWWorld
/// Spawn a blood effect for \a ptr at \a worldPosition /// Spawn a blood effect for \a ptr at \a worldPosition
virtual void spawnBloodEffect (const MWWorld::Ptr& ptr, const Ogre::Vector3& 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);
}; };
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment