forked from teamnwah/openmw-tes3coop
Lots of cleanup. Implemented Absorb and Resist. Implemented several instant effects. Added hand VFX.
This commit is contained in:
parent
7fd5f1df83
commit
0dc2e829dd
24 changed files with 801 additions and 587 deletions
|
@ -114,6 +114,11 @@ namespace MWBase
|
||||||
/// references that are currently not in the scene should be ignored.
|
/// references that are currently not in the scene should be ignored.
|
||||||
|
|
||||||
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0;
|
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) = 0;
|
||||||
|
|
||||||
|
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
|
||||||
|
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||||
|
virtual void updateMagicEffects (const MWWorld::Ptr& ptr) = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/movement.hpp"
|
#include "../mwmechanics/movement.hpp"
|
||||||
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
|
|
||||||
#include "../mwworld/ptr.hpp"
|
#include "../mwworld/ptr.hpp"
|
||||||
#include "../mwworld/actiontalk.hpp"
|
#include "../mwworld/actiontalk.hpp"
|
||||||
|
@ -467,12 +468,9 @@ namespace MWClass
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
weapon.getCellRef().mEnchantmentCharge -= castCost;
|
weapon.getCellRef().mEnchantmentCharge -= castCost;
|
||||||
// Touch
|
|
||||||
othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ptr, ESM::RT_Touch, weapon.getClass().getName(weapon));
|
MWMechanics::CastSpell cast(ptr, victim);
|
||||||
// Self
|
cast.cast(weapon);
|
||||||
getCreatureStats(ptr).getActiveSpells().addSpell(enchantmentName, ptr, ptr, ESM::RT_Self, weapon.getClass().getName(weapon));
|
|
||||||
// Target
|
|
||||||
MWBase::Environment::get().getWorld()->launchProjectile(enchantmentName, enchantment->mEffects, ptr, weapon.getClass().getName(weapon));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -932,11 +930,8 @@ namespace MWClass
|
||||||
bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id,
|
bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id,
|
||||||
const MWWorld::Ptr& actor) const
|
const MWWorld::Ptr& actor) const
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
|
MWMechanics::CastSpell cast(ptr, ptr);
|
||||||
|
return cast.cast(id);
|
||||||
/// \todo consider instant effects
|
|
||||||
|
|
||||||
return stats.getActiveSpells().addSpell (id, actor, actor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const
|
void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const
|
||||||
|
|
|
@ -21,17 +21,17 @@
|
||||||
namespace MWGui
|
namespace MWGui
|
||||||
{
|
{
|
||||||
|
|
||||||
void EffectSourceVisitor::visit (const ESM::ENAMstruct& enam,
|
void EffectSourceVisitor::visit (MWMechanics::EffectKey key,
|
||||||
const std::string& sourceName, float magnitude, float remainingTime)
|
const std::string& sourceName, float magnitude, float remainingTime)
|
||||||
{
|
{
|
||||||
MagicEffectInfo newEffectSource;
|
MagicEffectInfo newEffectSource;
|
||||||
newEffectSource.mKey = MWMechanics::EffectKey(enam);
|
newEffectSource.mKey = key;
|
||||||
newEffectSource.mMagnitude = magnitude;
|
newEffectSource.mMagnitude = magnitude;
|
||||||
newEffectSource.mPermanent = mIsPermanent;
|
newEffectSource.mPermanent = mIsPermanent;
|
||||||
newEffectSource.mRemainingTime = remainingTime;
|
newEffectSource.mRemainingTime = remainingTime;
|
||||||
newEffectSource.mSource = sourceName;
|
newEffectSource.mSource = sourceName;
|
||||||
|
|
||||||
mEffectSources[enam.mEffectID].push_back(newEffectSource);
|
mEffectSources[key.mId].push_back(newEffectSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace MWGui
|
||||||
|
|
||||||
std::map <int, std::vector<MagicEffectInfo> > mEffectSources;
|
std::map <int, std::vector<MagicEffectInfo> > mEffectSources;
|
||||||
|
|
||||||
virtual void visit (const ESM::ENAMstruct& enam,
|
virtual void visit (MWMechanics::EffectKey key,
|
||||||
const std::string& sourceName, float magnitude, float remainingTime = -1);
|
const std::string& sourceName, float magnitude, float remainingTime = -1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,7 @@
|
||||||
|
|
||||||
#include "activespells.hpp"
|
#include "activespells.hpp"
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
|
||||||
|
|
||||||
#include <components/esm/loadalch.hpp>
|
|
||||||
#include <components/esm/loadspel.hpp>
|
|
||||||
#include <components/esm/loadingr.hpp>
|
|
||||||
#include <components/esm/loadmgef.hpp>
|
|
||||||
#include <components/esm/loadskil.hpp>
|
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/soundmanager.hpp"
|
|
||||||
#include "../mwbase/windowmanager.hpp"
|
|
||||||
|
|
||||||
#include "../mwrender/animation.hpp"
|
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
|
||||||
|
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
|
||||||
|
|
||||||
#include "creaturestats.hpp"
|
|
||||||
#include "npcstats.hpp"
|
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
|
@ -35,6 +11,7 @@ namespace MWMechanics
|
||||||
|
|
||||||
MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
|
MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||||
|
|
||||||
|
// Erase no longer active spells
|
||||||
if (mLastUpdate!=now)
|
if (mLastUpdate!=now)
|
||||||
{
|
{
|
||||||
TContainer::iterator iter (mSpells.begin());
|
TContainer::iterator iter (mSpells.begin());
|
||||||
|
@ -42,7 +19,6 @@ namespace MWMechanics
|
||||||
if (!timeToExpire (iter))
|
if (!timeToExpire (iter))
|
||||||
{
|
{
|
||||||
mSpells.erase (iter++);
|
mSpells.erase (iter++);
|
||||||
//onSpellExpired
|
|
||||||
rebuild = true;
|
rebuild = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -69,277 +45,28 @@ namespace MWMechanics
|
||||||
|
|
||||||
for (TIterator iter (begin()); iter!=end(); ++iter)
|
for (TIterator iter (begin()); iter!=end(); ++iter)
|
||||||
{
|
{
|
||||||
std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iter->first);
|
|
||||||
|
|
||||||
const MWWorld::TimeStamp& start = iter->second.mTimeStamp;
|
const MWWorld::TimeStamp& start = iter->second.mTimeStamp;
|
||||||
|
|
||||||
int i = 0;
|
const std::vector<Effect>& effects = iter->second.mEffects;
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIter (effects.first.mList.begin());
|
|
||||||
effectIter!=effects.first.mList.end(); ++effectIter, ++i)
|
for (std::vector<Effect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
|
||||||
{
|
{
|
||||||
float random = iter->second.mRandom[i];
|
int duration = effectIt->mDuration;
|
||||||
if (effectIter->mRange != iter->second.mRange)
|
MWWorld::TimeStamp end = start;
|
||||||
continue;
|
end += static_cast<double> (duration)*
|
||||||
|
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
|
||||||
|
|
||||||
if (effectIter->mDuration)
|
if (end>now)
|
||||||
{
|
mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude));
|
||||||
int duration = effectIter->mDuration;
|
|
||||||
|
|
||||||
if (effects.second.first)
|
|
||||||
duration *= random;
|
|
||||||
|
|
||||||
MWWorld::TimeStamp end = start;
|
|
||||||
end += static_cast<double> (duration)*
|
|
||||||
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
|
|
||||||
|
|
||||||
if (end>now)
|
|
||||||
{
|
|
||||||
EffectParam param;
|
|
||||||
|
|
||||||
if (effects.second.first)
|
|
||||||
{
|
|
||||||
const ESM::MagicEffect *magicEffect =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
|
||||||
effectIter->mEffectID);
|
|
||||||
|
|
||||||
if (effectIter->mDuration==0)
|
|
||||||
{
|
|
||||||
param.mMagnitude =
|
|
||||||
static_cast<int> (random / (0.1 * magicEffect->mData.mBaseCost));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
param.mMagnitude =
|
|
||||||
static_cast<int> (0.05*random / (0.1 * magicEffect->mData.mBaseCost));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
param.mMagnitude = static_cast<int> (
|
|
||||||
(effectIter->mMagnMax-effectIter->mMagnMin)*random + effectIter->mMagnMin);
|
|
||||||
param.mMagnitude *= iter->second.mMultiplier[i];
|
|
||||||
|
|
||||||
if (param.mMagnitude)
|
|
||||||
mEffects.add (*effectIter, param);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<ESM::EffectList, std::pair<bool, bool> > ActiveSpells::getEffectList (const std::string& id) const
|
|
||||||
{
|
|
||||||
if (const ESM::Enchantment* enchantment =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().search (id))
|
|
||||||
return std::make_pair (enchantment->mEffects, std::make_pair(false, false));
|
|
||||||
|
|
||||||
if (const ESM::Spell *spell =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
|
||||||
return std::make_pair (spell->mEffects, std::make_pair(false, false));
|
|
||||||
|
|
||||||
if (const ESM::Potion *potion =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
|
||||||
return std::make_pair (potion->mEffects, std::make_pair(false, true));
|
|
||||||
|
|
||||||
if (const ESM::Ingredient *ingredient =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
|
||||||
{
|
|
||||||
const ESM::MagicEffect *magicEffect =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
|
||||||
ingredient->mData.mEffectID[0]);
|
|
||||||
|
|
||||||
ESM::ENAMstruct effect;
|
|
||||||
effect.mEffectID = ingredient->mData.mEffectID[0];
|
|
||||||
effect.mSkill = ingredient->mData.mSkills[0];
|
|
||||||
effect.mAttribute = ingredient->mData.mAttributes[0];
|
|
||||||
effect.mRange = 0;
|
|
||||||
effect.mArea = 0;
|
|
||||||
effect.mDuration = magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration ? 0 : 1;
|
|
||||||
effect.mMagnMin = 1;
|
|
||||||
effect.mMagnMax = 1;
|
|
||||||
|
|
||||||
std::pair<ESM::EffectList, std::pair<bool, bool> > result;
|
|
||||||
result.second.second = true;
|
|
||||||
result.second.first = true;
|
|
||||||
|
|
||||||
result.first.mList.push_back (effect);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw std::runtime_error ("ID " + id + " can not produce lasting effects");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ActiveSpells::getSpellDisplayName (const std::string& id) const
|
|
||||||
{
|
|
||||||
if (const ESM::Spell *spell =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
|
||||||
return spell->mName;
|
|
||||||
|
|
||||||
if (const ESM::Potion *potion =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
|
||||||
return potion->mName;
|
|
||||||
|
|
||||||
if (const ESM::Ingredient *ingredient =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
|
||||||
return ingredient->mName;
|
|
||||||
|
|
||||||
throw std::runtime_error ("ID " + id + " has no display name");
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveSpells::ActiveSpells()
|
ActiveSpells::ActiveSpells()
|
||||||
: mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
|
: mSpellsChanged (false)
|
||||||
|
, mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
|
||||||
{}
|
{}
|
||||||
|
|
||||||
bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range, const std::string& name, int effectIndex)
|
|
||||||
{
|
|
||||||
const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor);
|
|
||||||
|
|
||||||
std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (id);
|
|
||||||
bool stacks = effects.second.second;
|
|
||||||
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
|
|
||||||
iter!=effects.first.mList.end(); ++iter)
|
|
||||||
{
|
|
||||||
if (iter->mRange != range)
|
|
||||||
continue;
|
|
||||||
if (iter->mDuration)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If none of the effects need to apply, no need to add the spell
|
|
||||||
if (!found)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
TContainer::iterator iter = mSpells.find (id);
|
|
||||||
|
|
||||||
ActiveSpellParams params;
|
|
||||||
for (unsigned int i=0; i<effects.first.mList.size(); ++i)
|
|
||||||
{
|
|
||||||
float random = static_cast<float> (std::rand()) / RAND_MAX;
|
|
||||||
if (effects.second.first)
|
|
||||||
{
|
|
||||||
// ingredient -> special treatment required.
|
|
||||||
const NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor);
|
|
||||||
|
|
||||||
float x =
|
|
||||||
(npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
|
|
||||||
0.2 * creatureStats.getAttribute (1).getModified()
|
|
||||||
+ 0.1 * creatureStats.getAttribute (7).getModified())
|
|
||||||
* creatureStats.getFatigueTerm();
|
|
||||||
random *= 100;
|
|
||||||
random = random / std::min (x, 100.0f);
|
|
||||||
random *= 0.25 * x;
|
|
||||||
}
|
|
||||||
|
|
||||||
params.mRandom.push_back(random);
|
|
||||||
}
|
|
||||||
params.mRange = range;
|
|
||||||
params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp();
|
|
||||||
params.mName = name;
|
|
||||||
params.mMultiplier.resize(effects.first.mList.size(), 1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
for (int i=0; i<effects.first.mList.size(); ++i)
|
|
||||||
{
|
|
||||||
if (iter->mRange != range)
|
|
||||||
{
|
|
||||||
params.mDisabled.push_back(true);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool disabled = false;
|
|
||||||
|
|
||||||
int reflect = creatureStats.getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude;
|
|
||||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
|
||||||
if (roll < reflect)
|
|
||||||
disabled = true;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
bool first=true;
|
|
||||||
int i = 0;
|
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.first.mList.begin());
|
|
||||||
effectIt!=effects.first.mList.end(); ++effectIt, ++i)
|
|
||||||
{
|
|
||||||
if (effectIt->mRange != range)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const ESM::MagicEffect *magicEffect =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
|
||||||
effectIt->mEffectID);
|
|
||||||
|
|
||||||
if (caster.getRefData().getHandle() == "player" && actor != caster
|
|
||||||
&& magicEffect->mData.mFlags & ESM::MagicEffect::Harmful)
|
|
||||||
MWBase::Environment::get().getWindowManager()->setEnemy(actor);
|
|
||||||
|
|
||||||
// Try resisting effect in case its harmful
|
|
||||||
const ESM::Spell *spell =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id);
|
|
||||||
params.mMultiplier[i] = MWMechanics::getEffectMultiplier(effectIt->mEffectID, actor, caster, spell);
|
|
||||||
if (params.mMultiplier[i] == 0)
|
|
||||||
{
|
|
||||||
if (actor.getRefData().getHandle() == "player")
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
|
||||||
else
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If fully resisted, don't play sounds or particles
|
|
||||||
if (params.mMultiplier[i] == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO: For Area effects, launch a growing particle effect that applies the effect to more actors as it hits them. Best managed in World.
|
|
||||||
|
|
||||||
// Only the sound of the first effect plays
|
|
||||||
if (first)
|
|
||||||
{
|
|
||||||
static const std::string schools[] = {
|
|
||||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
|
||||||
};
|
|
||||||
|
|
||||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
|
||||||
if(!magicEffect->mHitSound.empty())
|
|
||||||
sndMgr->playSound3D(actor, magicEffect->mHitSound, 1.0f, 1.0f);
|
|
||||||
else
|
|
||||||
sndMgr->playSound3D(actor, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!magicEffect->mHit.empty())
|
|
||||||
{
|
|
||||||
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
|
|
||||||
bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
|
|
||||||
MWBase::Environment::get().getWorld()->getAnimation(actor)->addEffect("meshes\\" + castStatic->mModel, magicEffect->mIndex, loop, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (iter==mSpells.end() || stacks)
|
|
||||||
mSpells.insert (std::make_pair (id, params));
|
|
||||||
else
|
|
||||||
iter->second = params;
|
|
||||||
|
|
||||||
mSpellsChanged = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ActiveSpells::removeSpell (const std::string& id)
|
|
||||||
{
|
|
||||||
TContainer::iterator iter = mSpells.find (id);
|
|
||||||
|
|
||||||
if (iter!=mSpells.end())
|
|
||||||
{
|
|
||||||
mSpells.erase (iter);
|
|
||||||
mSpellsChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const MagicEffects& ActiveSpells::getMagicEffects() const
|
const MagicEffects& ActiveSpells::getMagicEffects() const
|
||||||
{
|
{
|
||||||
update();
|
update();
|
||||||
|
@ -348,37 +75,31 @@ namespace MWMechanics
|
||||||
|
|
||||||
ActiveSpells::TIterator ActiveSpells::begin() const
|
ActiveSpells::TIterator ActiveSpells::begin() const
|
||||||
{
|
{
|
||||||
update();
|
|
||||||
return mSpells.begin();
|
return mSpells.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveSpells::TIterator ActiveSpells::end() const
|
ActiveSpells::TIterator ActiveSpells::end() const
|
||||||
{
|
{
|
||||||
update();
|
|
||||||
return mSpells.end();
|
return mSpells.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
double ActiveSpells::timeToExpire (const TIterator& iterator) const
|
double ActiveSpells::timeToExpire (const TIterator& iterator) const
|
||||||
{
|
{
|
||||||
std::pair<ESM::EffectList, std::pair<bool, bool> > effects = getEffectList (iterator->first);
|
const std::vector<Effect>& effects = iterator->second.mEffects;
|
||||||
|
|
||||||
int duration = 0;
|
int duration = 0;
|
||||||
|
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
|
for (std::vector<Effect>::const_iterator iter (effects.begin());
|
||||||
iter!=effects.first.mList.end(); ++iter)
|
iter!=effects.end(); ++iter)
|
||||||
{
|
{
|
||||||
if (iter->mDuration > duration)
|
if (iter->mDuration > duration)
|
||||||
duration = iter->mDuration;
|
duration = iter->mDuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale duration by magnitude if needed
|
|
||||||
if (effects.second.first && iterator->second.mRandom.size())
|
|
||||||
duration *= iterator->second.mRandom.front();
|
|
||||||
|
|
||||||
double scaledDuration = duration *
|
double scaledDuration = duration *
|
||||||
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
|
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
|
||||||
|
|
||||||
double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp()-iterator->second.mTimeStamp;
|
double usedUp = MWBase::Environment::get().getWorld()->getTimeStamp() - iterator->second.mTimeStamp;
|
||||||
|
|
||||||
if (usedUp>=scaledDuration)
|
if (usedUp>=scaledDuration)
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -405,48 +126,67 @@ namespace MWMechanics
|
||||||
return mSpells;
|
return mSpells;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ActiveSpells::addSpell(const std::string &id, bool stack, std::vector<Effect> effects, const std::string &displayName)
|
||||||
|
{
|
||||||
|
bool exists = false;
|
||||||
|
for (TContainer::const_iterator it = begin(); it != end(); ++it)
|
||||||
|
{
|
||||||
|
if (id == it->first)
|
||||||
|
exists = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveSpellParams params;
|
||||||
|
params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp();
|
||||||
|
params.mEffects = effects;
|
||||||
|
params.mDisplayName = displayName;
|
||||||
|
|
||||||
|
if (!exists || stack)
|
||||||
|
mSpells.insert (std::make_pair(id, params));
|
||||||
|
else
|
||||||
|
mSpells.find(id)->second = params;
|
||||||
|
|
||||||
|
mSpellsChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
|
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
|
||||||
{
|
{
|
||||||
for (TContainer::const_iterator it = begin(); it != end(); ++it)
|
for (TContainer::const_iterator it = begin(); it != end(); ++it)
|
||||||
{
|
{
|
||||||
const ESM::EffectList& list = getEffectList(it->first).first;
|
|
||||||
|
|
||||||
float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
|
float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
|
||||||
|
|
||||||
int i=0;
|
for (std::vector<Effect>::const_iterator effectIt = it->second.mEffects.begin();
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
|
effectIt != it->second.mEffects.end(); ++effectIt)
|
||||||
effectIt != list.mList.end(); ++effectIt, ++i)
|
|
||||||
{
|
{
|
||||||
if (effectIt->mRange != it->second.mRange)
|
std::string name = it->second.mDisplayName;
|
||||||
continue;
|
|
||||||
|
|
||||||
std::string name;
|
|
||||||
if (it->second.mName.empty())
|
|
||||||
name = getSpellDisplayName(it->first);
|
|
||||||
else
|
|
||||||
name = it->second.mName;
|
|
||||||
|
|
||||||
float remainingTime = effectIt->mDuration +
|
float remainingTime = effectIt->mDuration +
|
||||||
(it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
|
(it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
|
||||||
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second.mRandom[i];
|
float magnitude = effectIt->mMagnitude;
|
||||||
|
|
||||||
// hack for ingredients
|
|
||||||
if (MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (it->first))
|
|
||||||
{
|
|
||||||
const ESM::MagicEffect *magicEffect =
|
|
||||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
|
||||||
effectIt->mEffectID);
|
|
||||||
|
|
||||||
remainingTime = effectIt->mDuration * it->second.mRandom[i] +
|
|
||||||
(it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale;
|
|
||||||
|
|
||||||
magnitude = static_cast<int> (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost));
|
|
||||||
}
|
|
||||||
|
|
||||||
magnitude *= it->second.mMultiplier[i];
|
|
||||||
if (magnitude)
|
if (magnitude)
|
||||||
visitor.visit(*effectIt, name, magnitude, remainingTime);
|
visitor.visit(effectIt->mKey, name, magnitude, remainingTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ActiveSpells::purgeAll()
|
||||||
|
{
|
||||||
|
mSpells.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActiveSpells::purgeEffect(short effectId)
|
||||||
|
{
|
||||||
|
for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it)
|
||||||
|
{
|
||||||
|
for (std::vector<Effect>::iterator effectIt = it->second.mEffects.begin();
|
||||||
|
effectIt != it->second.mEffects.end();)
|
||||||
|
{
|
||||||
|
if (effectIt->mKey.mId == effectId)
|
||||||
|
effectIt = it->second.mEffects.erase(effectIt);
|
||||||
|
else
|
||||||
|
effectIt++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,39 +11,8 @@
|
||||||
|
|
||||||
#include <components/esm/defs.hpp>
|
#include <components/esm/defs.hpp>
|
||||||
|
|
||||||
namespace ESM
|
|
||||||
{
|
|
||||||
struct Spell;
|
|
||||||
struct EffectList;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWWorld
|
|
||||||
{
|
|
||||||
class Ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
struct ActiveSpellParams
|
|
||||||
{
|
|
||||||
// Only apply effects of this range type
|
|
||||||
ESM::RangeType mRange;
|
|
||||||
|
|
||||||
// When the spell was added
|
|
||||||
MWWorld::TimeStamp mTimeStamp;
|
|
||||||
|
|
||||||
// Random factor for each effect
|
|
||||||
std::vector<float> mRandom;
|
|
||||||
|
|
||||||
// Effect magnitude multiplier. Use 0 to completely disable the effect
|
|
||||||
// (if it was resisted, reflected or absorbed). Use (0,1) for partially resisted.
|
|
||||||
std::vector<float> mMultiplier;
|
|
||||||
|
|
||||||
// Display name, we need this for enchantments, which don't have a name - so you need to supply the
|
|
||||||
// name of the item with the enchantment to addSpell
|
|
||||||
std::string mName;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// \brief Lasting spell effects
|
/// \brief Lasting spell effects
|
||||||
///
|
///
|
||||||
/// \note The name of this class is slightly misleading, since it also handels lasting potion
|
/// \note The name of this class is slightly misleading, since it also handels lasting potion
|
||||||
|
@ -52,6 +21,23 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
// Parameters of an effect concerning lasting effects.
|
||||||
|
// Note we are not using ENAMstruct since the magnitude may be modified by magic resistance, etc.
|
||||||
|
// It could also be a negative magnitude, in case of inversing an effect, e.g. Absorb spell causes damage on target, but heals the caster.
|
||||||
|
struct Effect
|
||||||
|
{
|
||||||
|
float mMagnitude;
|
||||||
|
EffectKey mKey;
|
||||||
|
float mDuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ActiveSpellParams
|
||||||
|
{
|
||||||
|
std::vector<Effect> mEffects;
|
||||||
|
MWWorld::TimeStamp mTimeStamp;
|
||||||
|
std::string mDisplayName;
|
||||||
|
};
|
||||||
|
|
||||||
typedef std::multimap<std::string, ActiveSpellParams > TContainer;
|
typedef std::multimap<std::string, ActiveSpellParams > TContainer;
|
||||||
typedef TContainer::const_iterator TIterator;
|
typedef TContainer::const_iterator TIterator;
|
||||||
|
|
||||||
|
@ -66,9 +52,6 @@ namespace MWMechanics
|
||||||
|
|
||||||
void rebuildEffects() const;
|
void rebuildEffects() const;
|
||||||
|
|
||||||
std::pair<ESM::EffectList, std::pair<bool, bool> > getEffectList (const std::string& id) const;
|
|
||||||
///< @return (EffectList, (isIngredient, stacks))
|
|
||||||
|
|
||||||
double timeToExpire (const TIterator& iterator) const;
|
double timeToExpire (const TIterator& iterator) const;
|
||||||
///< Returns time (in in-game hours) until the spell pointed to by \a iterator
|
///< Returns time (in in-game hours) until the spell pointed to by \a iterator
|
||||||
/// expires.
|
/// expires.
|
||||||
|
@ -79,25 +62,25 @@ namespace MWMechanics
|
||||||
|
|
||||||
TIterator end() const;
|
TIterator end() const;
|
||||||
|
|
||||||
std::string getSpellDisplayName (const std::string& id) const;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ActiveSpells();
|
ActiveSpells();
|
||||||
|
|
||||||
bool addSpell (const std::string& id, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, ESM::RangeType range = ESM::RT_Self, const std::string& name = "", int effectIndex = -1);
|
/// Add lasting effects
|
||||||
///< Overwrites an existing spell with the same ID. If the spell does not have any
|
|
||||||
/// non-instant effects, it is ignored.
|
|
||||||
/// @param id
|
|
||||||
/// @param actor actor to add the spell to
|
|
||||||
/// @param caster actor who casted the spell
|
|
||||||
/// @param range Only effects with range type \a range will be applied
|
|
||||||
/// @param name Display name for enchantments, since they don't have a name in their record
|
|
||||||
/// @param effectIndex Only apply one specific effect - useful for reflecting spells, since each effect is reflected individually
|
|
||||||
///
|
///
|
||||||
/// \return Has the spell been added?
|
/// \brief addSpell
|
||||||
|
/// \param id ID for stacking purposes.
|
||||||
|
/// \param stack If false, the spell is not added if one with the same ID exists already.
|
||||||
|
/// \param effects
|
||||||
|
/// \param displayName Name for display in magic menu.
|
||||||
|
///
|
||||||
|
void addSpell (const std::string& id, bool stack, std::vector<Effect> effects, const std::string& displayName);
|
||||||
|
|
||||||
void removeSpell (const std::string& id);
|
/// Remove all active effects with this id
|
||||||
|
void purgeEffect (short effectId);
|
||||||
|
|
||||||
|
/// Remove all active effects
|
||||||
|
void purgeAll ();
|
||||||
|
|
||||||
bool isSpellActive (std::string id) const;
|
bool isSpellActive (std::string id) const;
|
||||||
///< case insensitive
|
///< case insensitive
|
||||||
|
|
|
@ -53,6 +53,10 @@ namespace MWMechanics
|
||||||
|
|
||||||
Actors();
|
Actors();
|
||||||
|
|
||||||
|
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
|
||||||
|
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||||
|
void updateMagicEffects (const MWWorld::Ptr& ptr) { adjustMagicEffects(ptr); }
|
||||||
|
|
||||||
void addActor (const MWWorld::Ptr& ptr);
|
void addActor (const MWWorld::Ptr& ptr);
|
||||||
///< Register an actor for stats management
|
///< Register an actor for stats management
|
||||||
///
|
///
|
||||||
|
|
|
@ -516,6 +516,12 @@ bool CharacterController::updateNpcState(bool onground, bool inwater, bool isrun
|
||||||
const ESM::Static* castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
const ESM::Static* castStatic = store.get<ESM::Static>().find (effect->mCasting);
|
||||||
mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex);
|
mAnimation->addEffect("meshes\\" + castStatic->mModel, effect->mIndex);
|
||||||
|
|
||||||
|
castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Hands");
|
||||||
|
//mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle);
|
||||||
|
//mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle);
|
||||||
|
mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle);
|
||||||
|
mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle);
|
||||||
|
|
||||||
switch(effectentry.mRange)
|
switch(effectentry.mRange)
|
||||||
{
|
{
|
||||||
case 0: mAttackType = "self"; break;
|
case 0: mAttackType = "self"; break;
|
||||||
|
|
|
@ -12,13 +12,6 @@ namespace ESM
|
||||||
|
|
||||||
namespace MWMechanics
|
namespace MWMechanics
|
||||||
{
|
{
|
||||||
// Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display
|
|
||||||
struct EffectSourceVisitor
|
|
||||||
{
|
|
||||||
virtual void visit (const ESM::ENAMstruct& enam,
|
|
||||||
const std::string& sourceName, float magnitude, float remainingTime = -1) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EffectKey
|
struct EffectKey
|
||||||
{
|
{
|
||||||
int mId;
|
int mId;
|
||||||
|
@ -59,6 +52,13 @@ namespace MWMechanics
|
||||||
return param -= right;
|
return param -= right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used by effect management classes (ActiveSpells, InventoryStore, Spells) to list active effect sources for GUI display
|
||||||
|
struct EffectSourceVisitor
|
||||||
|
{
|
||||||
|
virtual void visit (MWMechanics::EffectKey key,
|
||||||
|
const std::string& sourceName, float magnitude, float remainingTime = -1) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
/// \brief Effects currently affecting a NPC or creature
|
/// \brief Effects currently affecting a NPC or creature
|
||||||
class MagicEffects
|
class MagicEffects
|
||||||
{
|
{
|
||||||
|
|
|
@ -679,4 +679,9 @@ namespace MWMechanics
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MechanicsManager::updateMagicEffects(const MWWorld::Ptr &ptr)
|
||||||
|
{
|
||||||
|
mActors.updateMagicEffects(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,6 +100,11 @@ namespace MWMechanics
|
||||||
virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
|
virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number);
|
||||||
virtual void skipAnimation(const MWWorld::Ptr& ptr);
|
virtual void skipAnimation(const MWWorld::Ptr& ptr);
|
||||||
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName);
|
virtual bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string &groupName);
|
||||||
|
|
||||||
|
/// Update magic effects for an actor. Usually done automatically once per frame, but if we're currently
|
||||||
|
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||||
|
virtual void updateMagicEffects (const MWWorld::Ptr& ptr);
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
473
apps/openmw/mwmechanics/spellcasting.cpp
Normal file
473
apps/openmw/mwmechanics/spellcasting.cpp
Normal file
|
@ -0,0 +1,473 @@
|
||||||
|
#include "spellcasting.hpp"
|
||||||
|
|
||||||
|
#include <boost/format.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
#include "../mwbase/soundmanager.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#include "../mwworld/containerstore.hpp"
|
||||||
|
|
||||||
|
#include "../mwrender/animation.hpp"
|
||||||
|
|
||||||
|
namespace MWMechanics
|
||||||
|
{
|
||||||
|
|
||||||
|
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target)
|
||||||
|
: mCaster(caster)
|
||||||
|
, mTarget(target)
|
||||||
|
, mStack(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void CastSpell::inflict(const MWWorld::Ptr &target, const MWWorld::Ptr &caster,
|
||||||
|
const ESM::EffectList &effects, ESM::RangeType range, bool reflected)
|
||||||
|
{
|
||||||
|
// If none of the effects need to apply, we can early-out
|
||||||
|
bool found = false;
|
||||||
|
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.mList.begin());
|
||||||
|
iter!=effects.mList.end(); ++iter)
|
||||||
|
{
|
||||||
|
if (iter->mRange != range)
|
||||||
|
continue;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
if (!found)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ESM::EffectList reflectedEffects;
|
||||||
|
std::vector<ActiveSpells::Effect> appliedLastingEffects;
|
||||||
|
bool firstAppliedEffect = true;
|
||||||
|
|
||||||
|
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt (effects.mList.begin());
|
||||||
|
effectIt!=effects.mList.end(); ++effectIt)
|
||||||
|
{
|
||||||
|
if (effectIt->mRange != range)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const ESM::MagicEffect *magicEffect =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||||
|
effectIt->mEffectID);
|
||||||
|
|
||||||
|
float magnitudeMult = 1;
|
||||||
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::Harmful && target.getClass().isActor())
|
||||||
|
{
|
||||||
|
// If player is attempting to cast a harmful spell, show the target's HP bar
|
||||||
|
if (caster.getRefData().getHandle() == "player" && target != caster)
|
||||||
|
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||||
|
|
||||||
|
// Try absorbing if it's a spell
|
||||||
|
// NOTE: Vanilla does this once per effect source instead of adding the % from all sources together, not sure
|
||||||
|
// if that is worth replicating.
|
||||||
|
if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId))
|
||||||
|
{
|
||||||
|
int absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).mMagnitude;
|
||||||
|
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||||
|
bool isAbsorbed = (roll < absorb);
|
||||||
|
if (isAbsorbed)
|
||||||
|
{
|
||||||
|
const ESM::Static* absorbStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Absorb");
|
||||||
|
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||||
|
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::Reflect, false, "");
|
||||||
|
// Magicka is increased by cost of spell
|
||||||
|
DynamicStat<float> magicka = target.getClass().getCreatureStats(target).getMagicka();
|
||||||
|
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||||
|
target.getClass().getCreatureStats(target).setMagicka(magicka);
|
||||||
|
magnitudeMult = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try reflecting
|
||||||
|
if (!reflected && magnitudeMult > 0)
|
||||||
|
{
|
||||||
|
int reflect = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Reflect).mMagnitude;
|
||||||
|
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||||
|
bool isReflected = (roll < reflect);
|
||||||
|
if (isReflected)
|
||||||
|
{
|
||||||
|
const ESM::Static* reflectStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find ("VFX_Reflect");
|
||||||
|
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||||
|
"meshes\\" + reflectStatic->mModel, ESM::MagicEffect::Reflect, false, "");
|
||||||
|
reflectedEffects.mList.push_back(*effectIt);
|
||||||
|
magnitudeMult = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try resisting
|
||||||
|
if (magnitudeMult > 0 && caster.getClass().isActor())
|
||||||
|
{
|
||||||
|
const ESM::Spell *spell =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (mId);
|
||||||
|
magnitudeMult = MWMechanics::getEffectMultiplier(effectIt->mEffectID, target, caster, spell);
|
||||||
|
if (magnitudeMult == 0)
|
||||||
|
{
|
||||||
|
// Fully resisted, show message
|
||||||
|
if (target.getRefData().getHandle() == "player")
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicPCResisted}");
|
||||||
|
else
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicTargetResisted}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (magnitudeMult > 0)
|
||||||
|
{
|
||||||
|
float random = std::rand() / static_cast<float>(RAND_MAX);
|
||||||
|
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random;
|
||||||
|
magnitude *= magnitudeMult;
|
||||||
|
|
||||||
|
if (target.getClass().isActor() && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
||||||
|
{
|
||||||
|
ActiveSpells::Effect effect;
|
||||||
|
effect.mKey = MWMechanics::EffectKey(*effectIt);
|
||||||
|
effect.mDuration = effectIt->mDuration;
|
||||||
|
effect.mMagnitude = magnitude;
|
||||||
|
|
||||||
|
appliedLastingEffects.push_back(effect);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
applyInstantEffect(mTarget, effectIt->mEffectID, magnitude);
|
||||||
|
|
||||||
|
if (target.getClass().isActor() || magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||||
|
{
|
||||||
|
// Play sound, only for the first effect
|
||||||
|
if (firstAppliedEffect)
|
||||||
|
{
|
||||||
|
static const std::string schools[] = {
|
||||||
|
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||||
|
};
|
||||||
|
|
||||||
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
|
if(!magicEffect->mHitSound.empty())
|
||||||
|
sndMgr->playSound3D(target, magicEffect->mHitSound, 1.0f, 1.0f);
|
||||||
|
else
|
||||||
|
sndMgr->playSound3D(target, schools[magicEffect->mData.mSchool]+" hit", 1.0f, 1.0f);
|
||||||
|
firstAppliedEffect = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add VFX
|
||||||
|
if (!magicEffect->mHit.empty())
|
||||||
|
{
|
||||||
|
const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get<ESM::Static>().find (magicEffect->mHit);
|
||||||
|
bool loop = magicEffect->mData.mFlags & ESM::MagicEffect::ContinuousVfx;
|
||||||
|
// Note: in case of non actor, a free effect should be fine as well
|
||||||
|
MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(target);
|
||||||
|
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 (reflectedEffects.mList.size())
|
||||||
|
inflict(caster, target, reflectedEffects, range, true);
|
||||||
|
|
||||||
|
if (appliedLastingEffects.size())
|
||||||
|
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(mId, mStack, appliedLastingEffects, mSourceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CastSpell::applyInstantEffect(const MWWorld::Ptr &target, short effectId, float magnitude)
|
||||||
|
{
|
||||||
|
if (!target.getClass().isActor())
|
||||||
|
{
|
||||||
|
if (effectId == ESM::MagicEffect::Lock)
|
||||||
|
{
|
||||||
|
if (target.getCellRef().mLockLevel < magnitude)
|
||||||
|
target.getCellRef().mLockLevel = magnitude;
|
||||||
|
}
|
||||||
|
else if (effectId == ESM::MagicEffect::Open)
|
||||||
|
{
|
||||||
|
// TODO: This is a crime
|
||||||
|
if (target.getCellRef().mLockLevel <= magnitude)
|
||||||
|
{
|
||||||
|
if (target.getCellRef().mLockLevel > 0)
|
||||||
|
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock", 1.f, 1.f);
|
||||||
|
target.getCellRef().mLockLevel = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
MWBase::Environment::get().getSoundManager()->playSound3D(target, "Open Lock Fail", 1.f, 1.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (effectId == ESM::MagicEffect::CurePoison)
|
||||||
|
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
|
||||||
|
else if (effectId == ESM::MagicEffect::CureParalyzation)
|
||||||
|
target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
|
||||||
|
else if (effectId == ESM::MagicEffect::CureCommonDisease)
|
||||||
|
target.getClass().getCreatureStats(target).getSpells().purgeCommonDisease();
|
||||||
|
else if (effectId == ESM::MagicEffect::CureBlightDisease)
|
||||||
|
target.getClass().getCreatureStats(target).getSpells().purgeBlightDisease();
|
||||||
|
else if (effectId == ESM::MagicEffect::CureCorprusDisease)
|
||||||
|
target.getClass().getCreatureStats(target).getSpells().purgeCorprusDisease();
|
||||||
|
else if (effectId == ESM::MagicEffect::Dispel)
|
||||||
|
target.getClass().getCreatureStats(target).getActiveSpells().purgeAll();
|
||||||
|
else if (effectId == ESM::MagicEffect::RemoveCurse)
|
||||||
|
target.getClass().getCreatureStats(target).getSpells().purgeCurses();
|
||||||
|
|
||||||
|
else if (effectId == ESM::MagicEffect::DivineIntervention)
|
||||||
|
{
|
||||||
|
// We need to be able to get the world location of an interior cell before implementing this
|
||||||
|
// or alternatively, the last known exterior location of the player, which is how vanilla does it.
|
||||||
|
}
|
||||||
|
else if (effectId == ESM::MagicEffect::AlmsiviIntervention)
|
||||||
|
{
|
||||||
|
// Same as above
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (effectId == ESM::MagicEffect::Mark)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
else if (effectId == ESM::MagicEffect::Recall)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CastSpell::cast(const std::string &id)
|
||||||
|
{
|
||||||
|
if (const ESM::Spell *spell =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().search (id))
|
||||||
|
return cast(spell);
|
||||||
|
|
||||||
|
if (const ESM::Potion *potion =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Potion>().search (id))
|
||||||
|
return cast(potion);
|
||||||
|
|
||||||
|
if (const ESM::Ingredient *ingredient =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Ingredient>().search (id))
|
||||||
|
return cast(ingredient);
|
||||||
|
|
||||||
|
throw std::runtime_error("ID type cannot be casted");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CastSpell::cast(const MWWorld::Ptr &item)
|
||||||
|
{
|
||||||
|
std::string enchantmentName = item.getClass().getEnchantment(item);
|
||||||
|
if (enchantmentName.empty())
|
||||||
|
throw std::runtime_error("can't cast an item without an enchantment");
|
||||||
|
|
||||||
|
mSourceName = item.getClass().getName(item);
|
||||||
|
mId = item.getCellRef().mRefID;
|
||||||
|
|
||||||
|
const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>().find(enchantmentName);
|
||||||
|
|
||||||
|
mStack = (enchantment->mData.mType == ESM::Enchantment::CastOnce);
|
||||||
|
|
||||||
|
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
|
||||||
|
{
|
||||||
|
// 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();
|
||||||
|
const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10);
|
||||||
|
|
||||||
|
if (item.getCellRef().mEnchantmentCharge == -1)
|
||||||
|
item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
|
||||||
|
|
||||||
|
if (mCaster.getRefData().getHandle() == "player" && item.getCellRef().mEnchantmentCharge < castCost)
|
||||||
|
{
|
||||||
|
// TODO: Should there be a sound here?
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce charge
|
||||||
|
item.getCellRef().mEnchantmentCharge -= castCost;
|
||||||
|
}
|
||||||
|
if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
|
||||||
|
item.getContainerStore()->remove(item, 1, mCaster);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (mCaster.getRefData().getHandle() == "player")
|
||||||
|
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
|
||||||
|
}
|
||||||
|
|
||||||
|
inflict(mCaster, mCaster, enchantment->mEffects, ESM::RT_Self);
|
||||||
|
|
||||||
|
if (!mTarget.isEmpty())
|
||||||
|
{
|
||||||
|
if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead())
|
||||||
|
inflict(mTarget, mCaster, enchantment->mEffects, ESM::RT_Touch);
|
||||||
|
}
|
||||||
|
|
||||||
|
MWBase::Environment::get().getWorld()->launchProjectile(mId, enchantment->mEffects, mCaster, mSourceName);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CastSpell::cast(const ESM::Potion* potion)
|
||||||
|
{
|
||||||
|
mSourceName = potion->mName;
|
||||||
|
mId = potion->mId;
|
||||||
|
mStack = true;
|
||||||
|
|
||||||
|
inflict(mCaster, mCaster, potion->mEffects, ESM::RT_Self);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CastSpell::cast(const ESM::Spell* spell)
|
||||||
|
{
|
||||||
|
mSourceName = spell->mName;
|
||||||
|
mId = spell->mId;
|
||||||
|
mStack = false;
|
||||||
|
|
||||||
|
const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore();
|
||||||
|
|
||||||
|
int school = 0;
|
||||||
|
|
||||||
|
if (mCaster.getClass().isActor())
|
||||||
|
{
|
||||||
|
school = getSpellSchool(spell, mCaster);
|
||||||
|
|
||||||
|
CreatureStats& stats = mCaster.getClass().getCreatureStats(mCaster);
|
||||||
|
|
||||||
|
// Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss)
|
||||||
|
static const float fFatigueSpellBase = store.get<ESM::GameSetting>().find("fFatigueSpellBase")->getFloat();
|
||||||
|
static const float fFatigueSpellMult = store.get<ESM::GameSetting>().find("fFatigueSpellMult")->getFloat();
|
||||||
|
DynamicStat<float> fatigue = stats.getFatigue();
|
||||||
|
const float normalizedEncumbrance = mCaster.getClass().getEncumbrance(mCaster) / mCaster.getClass().getCapacity(mCaster);
|
||||||
|
float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult);
|
||||||
|
fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss));
|
||||||
|
stats.setFatigue(fatigue);
|
||||||
|
|
||||||
|
// Check mana
|
||||||
|
bool fail = false;
|
||||||
|
DynamicStat<float> magicka = stats.getMagicka();
|
||||||
|
if (magicka.getCurrent() < spell->mData.mCost)
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce mana
|
||||||
|
if (!fail)
|
||||||
|
{
|
||||||
|
magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
|
||||||
|
stats.setMagicka(magicka);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a power, check if it was already used in last 24h
|
||||||
|
if (!fail && spell->mData.mType & ESM::Spell::ST_Power)
|
||||||
|
{
|
||||||
|
if (stats.canUsePower(spell->mId))
|
||||||
|
stats.usePower(spell->mId);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check success
|
||||||
|
int successChance = getSpellSuccessChance(spell, mCaster);
|
||||||
|
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||||
|
if (!fail && roll >= successChance)
|
||||||
|
{
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
|
||||||
|
fail = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail)
|
||||||
|
{
|
||||||
|
// Failure sound
|
||||||
|
static const std::string schools[] = {
|
||||||
|
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
||||||
|
};
|
||||||
|
|
||||||
|
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||||
|
sndMgr->playSound3D(mCaster, "Spell Failure " + schools[school], 1.0f, 1.0f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCaster.getRefData().getHandle() == "player" && spell->mData.mType == ESM::Spell::ST_Spell)
|
||||||
|
mCaster.getClass().skillUsageSucceeded(mCaster,
|
||||||
|
spellSchoolToSkill(school), 0);
|
||||||
|
|
||||||
|
inflict(mCaster, mCaster, spell->mEffects, ESM::RT_Self);
|
||||||
|
|
||||||
|
if (!mTarget.isEmpty())
|
||||||
|
{
|
||||||
|
if (!mTarget.getClass().isActor() || !mTarget.getClass().getCreatureStats(mTarget).isDead())
|
||||||
|
{
|
||||||
|
inflict(mTarget, mCaster, spell->mEffects, ESM::RT_Touch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MWBase::Environment::get().getWorld()->launchProjectile(mId, spell->mEffects, mCaster, mSourceName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CastSpell::cast (const ESM::Ingredient* ingredient)
|
||||||
|
{
|
||||||
|
mId = ingredient->mId;
|
||||||
|
mStack = true;
|
||||||
|
mSourceName = ingredient->mName;
|
||||||
|
|
||||||
|
ESM::ENAMstruct effect;
|
||||||
|
effect.mEffectID = ingredient->mData.mEffectID[0];
|
||||||
|
effect.mSkill = ingredient->mData.mSkills[0];
|
||||||
|
effect.mAttribute = ingredient->mData.mAttributes[0];
|
||||||
|
effect.mRange = ESM::RT_Self;
|
||||||
|
effect.mArea = 0;
|
||||||
|
|
||||||
|
const ESM::MagicEffect *magicEffect =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
|
||||||
|
effect.mEffectID);
|
||||||
|
|
||||||
|
const MWMechanics::NpcStats& npcStats = mCaster.getClass().getNpcStats(mCaster);
|
||||||
|
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
|
||||||
|
|
||||||
|
float x = (npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
|
||||||
|
0.2 * creatureStats.getAttribute (ESM::Attribute::Intelligence).getModified()
|
||||||
|
+ 0.1 * creatureStats.getAttribute (ESM::Attribute::Luck).getModified())
|
||||||
|
* creatureStats.getFatigueTerm();
|
||||||
|
|
||||||
|
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
||||||
|
if (roll > x)
|
||||||
|
{
|
||||||
|
// "X has no effect on you"
|
||||||
|
std::string message = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("#{sNotifyMessage50}")->getString();
|
||||||
|
message = boost::str(boost::format(message) % ingredient->mName);
|
||||||
|
|
||||||
|
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float magnitude = 0;
|
||||||
|
float y = roll / std::min(x, 100.f);
|
||||||
|
y *= 0.25 * x;
|
||||||
|
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||||
|
effect.mDuration = int(y);
|
||||||
|
else
|
||||||
|
effect.mDuration = 1;
|
||||||
|
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||||
|
{
|
||||||
|
if (!magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||||
|
magnitude = int((0.05 * y) / (0.1 * magicEffect->mData.mBaseCost));
|
||||||
|
else
|
||||||
|
magnitude = int(y / (0.1 * magicEffect->mData.mBaseCost));
|
||||||
|
magnitude = std::max(1.f, magnitude);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
magnitude = 1;
|
||||||
|
|
||||||
|
effect.mMagnMax = magnitude;
|
||||||
|
effect.mMagnMin = magnitude;
|
||||||
|
|
||||||
|
ESM::EffectList effects;
|
||||||
|
effects.mList.push_back(effect);
|
||||||
|
|
||||||
|
inflict(mCaster, mCaster, effects, ESM::RT_Self);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -44,12 +44,6 @@ namespace MWMechanics
|
||||||
if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
|
if (creatureStats.getMagicEffects().get(ESM::MagicEffect::Silence).mMagnitude)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
|
||||||
return 100;
|
|
||||||
|
|
||||||
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
|
||||||
return 100;
|
|
||||||
|
|
||||||
float y = FLT_MAX;
|
float y = FLT_MAX;
|
||||||
float lowestSkill = 0;
|
float lowestSkill = 0;
|
||||||
|
|
||||||
|
@ -79,8 +73,14 @@ namespace MWMechanics
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||||
|
return 100;
|
||||||
|
|
||||||
|
if (spell->mData.mFlags & ESM::Spell::F_Always)
|
||||||
|
return 100;
|
||||||
|
|
||||||
int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
|
int castBonus = -stats.getMagicEffects().get(ESM::MagicEffect::Sound).mMagnitude;
|
||||||
|
|
||||||
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
int actorWillpower = stats.getAttribute(ESM::Attribute::Willpower).getModified();
|
||||||
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
int actorLuck = stats.getAttribute(ESM::Attribute::Luck).getModified();
|
||||||
|
|
||||||
|
@ -98,7 +98,6 @@ namespace MWMechanics
|
||||||
return getSpellSuccessChance(spell, actor, effectiveSchool);
|
return getSpellSuccessChance(spell, actor, effectiveSchool);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @note this only works for ST_Spell
|
|
||||||
inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
inline int getSpellSchool(const std::string& spellId, const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
int school = 0;
|
int school = 0;
|
||||||
|
@ -106,7 +105,6 @@ namespace MWMechanics
|
||||||
return school;
|
return school;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @note this only works for ST_Spell
|
|
||||||
inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
inline int getSpellSchool(const ESM::Spell* spell, const MWWorld::Ptr& actor)
|
||||||
{
|
{
|
||||||
int school = 0;
|
int school = 0;
|
||||||
|
@ -180,6 +178,34 @@ namespace MWMechanics
|
||||||
return -(resistance-100) / 100.f;
|
return -(resistance-100) / 100.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CastSpell
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
MWWorld::Ptr mCaster;
|
||||||
|
MWWorld::Ptr mTarget;
|
||||||
|
|
||||||
|
bool mStack;
|
||||||
|
std::string mId; // ID of spell, potion, item etc
|
||||||
|
std::string mSourceName; // Display name for spell, potion, etc
|
||||||
|
|
||||||
|
public:
|
||||||
|
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target);
|
||||||
|
|
||||||
|
bool cast (const ESM::Spell* spell);
|
||||||
|
bool cast (const MWWorld::Ptr& item);
|
||||||
|
bool cast (const ESM::Ingredient* ingredient);
|
||||||
|
bool cast (const ESM::Potion* potion);
|
||||||
|
|
||||||
|
/// @note Auto detects if spell, ingredient or potion
|
||||||
|
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);
|
||||||
|
|
||||||
|
void applyInstantEffect (const MWWorld::Ptr& target, short effectId, float magnitude);
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -118,6 +118,62 @@ namespace MWMechanics
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Spells::purgeCommonDisease()
|
||||||
|
{
|
||||||
|
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
||||||
|
{
|
||||||
|
const ESM::Spell *spell =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
|
||||||
|
|
||||||
|
if (spell->mData.mType & ESM::Spell::ST_Disease)
|
||||||
|
mSpells.erase(iter++);
|
||||||
|
else
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spells::purgeBlightDisease()
|
||||||
|
{
|
||||||
|
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
||||||
|
{
|
||||||
|
const ESM::Spell *spell =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
|
||||||
|
|
||||||
|
if (spell->mData.mType & ESM::Spell::ST_Blight)
|
||||||
|
mSpells.erase(iter++);
|
||||||
|
else
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spells::purgeCorprusDisease()
|
||||||
|
{
|
||||||
|
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
||||||
|
{
|
||||||
|
const ESM::Spell *spell =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
|
||||||
|
|
||||||
|
if (Misc::StringUtils::ciEqual(spell->mId, "corprus"))
|
||||||
|
mSpells.erase(iter++);
|
||||||
|
else
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Spells::purgeCurses()
|
||||||
|
{
|
||||||
|
for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();)
|
||||||
|
{
|
||||||
|
const ESM::Spell *spell =
|
||||||
|
MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find (iter->first);
|
||||||
|
|
||||||
|
if (spell->mData.mType == ESM::Spell::ST_Curse)
|
||||||
|
mSpells.erase(iter++);
|
||||||
|
else
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Spells::visitEffectSources(EffectSourceVisitor &visitor) const
|
void Spells::visitEffectSources(EffectSourceVisitor &visitor) const
|
||||||
{
|
{
|
||||||
for (TIterator it = begin(); it != end(); ++it)
|
for (TIterator it = begin(); it != end(); ++it)
|
||||||
|
@ -136,7 +192,7 @@ namespace MWMechanics
|
||||||
effectIt != list.mList.end(); ++effectIt, ++i)
|
effectIt != list.mList.end(); ++effectIt, ++i)
|
||||||
{
|
{
|
||||||
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i];
|
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * it->second[i];
|
||||||
visitor.visit(*effectIt, spell->mName, magnitude);
|
visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, magnitude);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,11 @@ namespace MWMechanics
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
void purgeCommonDisease();
|
||||||
|
void purgeBlightDisease();
|
||||||
|
void purgeCorprusDisease();
|
||||||
|
void purgeCurses();
|
||||||
|
|
||||||
TIterator begin() const;
|
TIterator begin() const;
|
||||||
|
|
||||||
TIterator end() const;
|
TIterator end() const;
|
||||||
|
|
|
@ -994,16 +994,27 @@ void Animation::detachObjectFromBone(Ogre::MovableObject *obj)
|
||||||
mSkelBase->detachObjectFromBone(obj);
|
mSkelBase->detachObjectFromBone(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename)
|
void Animation::addEffect(const std::string &model, int effectId, bool loop, const std::string &bonename, std::string texture)
|
||||||
{
|
{
|
||||||
// Early out if we already have this effect
|
// Early out if we already have this effect
|
||||||
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
for (std::vector<EffectParams>::iterator it = mEffects.begin(); it != mEffects.end(); ++it)
|
||||||
if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename)
|
if (it->mLoop && loop && it->mEffectId == effectId && it->mBoneName == bonename)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// fix texture extension to .dds
|
||||||
|
if (texture.size() > 4)
|
||||||
|
{
|
||||||
|
texture[texture.size()-3] = 'd';
|
||||||
|
texture[texture.size()-2] = 'd';
|
||||||
|
texture[texture.size()-1] = 's';
|
||||||
|
}
|
||||||
|
|
||||||
EffectParams params;
|
EffectParams params;
|
||||||
params.mModelName = model;
|
params.mModelName = model;
|
||||||
params.mObjects = NifOgre::Loader::createObjects(mInsert, model);
|
if (bonename.empty())
|
||||||
|
params.mObjects = NifOgre::Loader::createObjects(mInsert, model);
|
||||||
|
else
|
||||||
|
params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model);
|
||||||
params.mLoop = loop;
|
params.mLoop = loop;
|
||||||
params.mEffectId = effectId;
|
params.mEffectId = effectId;
|
||||||
params.mBoneName = bonename;
|
params.mBoneName = bonename;
|
||||||
|
@ -1013,6 +1024,35 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con
|
||||||
if(params.mObjects.mControllers[i].getSource().isNull())
|
if(params.mObjects.mControllers[i].getSource().isNull())
|
||||||
params.mObjects.mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationValue> (new EffectAnimationValue()));
|
params.mObjects.mControllers[i].setSource(Ogre::SharedPtr<EffectAnimationValue> (new EffectAnimationValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!texture.empty())
|
||||||
|
{
|
||||||
|
for(size_t i = 0;i < params.mObjects.mParticles.size(); ++i)
|
||||||
|
{
|
||||||
|
Ogre::ParticleSystem* partSys = params.mObjects.mParticles[i];
|
||||||
|
Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(partSys->getMaterialName());
|
||||||
|
static int count = 0;
|
||||||
|
Ogre::String materialName = "openmw/" + Ogre::StringConverter::toString(count++);
|
||||||
|
// TODO: destroy when effect is removed
|
||||||
|
Ogre::MaterialPtr newMat = mat->clone(materialName);
|
||||||
|
partSys->setMaterialName(materialName);
|
||||||
|
|
||||||
|
for (int t=0; t<newMat->getNumTechniques(); ++t)
|
||||||
|
{
|
||||||
|
Ogre::Technique* tech = newMat->getTechnique(t);
|
||||||
|
for (int p=0; p<tech->getNumPasses(); ++p)
|
||||||
|
{
|
||||||
|
Ogre::Pass* pass = tech->getPass(p);
|
||||||
|
for (int tex=0; tex<pass->getNumTextureUnitStates(); ++tex)
|
||||||
|
{
|
||||||
|
Ogre::TextureUnitState* tus = pass->getTextureUnitState(tex);
|
||||||
|
tus->setTextureName("textures\\" + texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mEffects.push_back(params);
|
mEffects.push_back(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -206,9 +206,10 @@ public:
|
||||||
* @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true,
|
* @param loop Loop the effect. If false, it is removed automatically after it finishes playing. If true,
|
||||||
* you need to remove it manually using removeEffect when the effect should end.
|
* you need to remove it manually using removeEffect when the effect should end.
|
||||||
* @param bonename Bone to attach to, or empty string to use the scene node instead
|
* @param bonename Bone to attach to, or empty string to use the scene node instead
|
||||||
|
* @param texture override the texture specified in the model's materials
|
||||||
* @note Will not add an effect twice.
|
* @note Will not add an effect twice.
|
||||||
*/
|
*/
|
||||||
void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "");
|
void addEffect (const std::string& model, int effectId, bool loop = false, const std::string& bonename = "", std::string texture = "");
|
||||||
void removeEffect (int effectId);
|
void removeEffect (int effectId);
|
||||||
void getLoopingEffects (std::vector<int>& out);
|
void getLoopingEffects (std::vector<int>& out);
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -281,3 +281,11 @@ void Objects::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur)
|
||||||
node->addChild(cur.getRefData().getBaseNode());
|
node->addChild(cur.getRefData().getBaseNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ObjectAnimation* Objects::getAnimation(const MWWorld::Ptr &ptr)
|
||||||
|
{
|
||||||
|
PtrAnimationMap::const_iterator iter = mObjects.find(ptr);
|
||||||
|
if(iter != mObjects.end())
|
||||||
|
return iter->second;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,8 @@ public:
|
||||||
~Objects(){}
|
~Objects(){}
|
||||||
void insertModel(const MWWorld::Ptr& ptr, const std::string &model);
|
void insertModel(const MWWorld::Ptr& ptr, const std::string &model);
|
||||||
|
|
||||||
|
ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr);
|
||||||
|
|
||||||
void enableLights();
|
void enableLights();
|
||||||
void disableLights();
|
void disableLights();
|
||||||
|
|
||||||
|
|
|
@ -975,8 +975,13 @@ void RenderingManager::setupExternalRendering (MWRender::ExternalRendering& rend
|
||||||
Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr)
|
Animation* RenderingManager::getAnimation(const MWWorld::Ptr &ptr)
|
||||||
{
|
{
|
||||||
Animation *anim = mActors.getAnimation(ptr);
|
Animation *anim = mActors.getAnimation(ptr);
|
||||||
|
|
||||||
if(!anim && ptr.getRefData().getHandle() == "player")
|
if(!anim && ptr.getRefData().getHandle() == "player")
|
||||||
anim = mPlayerAnimation;
|
anim = mPlayerAnimation;
|
||||||
|
|
||||||
|
if (!anim)
|
||||||
|
anim = mObjects.getAnimation(ptr);
|
||||||
|
|
||||||
return anim;
|
return anim;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,11 @@
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
#include <components/esm/loadskil.hpp>
|
|
||||||
|
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
|
||||||
|
|
||||||
#include "../mwworld/containerstore.hpp"
|
#include "../mwworld/containerstore.hpp"
|
||||||
|
|
||||||
#include "esmstore.hpp"
|
|
||||||
#include "class.hpp"
|
#include "class.hpp"
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
|
@ -23,27 +17,11 @@ namespace MWWorld
|
||||||
// remove used item (assume the item is present in inventory)
|
// remove used item (assume the item is present in inventory)
|
||||||
getTarget().getContainerStore()->remove(getTarget(), 1, actor);
|
getTarget().getContainerStore()->remove(getTarget(), 1, actor);
|
||||||
|
|
||||||
// check for success
|
// apply to actor
|
||||||
const MWMechanics::CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor);
|
std::string id = Class::get (getTarget()).getId (getTarget());
|
||||||
MWMechanics::NpcStats& npcStats = MWWorld::Class::get (actor).getNpcStats (actor);
|
|
||||||
|
|
||||||
float x =
|
|
||||||
(npcStats.getSkill (ESM::Skill::Alchemy).getModified() +
|
|
||||||
0.2 * creatureStats.getAttribute (1).getModified()
|
|
||||||
+ 0.1 * creatureStats.getAttribute (7).getModified())
|
|
||||||
* creatureStats.getFatigueTerm();
|
|
||||||
|
|
||||||
if (x>=100*static_cast<float> (std::rand()) / RAND_MAX)
|
|
||||||
{
|
|
||||||
// apply to actor
|
|
||||||
std::string id = Class::get (getTarget()).getId (getTarget());
|
|
||||||
|
|
||||||
Class::get (actor).apply (actor, id, actor);
|
if (Class::get (actor).apply (actor, id, actor))
|
||||||
// we ignore the result here. Skill increases no matter if the ingredient did something or not.
|
|
||||||
|
|
||||||
// increase skill
|
|
||||||
Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1);
|
Class::get (actor).skillUsageSucceeded (actor, ESM::Skill::Alchemy, 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {}
|
ActionEat::ActionEat (const MWWorld::Ptr& object) : Action (false, object) {}
|
||||||
|
|
|
@ -1,26 +1,14 @@
|
||||||
#include "actiontrap.hpp"
|
#include "actiontrap.hpp"
|
||||||
|
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/activespells.hpp"
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
|
||||||
|
|
||||||
#include "../mwbase/world.hpp"
|
|
||||||
#include "../mwbase/environment.hpp"
|
|
||||||
|
|
||||||
namespace MWWorld
|
namespace MWWorld
|
||||||
{
|
{
|
||||||
|
|
||||||
void ActionTrap::executeImp(const Ptr &actor)
|
void ActionTrap::executeImp(const Ptr &actor)
|
||||||
{
|
{
|
||||||
// TODO: Apply RT_Self effects on the door / container that triggered the trap. Not terribly useful, but you could
|
MWMechanics::CastSpell cast(mTrapSource, actor);
|
||||||
// make it lock itself when activated for example.
|
cast.cast(mSpellId);
|
||||||
|
|
||||||
actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(mSpellId, actor, actor, ESM::RT_Touch);
|
|
||||||
|
|
||||||
const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get<ESM::Spell>().find(mSpellId);
|
|
||||||
|
|
||||||
MWBase::Environment::get().getWorld()->launchProjectile(mSpellId, spell->mEffects, mTrapSource, spell->mName);
|
|
||||||
|
|
||||||
mTrapSource.getCellRef().mTrap = "";
|
mTrapSource.getCellRef().mTrap = "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
#include "../mwbase/world.hpp"
|
#include "../mwbase/world.hpp"
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
#include "../mwbase/mechanicsmanager.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/npcstats.hpp"
|
#include "../mwmechanics/npcstats.hpp"
|
||||||
#include "../mwmechanics/spellcasting.hpp"
|
#include "../mwmechanics/spellcasting.hpp"
|
||||||
|
@ -339,6 +340,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
|
||||||
if (params[i].mMultiplier == 0)
|
if (params[i].mMultiplier == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom;
|
||||||
|
magnitude *= params[i].mMultiplier;
|
||||||
|
|
||||||
if (!existed)
|
if (!existed)
|
||||||
{
|
{
|
||||||
// During first auto equip, we don't play any sounds.
|
// During first auto equip, we don't play any sounds.
|
||||||
|
@ -346,10 +350,13 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
|
||||||
// the items should appear as if they'd always been equipped.
|
// the items should appear as if they'd always been equipped.
|
||||||
mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip,
|
mListener->permanentEffectAdded(magicEffect, !mFirstAutoEquip,
|
||||||
!mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin());
|
!mFirstAutoEquip && effectIt == enchantment.mEffects.mList.begin());
|
||||||
|
|
||||||
|
// Apply instant effects
|
||||||
|
MWMechanics::CastSpell cast(actor, actor);
|
||||||
|
if (magnitude)
|
||||||
|
cast.applyInstantEffect(actor, effectIt->mEffectID, magnitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom;
|
|
||||||
magnitude *= params[i].mMultiplier;
|
|
||||||
if (magnitude)
|
if (magnitude)
|
||||||
mMagicEffects.add (*effectIt, magnitude);
|
mMagicEffects.add (*effectIt, magnitude);
|
||||||
}
|
}
|
||||||
|
@ -376,6 +383,9 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor)
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Magic effects are normally not updated when paused, but we need this to make resistances work immediately after equipping
|
||||||
|
MWBase::Environment::get().getMechanicsManager()->updateMagicEffects(actor);
|
||||||
|
|
||||||
mFirstAutoEquip = false;
|
mFirstAutoEquip = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,6 +452,13 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor
|
||||||
autoEquip(actor);
|
autoEquip(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end()
|
||||||
|
&& *mSelectedEnchantItem == item && actor.getRefData().getHandle() == "player")
|
||||||
|
{
|
||||||
|
mSelectedEnchantItem = end();
|
||||||
|
MWBase::Environment::get().getWindowManager()->unsetSelectedSpell();
|
||||||
|
}
|
||||||
|
|
||||||
return retCount;
|
return retCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,7 +571,7 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito
|
||||||
const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i];
|
const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().mRefID][i];
|
||||||
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom;
|
float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom;
|
||||||
magnitude *= params.mMultiplier;
|
magnitude *= params.mMultiplier;
|
||||||
visitor.visit(*effectIt, (**iter).getClass().getName(**iter), magnitude);
|
visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), magnitude);
|
||||||
|
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2029,156 +2029,28 @@ namespace MWWorld
|
||||||
void World::castSpell(const Ptr &actor)
|
void World::castSpell(const Ptr &actor)
|
||||||
{
|
{
|
||||||
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||||
|
InventoryStore& inv = actor.getClass().getInventoryStore(actor);
|
||||||
|
|
||||||
|
// Unset casting flag, otherwise pressing the mouse button down would continue casting every frame if using an enchantment
|
||||||
|
// (which casts instantly without an animation)
|
||||||
stats.setAttackingOrSpell(false);
|
stats.setAttackingOrSpell(false);
|
||||||
|
|
||||||
ESM::EffectList effects;
|
MWWorld::Ptr target = getFacedObject();
|
||||||
|
|
||||||
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
std::string selectedSpell = stats.getSpells().getSelectedSpell();
|
||||||
std::string sourceName;
|
|
||||||
|
MWMechanics::CastSpell cast(actor, target);
|
||||||
|
|
||||||
if (!selectedSpell.empty())
|
if (!selectedSpell.empty())
|
||||||
{
|
{
|
||||||
const ESM::Spell* spell = getStore().get<ESM::Spell>().search(selectedSpell);
|
const ESM::Spell* spell = getStore().get<ESM::Spell>().search(selectedSpell);
|
||||||
|
|
||||||
// Reduce fatigue (note that in the vanilla game, both GMSTs are 0, and there's no fatigue loss)
|
cast.cast(spell);
|
||||||
static const float fFatigueSpellBase = getStore().get<ESM::GameSetting>().find("fFatigueSpellBase")->getFloat();
|
|
||||||
static const float fFatigueSpellMult = getStore().get<ESM::GameSetting>().find("fFatigueSpellMult")->getFloat();
|
|
||||||
MWMechanics::DynamicStat<float> fatigue = stats.getFatigue();
|
|
||||||
const float normalizedEncumbrance = actor.getClass().getEncumbrance(actor) / actor.getClass().getCapacity(actor);
|
|
||||||
float fatigueLoss = spell->mData.mCost * (fFatigueSpellBase + normalizedEncumbrance * fFatigueSpellMult);
|
|
||||||
fatigue.setCurrent(std::max(0.f, fatigue.getCurrent() - fatigueLoss));
|
|
||||||
stats.setFatigue(fatigue);
|
|
||||||
|
|
||||||
// Check mana
|
|
||||||
bool fail = false;
|
|
||||||
MWMechanics::DynamicStat<float> magicka = stats.getMagicka();
|
|
||||||
if (magicka.getCurrent() < spell->mData.mCost)
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientSP}");
|
|
||||||
fail = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reduce mana
|
|
||||||
if (!fail)
|
|
||||||
{
|
|
||||||
magicka.setCurrent(magicka.getCurrent() - spell->mData.mCost);
|
|
||||||
stats.setMagicka(magicka);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a power, check if it was already used in last 24h
|
|
||||||
if (!fail && spell->mData.mType & ESM::Spell::ST_Power)
|
|
||||||
{
|
|
||||||
if (stats.canUsePower(selectedSpell))
|
|
||||||
stats.usePower(selectedSpell);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sPowerAlreadyUsed}");
|
|
||||||
fail = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check success
|
|
||||||
int successChance = MWMechanics::getSpellSuccessChance(selectedSpell, actor);
|
|
||||||
int roll = std::rand()/ (static_cast<double> (RAND_MAX) + 1) * 100; // [0, 99]
|
|
||||||
if (!fail && roll >= successChance)
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicSkillFail}");
|
|
||||||
fail = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fail)
|
|
||||||
{
|
|
||||||
// Failure sound
|
|
||||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter (spell->mEffects.mList.begin());
|
|
||||||
iter!=spell->mEffects.mList.end(); ++iter)
|
|
||||||
{
|
|
||||||
const ESM::MagicEffect *magicEffect = getStore().get<ESM::MagicEffect>().find (
|
|
||||||
iter->mEffectID);
|
|
||||||
|
|
||||||
static const std::string schools[] = {
|
|
||||||
"alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration"
|
|
||||||
};
|
|
||||||
|
|
||||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
|
||||||
sndMgr->playSound3D(actor, "Spell Failure " + schools[magicEffect->mData.mSchool], 1.0f, 1.0f);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actor == getPlayer().getPlayer() && spell->mData.mType == ESM::Spell::ST_Spell)
|
|
||||||
actor.getClass().skillUsageSucceeded(actor,
|
|
||||||
MWMechanics::spellSchoolToSkill(MWMechanics::getSpellSchool(selectedSpell, actor)), 0);
|
|
||||||
|
|
||||||
effects = spell->mEffects;
|
|
||||||
}
|
}
|
||||||
InventoryStore& inv = actor.getClass().getInventoryStore(actor);
|
else if (inv.getSelectedEnchantItem() != inv.end())
|
||||||
if (selectedSpell.empty() && inv.getSelectedEnchantItem() != inv.end())
|
|
||||||
{
|
{
|
||||||
MWWorld::Ptr item = *inv.getSelectedEnchantItem();
|
cast.cast(*inv.getSelectedEnchantItem());
|
||||||
selectedSpell = item.getClass().getEnchantment(item);
|
|
||||||
const ESM::Enchantment* enchantment = getStore().get<ESM::Enchantment>().search (selectedSpell);
|
|
||||||
|
|
||||||
if (enchantment->mData.mType == ESM::Enchantment::WhenUsed)
|
|
||||||
{
|
|
||||||
// Check if there's enough charge left
|
|
||||||
const float enchantCost = enchantment->mData.mCost;
|
|
||||||
MWMechanics::NpcStats &stats = MWWorld::Class::get(actor).getNpcStats(actor);
|
|
||||||
int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified();
|
|
||||||
const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10);
|
|
||||||
|
|
||||||
if (item.getCellRef().mEnchantmentCharge == -1)
|
|
||||||
item.getCellRef().mEnchantmentCharge = enchantment->mData.mCharge;
|
|
||||||
|
|
||||||
if (item.getCellRef().mEnchantmentCharge < castCost)
|
|
||||||
{
|
|
||||||
MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInsufficientCharge}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reduce charge
|
|
||||||
item.getCellRef().mEnchantmentCharge -= castCost;
|
|
||||||
}
|
|
||||||
if (enchantment->mData.mType == ESM::Enchantment::CastOnce)
|
|
||||||
{
|
|
||||||
if (!item.getContainerStore()->remove(item, 1, actor))
|
|
||||||
{
|
|
||||||
// Item was used up
|
|
||||||
MWBase::Environment::get().getWindowManager()->unsetSelectedSpell();
|
|
||||||
inv.setSelectedEnchantItem(inv.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(item); // Set again to show the modified charge
|
|
||||||
|
|
||||||
sourceName = item.getClass().getName(item);
|
|
||||||
|
|
||||||
effects = enchantment->mEffects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now apply the spell!
|
|
||||||
|
|
||||||
// Apply Self portion
|
|
||||||
actor.getClass().getCreatureStats(actor).getActiveSpells().addSpell(selectedSpell, actor, actor, ESM::RT_Self, sourceName);
|
|
||||||
|
|
||||||
// Apply Touch portion
|
|
||||||
// TODO: Distance is probably incorrect, and should it be hardcoded?
|
|
||||||
std::pair<MWWorld::Ptr, Ogre::Vector3> contact = getHitContact(actor, 100);
|
|
||||||
if (!contact.first.isEmpty())
|
|
||||||
{
|
|
||||||
if (contact.first.getClass().isActor())
|
|
||||||
{
|
|
||||||
if (!contact.first.getClass().getCreatureStats(contact.first).isDead())
|
|
||||||
contact.first.getClass().getCreatureStats(contact.first).getActiveSpells().addSpell(selectedSpell, contact.first, actor, ESM::RT_Touch, sourceName);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// We hit a non-actor, e.g. a door. Only instant effects are relevant.
|
|
||||||
// inflictSpellOnNonActor(contact.first, selectedSpell, ESM::RT_Touch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
launchProjectile(selectedSpell, effects, actor, sourceName);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::launchProjectile (const std::string& id, const ESM::EffectList& effects,
|
void World::launchProjectile (const std::string& id, const ESM::EffectList& effects,
|
||||||
|
|
Loading…
Reference in a new issue