1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-15 18:19:55 +00:00
openmw-tes3mp/apps/openmw/mwmechanics/activespells.cpp

422 lines
15 KiB
C++

#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 "../mwrender/animation.hpp"
#include "../mwworld/class.hpp"
#include "creaturestats.hpp"
#include "npcstats.hpp"
namespace MWMechanics
{
void ActiveSpells::update() const
{
bool rebuild = false;
MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
if (mLastUpdate!=now)
{
TContainer::iterator iter (mSpells.begin());
while (iter!=mSpells.end())
if (!timeToExpire (iter))
{
mSpells.erase (iter++);
//onSpellExpired
rebuild = true;
}
else
++iter;
mLastUpdate = now;
}
if (mSpellsChanged)
{
mSpellsChanged = false;
rebuild = true;
}
if (rebuild)
rebuildEffects();
}
void ActiveSpells::rebuildEffects() const
{
MWWorld::TimeStamp now = MWBase::Environment::get().getWorld()->getTimeStamp();
mEffects = MagicEffects();
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)
{
float magnitude = iter->second.mRandom[i];
if (effectIter->mRange != iter->second.mRange)
continue;
if (effectIter->mDuration)
{
int duration = effectIter->mDuration;
if (effects.second.first)
duration *= magnitude;
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> (magnitude / (0.1 * magicEffect->mData.mBaseCost));
}
else
{
param.mMagnitude =
static_cast<int> (0.05*magnitude / (0.1 * magicEffect->mData.mBaseCost));
}
}
else
param.mMagnitude = static_cast<int> (
(effectIter->mMagnMax-effectIter->mMagnMin)*magnitude + effectIter->mMagnMin);
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()
: mSpellsChanged (false), mLastUpdate (MWBase::Environment::get().getWorld()->getTimeStamp())
{}
bool ActiveSpells::addSpell (const std::string& id, const MWWorld::Ptr& actor, 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->mDuration)
{
found = true;
break;
}
}
if (!found)
return false;
TContainer::iterator iter = mSpells.find (id);
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;
}
ActiveSpellParams params;
for (unsigned int i=0; i<effects.first.mList.size(); ++i)
params.mRandom.push_back(static_cast<float> (std::rand()) / RAND_MAX);
params.mRange = range;
params.mTimeStamp = MWBase::Environment::get().getWorld()->getTimeStamp();
params.mName = name;
if (iter==mSpells.end() || stacks)
mSpells.insert (std::make_pair (id, params));
else
iter->second = params;
/*
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;
}
*/
// Play sounds & particles
bool first=true;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
iter!=effects.first.mList.end(); ++iter)
{
if (iter->mRange != range)
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.
const ESM::MagicEffect *magicEffect =
MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find (
iter->mEffectID);
// 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;
}
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();
return mEffects;
}
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);
int duration = 0;
for (std::vector<ESM::ENAMstruct>::const_iterator iter (effects.first.mList.begin());
iter!=effects.first.mList.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;
if (usedUp>=scaledDuration)
return 0;
return scaledDuration-usedUp;
}
bool ActiveSpells::isSpellActive(std::string id) const
{
Misc::StringUtils::toLower(id);
for (TContainer::iterator iter = mSpells.begin(); iter != mSpells.end(); ++iter)
{
std::string left = iter->first;
Misc::StringUtils::toLower(left);
if (iter->first == id)
return true;
}
return false;
}
const ActiveSpells::TContainer& ActiveSpells::getActiveSpells() const
{
return mSpells;
}
void ActiveSpells::visitEffectSources(EffectSourceVisitor &visitor) const
{
for (TContainer::const_iterator it = begin(); it != end(); ++it)
{
const ESM::EffectList& list = getEffectList(it->first).first;
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)
{
if (effectIt->mRange != it->second.mRange)
continue;
std::string name;
if (it->second.mName.empty())
name = getSpellDisplayName(it->first);
else
name = it->second.mName;
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];
// 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));
}
visitor.visit(*effectIt, name, magnitude, remainingTime);
}
}
}
}