#include "magiceffects.hpp" #include #include #include #include #include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/esmstore.hpp" namespace { // Round value to prevent precision issues void truncate(float& value) { value = static_cast(value * 1024.f) / 1024.f; } } namespace MWMechanics { EffectKey::EffectKey() : mId(0) { } EffectKey::EffectKey(const ESM::ENAMstruct& effect) { mId = effect.mEffectID; mArg = ESM::Skill::indexToRefId(effect.mSkill); ESM::RefId attribute = ESM::Attribute::indexToRefId(effect.mAttribute); if (!attribute.empty()) { if (!mArg.empty()) throw std::runtime_error("magic effect can't have both a skill and an attribute argument"); mArg = attribute; } } std::string EffectKey::toString() const { const auto& store = MWBase::Environment::get().getESMStore(); const ESM::MagicEffect* magicEffect = store->get().find(mId); return getMagicEffectString( *magicEffect, store->get().search(mArg), store->get().search(mArg)); } bool operator<(const EffectKey& left, const EffectKey& right) { if (left.mId < right.mId) return true; if (left.mId > right.mId) return false; return left.mArg < right.mArg; } bool operator==(const EffectKey& left, const EffectKey& right) { return left.mId == right.mId && left.mArg == right.mArg; } float EffectParam::getMagnitude() const { return mBase + mModifier; } void EffectParam::modifyBase(int diff) { mBase += diff; } int EffectParam::getBase() const { return mBase; } void EffectParam::setBase(int base) { mBase = base; } void EffectParam::setModifier(float mod) { mModifier = mod; } float EffectParam::getModifier() const { return mModifier; } EffectParam::EffectParam() : mModifier(0) , mBase(0) { } EffectParam& EffectParam::operator+=(const EffectParam& param) { mModifier += param.mModifier; mBase += param.mBase; truncate(mModifier); return *this; } EffectParam& EffectParam::operator-=(const EffectParam& param) { mModifier -= param.mModifier; mBase -= param.mBase; truncate(mModifier); return *this; } void MagicEffects::remove(const EffectKey& key) { mCollection.erase(key); } void MagicEffects::add(const EffectKey& key, const EffectParam& param) { Collection::iterator iter = mCollection.find(key); if (iter == mCollection.end()) { mCollection.insert(std::make_pair(key, param)); } else { iter->second += param; } } void MagicEffects::modifyBase(const EffectKey& key, int diff) { mCollection[key].modifyBase(diff); } void MagicEffects::setModifiers(const MagicEffects& effects) { for (Collection::iterator it = mCollection.begin(); it != mCollection.end(); ++it) { it->second.setModifier(effects.getOrDefault(it->first).getModifier()); } for (Collection::const_iterator it = effects.begin(); it != effects.end(); ++it) { mCollection[it->first].setModifier(it->second.getModifier()); } } EffectParam MagicEffects::getOrDefault(const EffectKey& key) const { return get(key).value_or(EffectParam()); } std::optional MagicEffects::get(const EffectKey& key) const { Collection::const_iterator iter = mCollection.find(key); if (iter != mCollection.end()) { return iter->second; } return std::nullopt; } MagicEffects MagicEffects::diff(const MagicEffects& prev, const MagicEffects& now) { MagicEffects result; // adding/changing for (Collection::const_iterator iter(now.begin()); iter != now.end(); ++iter) { Collection::const_iterator other = prev.mCollection.find(iter->first); if (other == prev.end()) { // adding result.add(iter->first, iter->second); } else { // changing result.add(iter->first, iter->second - other->second); } } // removing for (Collection::const_iterator iter(prev.begin()); iter != prev.end(); ++iter) { Collection::const_iterator other = now.mCollection.find(iter->first); if (other == now.end()) { result.add(iter->first, EffectParam() - iter->second); } } return result; } void MagicEffects::writeState(ESM::MagicEffects& state) const { for (const auto& [key, params] : mCollection) { if (params.getBase() != 0 || params.getModifier() != 0.f) { // Don't worry about mArg, never used by magic effect script instructions state.mEffects[key.mId] = { params.getBase(), params.getModifier() }; } } } void MagicEffects::readState(const ESM::MagicEffects& state) { for (const auto& [key, params] : state.mEffects) { mCollection[EffectKey(key)].setBase(params.first); mCollection[EffectKey(key)].setModifier(params.second); } } std::string getMagicEffectString( const ESM::MagicEffect& effect, const ESM::Attribute* attribute, const ESM::Skill* skill) { const bool targetsSkill = effect.mData.mFlags & ESM::MagicEffect::TargetSkill && skill; const bool targetsAttribute = effect.mData.mFlags & ESM::MagicEffect::TargetAttribute && attribute; std::string spellLine; auto windowManager = MWBase::Environment::get().getWindowManager(); if (targetsSkill || targetsAttribute) { switch (effect.mIndex) { case ESM::MagicEffect::AbsorbAttribute: case ESM::MagicEffect::AbsorbSkill: spellLine = windowManager->getGameSettingString("sAbsorb", {}); break; case ESM::MagicEffect::DamageAttribute: case ESM::MagicEffect::DamageSkill: spellLine = windowManager->getGameSettingString("sDamage", {}); break; case ESM::MagicEffect::DrainAttribute: case ESM::MagicEffect::DrainSkill: spellLine = windowManager->getGameSettingString("sDrain", {}); break; case ESM::MagicEffect::FortifyAttribute: case ESM::MagicEffect::FortifySkill: spellLine = windowManager->getGameSettingString("sFortify", {}); break; case ESM::MagicEffect::RestoreAttribute: case ESM::MagicEffect::RestoreSkill: spellLine = windowManager->getGameSettingString("sRestore", {}); break; } } if (spellLine.empty()) { auto& effectIDStr = ESM::MagicEffect::indexToGmstString(effect.mIndex); spellLine = windowManager->getGameSettingString(effectIDStr, {}); } if (targetsSkill) { spellLine += ' '; spellLine += skill->mName; } else if (targetsAttribute) { spellLine += ' '; spellLine += attribute->mName; } return spellLine; } }