Commit 0dc2e829 authored by scrawl's avatar scrawl

Lots of cleanup. Implemented Absorb and Resist. Implemented several instant...

Lots of cleanup. Implemented Absorb and Resist. Implemented several instant effects. Added hand VFX.
parent 7fd5f1df
......@@ -114,6 +114,11 @@ namespace MWBase
/// references that are currently not in the scene should be ignored.
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/npcstats.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwmechanics/spellcasting.hpp"
#include "../mwworld/ptr.hpp"
#include "../mwworld/actiontalk.hpp"
......@@ -467,12 +468,9 @@ namespace MWClass
else
{
weapon.getCellRef().mEnchantmentCharge -= castCost;
// Touch
othercls.getCreatureStats(victim).getActiveSpells().addSpell(enchantmentName, victim, ptr, ESM::RT_Touch, weapon.getClass().getName(weapon));
// Self
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));
MWMechanics::CastSpell cast(ptr, victim);
cast.cast(weapon);
}
}
}
......@@ -932,11 +930,8 @@ namespace MWClass
bool Npc::apply (const MWWorld::Ptr& ptr, const std::string& id,
const MWWorld::Ptr& actor) const
{
MWMechanics::CreatureStats& stats = getCreatureStats (ptr);
/// \todo consider instant effects
return stats.getActiveSpells().addSpell (id, actor, actor);
MWMechanics::CastSpell cast(ptr, ptr);
return cast.cast(id);
}
void Npc::skillUsageSucceeded (const MWWorld::Ptr& ptr, int skill, int usageType) const
......
......@@ -21,17 +21,17 @@
namespace MWGui
{
void EffectSourceVisitor::visit (const ESM::ENAMstruct& enam,
void EffectSourceVisitor::visit (MWMechanics::EffectKey key,
const std::string& sourceName, float magnitude, float remainingTime)
{
MagicEffectInfo newEffectSource;
newEffectSource.mKey = MWMechanics::EffectKey(enam);
newEffectSource.mKey = key;
newEffectSource.mMagnitude = magnitude;
newEffectSource.mPermanent = mIsPermanent;
newEffectSource.mRemainingTime = remainingTime;
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;
virtual void visit (const ESM::ENAMstruct& enam,
virtual void visit (MWMechanics::EffectKey key,
const std::string& sourceName, float magnitude, float remainingTime = -1);
};
......
#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/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
{
......@@ -35,6 +11,7 @@ namespace MWMechanics
MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
// Erase no longer active spells
if (mLastUpdate!=now)
{
TContainer::iterator iter (mSpells.begin());
......@@ -42,7 +19,6 @@ namespace MWMechanics
if (!timeToExpire (iter))
{
mSpells.erase (iter++);
//onSpellExpired
rebuild = true;
}
else
......@@ -69,277 +45,28 @@ namespace MWMechanics
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;
int i = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIter (effects.first.mList.begin());
effectIter!=effects.first.mList.end(); ++effectIter, ++i)
const std::vector<Effect>& effects = iter->second.mEffects;
for (std::vector<Effect>::const_iterator effectIt = effects.begin(); effectIt != effects.end(); ++effectIt)
{
float random = iter->second.mRandom[i];
if (effectIter->mRange != iter->second.mRange)
continue;
int duration = effectIt->mDuration;
MWWorld::TimeStamp end = start;
end += static_cast<double> (duration)*
MWBase::Environment::get().getWorld()->getTimeScaleFactor()/(60*60);
if (effectIter->mDuration)
{
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);
}
}
if (end>now)
mEffects.add(effectIt->mKey, MWMechanics::EffectParam(effectIt->mMagnitude));
}
}
}
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()
: 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
{
update();
......@@ -348,37 +75,31 @@ namespace MWMechanics
ActiveSpells::TIterator ActiveSpells::begin() const
{
update();
return mSpells.begin();
}
ActiveSpells::TIterator ActiveSpells::end() const
{
update();
return mSpells.end();
}
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;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
iter!=effects.first.mList.end(); ++iter)
for (std::vector<Effect>::const_iterator iter (effects.begin());
iter!=effects.end(); ++iter)
{
if (iter->mDuration > duration)
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 *
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)
return 0;
......@@ -405,48 +126,67 @@ namespace MWMechanics
return mSpells;
}
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
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)
{
const ESM::EffectList& list = getEffectList(it->first).first;
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
{
for (TContainer::const_iterator it = begin(); it != end(); ++it)
{
float timeScale = MWBase::Environment::get().getWorld()->getTimeScaleFactor();
int i=0;
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = list.mList.begin();
effectIt != list.mList.end(); ++effectIt, ++i)
for (std::vector<Effect>::const_iterator effectIt = it->second.mEffects.begin();
effectIt != it->second.mEffects.end(); ++effectIt)
{
if (effectIt->mRange != it->second.mRange)
continue;
std::string name;
if (it->second.mName.empty())
name = getSpellDisplayName(it->first);
else
name = it->second.mName;
std::string name = it->second.mDisplayName;
float remainingTime = effectIt->mDuration +
(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;
if (magnitude)
visitor.visit(effectIt->mKey, name, magnitude, remainingTime);
}
}
}
magnitude = static_cast<int> (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost));
}
void ActiveSpells::purgeAll()
{
mSpells.clear();
}
magnitude *= it->second.mMultiplier[i];
if (magnitude)
visitor.visit(*effectIt, name, magnitude, remainingTime);
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>
namespace ESM
{
struct Spell;
struct EffectList;
}
namespace MWWorld
{
class Ptr;
}
namespace MWMechanics
{
struct ActiveSpellParams