#include "activespells.hpp" #include #include #include #include #include #include #include #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 { 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 > effects = getEffectList (iter->first); const MWWorld::TimeStamp& start = iter->second.mTimeStamp; int i = 0; for (std::vector::const_iterator effectIter (effects.first.mList.begin()); effectIter!=effects.first.mList.end(); ++effectIter, ++i) { float random = iter->second.mRandom[i]; if (effectIter->mRange != iter->second.mRange) continue; if (effectIter->mDuration) { int duration = effectIter->mDuration; if (effects.second.first) duration *= random; MWWorld::TimeStamp end = start; end += static_cast (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().find ( effectIter->mEffectID); if (effectIter->mDuration==0) { param.mMagnitude = static_cast (random / (0.1 * magicEffect->mData.mBaseCost)); } else { param.mMagnitude = static_cast (0.05*random / (0.1 * magicEffect->mData.mBaseCost)); } } else param.mMagnitude = static_cast ( (effectIter->mMagnMax-effectIter->mMagnMin)*random + effectIter->mMagnMin); param.mMagnitude *= iter->second.mMultiplier[i]; if (param.mMagnitude) mEffects.add (*effectIter, param); } } } } } std::pair > ActiveSpells::getEffectList (const std::string& id) const { if (const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return std::make_pair (enchantment->mEffects, std::make_pair(false, false)); if (const ESM::Spell *spell = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return std::make_pair (spell->mEffects, std::make_pair(false, false)); if (const ESM::Potion *potion = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return std::make_pair (potion->mEffects, std::make_pair(false, true)); if (const ESM::Ingredient *ingredient = MWBase::Environment::get().getWorld()->getStore().get().search (id)) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().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 > 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().search (id)) return spell->mName; if (const ESM::Potion *potion = MWBase::Environment::get().getWorld()->getStore().get().search (id)) return potion->mName; if (const ESM::Ingredient *ingredient = MWBase::Environment::get().getWorld()->getStore().get().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, const MWWorld::Ptr& caster, ESM::RangeType range, const std::string& name, int effectIndex) { const CreatureStats& creatureStats = MWWorld::Class::get (actor).getCreatureStats (actor); std::pair > effects = getEffectList (id); bool stacks = effects.second.second; bool found = false; for (std::vector::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 (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; imRange != 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 (RAND_MAX) + 1) * 100; // [0, 99] if (roll < reflect) disabled = true; } */ bool first=true; int i = 0; for (std::vector::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().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().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().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(); 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 > effects = getEffectList (iterator->first); int duration = 0; for (std::vector::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::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().search (it->first)) { const ESM::MagicEffect *magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find ( effectIt->mEffectID); remainingTime = effectIt->mDuration * it->second.mRandom[i] + (it->second.mTimeStamp - MWBase::Environment::get().getWorld()->getTimeStamp())*3600/timeScale; magnitude = static_cast (0.05*it->second.mRandom[i] / (0.1 * magicEffect->mData.mBaseCost)); } magnitude *= it->second.mMultiplier[i]; if (magnitude) visitor.visit(*effectIt, name, magnitude, remainingTime); } } } }