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
/// 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;
};
}
......
......@@ -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);
}
}
}
......
......@@ -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));
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);
MWMechanics::CastSpell cast(ptr, victim);
cast.mHitPosition = hitPosition;
bool success = 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);
}
}
......
......@@ -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())
......
......@@ -3,6 +3,8 @@
#include "../mwworld/ptr.hpp"
#include <OgreVector3.h>
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);
};
......
......@@ -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);
}
};
......
......@@ -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 = "";
......
......@@ -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<MWWorld::Ptr, std::vector<ESM::ENAMstruct> > toApply;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = it->second.mEffects.mList.begin();
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);
}
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<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
/// 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);
};
}
......
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