diff --git a/apps/components_tests/esm3/testsaveload.cpp b/apps/components_tests/esm3/testsaveload.cpp index 0aac55d0fd..9d9a549ef3 100644 --- a/apps/components_tests/esm3/testsaveload.cpp +++ b/apps/components_tests/esm3/testsaveload.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -574,7 +575,7 @@ namespace ESM { EffectList record; record.mList.emplace_back(IndexedENAMstruct{ { - .mEffectID = 1, + .mEffectID = ESM::MagicEffect::SwiftSwim, .mSkill = 2, .mAttribute = 3, .mRange = 4, diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 48d18a235a..5832339dd8 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -153,8 +153,10 @@ namespace int i = 0; for (const ESM::IndexedENAMstruct& effect : effects.mList) { - std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mData.mEffectID) << " (" - << effect.mData.mEffectID << ")" << std::endl; + int effectIdx = ESM::MagicEffect::refIdToIndex(effect.mData.mEffectID); + if (effectIdx != -1) + std::cout << " Effect[" << i << "]: " << magicEffectLabel(effectIdx) << " (" << effectIdx << ")" + << std::endl; if (effect.mData.mSkill != -1) std::cout << " Skill: " << skillLabel(effect.mData.mSkill) << " (" << (int)effect.mData.mSkill << ")" << std::endl; @@ -843,11 +845,12 @@ namespace EsmTool std::cout << " Value: " << mData.mData.mValue << std::endl; for (int i = 0; i != 4; i++) { - // A value of -1 means no effect - if (mData.mData.mEffectID[i] == -1) + // A value of EmptyRefId means no effect + if (mData.mData.mEffectID[i].empty()) continue; - std::cout << " Effect: " << magicEffectLabel(mData.mData.mEffectID[i]) << " (" << mData.mData.mEffectID[i] - << ")" << std::endl; + + int effectIdx = ESM::MagicEffect::refIdToIndex(mData.mData.mEffectID[i]); + std::cout << " Effect: " << magicEffectLabel(effectIdx) << " (" << effectIdx << ")" << std::endl; std::cout << " Skill: " << skillLabel(mData.mData.mSkills[i]) << " (" << mData.mData.mSkills[i] << ")" << std::endl; std::cout << " Attribute: " << attributeLabel(mData.mData.mAttributes[i]) << " (" @@ -973,7 +976,8 @@ namespace EsmTool template <> void Record::print() { - std::cout << " Index: " << magicEffectLabel(mData.mIndex) << " (" << mData.mIndex << ")" << std::endl; + int effectIdx = ESM::MagicEffect::refIdToIndex(mData.mId); + std::cout << " Index: " << magicEffectLabel(effectIdx) << " (" << effectIdx << ")" << std::endl; std::cout << " Description: " << mData.mDescription << std::endl; std::cout << " Icon: " << mData.mIcon << std::endl; std::cout << " Flags: " << magicEffectFlags(mData.mData.mFlags) << std::endl; diff --git a/apps/opencs/model/doc/document.cpp b/apps/opencs/model/doc/document.cpp index 536a5c0d2d..327ef4e144 100644 --- a/apps/opencs/model/doc/document.cpp +++ b/apps/opencs/model/doc/document.cpp @@ -128,11 +128,18 @@ void CSMDoc::Document::addOptionalGlobals() void CSMDoc::Document::addOptionalMagicEffects() { - for (int i = ESM::MagicEffect::SummonFabricant; i <= ESM::MagicEffect::SummonCreature05; ++i) + static const std::array optionalMagicEffects{ + ESM::MagicEffect::SummonFabricant, + ESM::MagicEffect::SummonWolf, + ESM::MagicEffect::SummonBear, + ESM::MagicEffect::SummonBonewolf, + ESM::MagicEffect::SummonCreature04, + ESM::MagicEffect::SummonCreature05, + }; + for (const auto effectId : optionalMagicEffects) { ESM::MagicEffect effect; - effect.mIndex = i; - effect.mId = ESM::MagicEffect::indexToRefId(i); + effect.mId = effectId; effect.blank(); addOptionalMagicEffect(effect); @@ -282,10 +289,7 @@ void CSMDoc::Document::createBase() for (int i = 0; i < ESM::MagicEffect::Length; ++i) { ESM::MagicEffect record; - - record.mIndex = i; record.mId = ESM::MagicEffect::indexToRefId(i); - record.blank(); getData().getMagicEffects().add(record); diff --git a/apps/opencs/model/tools/effectlistcheck.cpp b/apps/opencs/model/tools/effectlistcheck.cpp index b8695bc419..461950e457 100644 --- a/apps/opencs/model/tools/effectlistcheck.cpp +++ b/apps/opencs/model/tools/effectlistcheck.cpp @@ -26,7 +26,8 @@ namespace CSMTools const std::string number = std::to_string(i); // At the time of writing this effects, attributes and skills are mostly hardcoded - if (effect.mData.mEffectID < 0 || effect.mData.mEffectID >= ESM::MagicEffect::Length) + int effectIndex = ESM::MagicEffect::refIdToIndex(effect.mData.mEffectID); + if (effectIndex < -1 || effectIndex >= ESM::MagicEffect::Length) messages.add(id, "Effect #" + number + ": invalid effect ID", "", CSMDoc::Message::Severity_Error); if (effect.mData.mSkill < -1 || effect.mData.mSkill >= ESM::Skill::Length) messages.add(id, "Effect #" + number + ": invalid skill", "", CSMDoc::Message::Severity_Error); @@ -68,13 +69,14 @@ namespace CSMTools for (size_t i = 0; i < 4; i++) { - if (ingredient.mData.mEffectID[i] == -1) + if (ingredient.mData.mEffectID[i].empty()) continue; hasEffects = true; + int effectIndex = ESM::MagicEffect::refIdToIndex(ingredient.mData.mEffectID[i]); const std::string number = std::to_string(i + 1); - if (ingredient.mData.mEffectID[i] < -1 || ingredient.mData.mEffectID[i] >= ESM::MagicEffect::Length) + if (effectIndex < -1 || effectIndex >= ESM::MagicEffect::Length) messages.add(id, "Effect #" + number + ": invalid effect ID", "", CSMDoc::Message::Severity_Error); if (ingredient.mData.mSkills[i] < -1 || ingredient.mData.mSkills[i] >= ESM::Skill::Length) messages.add(id, "Effect #" + number + ": invalid skill", "", CSMDoc::Message::Severity_Error); diff --git a/apps/opencs/model/world/collection.hpp b/apps/opencs/model/world/collection.hpp index 9d3658cdf0..227c36803b 100644 --- a/apps/opencs/model/world/collection.hpp +++ b/apps/opencs/model/world/collection.hpp @@ -73,17 +73,6 @@ namespace CSMWorld return ESM::RefId::stringRefId(Land::createUniqueRecordId(record.mX, record.mY)); } - inline ESM::RefId getRecordId(const ESM::MagicEffect& record) - { - return ESM::RefId::stringRefId(CSMWorld::getStringId(record.mId)); - } - - inline void setRecordId(const ESM::RefId& id, ESM::MagicEffect& record) - { - int index = ESM::MagicEffect::indexNameToIndex(id.getRefIdString()); - record.mId = ESM::RefId::index(ESM::REC_MGEF, static_cast(index)); - } - inline void setRecordId(const ESM::RefId& id, ESM::Skill& record) { if (const auto* skillId = id.getIf()) diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index baf2e5d8e3..f49b2bab08 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -25,18 +25,7 @@ namespace CSMWorld std::string operator()(ESM::FormId value) const { return value.toString("FormId:"); } - std::string operator()(ESM::IndexRefId value) const - { - switch (value.getRecordType()) - { - case ESM::REC_MGEF: - return std::string(ESM::MagicEffect::sIndexNames[value.getValue()]); - default: - break; - } - - return value.toDebugString(); - } + std::string operator()(ESM::IndexRefId value) const { return value.toDebugString(); } template std::string operator()(const T& value) const diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index f5c5501889..0361187846 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -260,7 +260,7 @@ namespace CSMWorld // blank row ESM::IndexedENAMstruct effect; effect.mIndex = position; - effect.mData.mEffectID = 0; + effect.mData.mEffectID = ESM::MagicEffect::WaterBreathing; effect.mData.mSkill = -1; effect.mData.mAttribute = -1; effect.mData.mRange = 0; @@ -319,34 +319,28 @@ namespace CSMWorld switch (subColIndex) { case 0: - return effect.mEffectID; + return ESM::MagicEffect::refIdToIndex(effect.mEffectID); case 1: { - switch (effect.mEffectID) - { - case ESM::MagicEffect::DrainSkill: - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::RestoreSkill: - case ESM::MagicEffect::FortifySkill: - case ESM::MagicEffect::AbsorbSkill: - return effect.mSkill; - default: - return QVariant(); - } + if (effect.mEffectID == ESM::MagicEffect::DrainSkill + || effect.mEffectID == ESM::MagicEffect::DamageSkill + || effect.mEffectID == ESM::MagicEffect::RestoreSkill + || effect.mEffectID == ESM::MagicEffect::FortifySkill + || effect.mEffectID == ESM::MagicEffect::AbsorbSkill) + return effect.mSkill; + else + return QVariant(); } case 2: { - switch (effect.mEffectID) - { - case ESM::MagicEffect::DrainAttribute: - case ESM::MagicEffect::DamageAttribute: - case ESM::MagicEffect::RestoreAttribute: - case ESM::MagicEffect::FortifyAttribute: - case ESM::MagicEffect::AbsorbAttribute: - return effect.mAttribute; - default: - return QVariant(); - } + if (effect.mEffectID == ESM::MagicEffect::DrainAttribute + || effect.mEffectID == ESM::MagicEffect::DamageAttribute + || effect.mEffectID == ESM::MagicEffect::RestoreAttribute + || effect.mEffectID == ESM::MagicEffect::FortifyAttribute + || effect.mEffectID == ESM::MagicEffect::AbsorbAttribute) + return effect.mAttribute; + else + return QVariant(); } case 3: return effect.mRange; @@ -377,26 +371,23 @@ namespace CSMWorld { case 0: { - effect.mEffectID = static_cast(value.toInt()); - switch (effect.mEffectID) + effect.mEffectID = ESM::MagicEffect::indexToRefId(value.toInt()); + if (effect.mEffectID == ESM::MagicEffect::DrainSkill + || effect.mEffectID == ESM::MagicEffect::DamageSkill + || effect.mEffectID == ESM::MagicEffect::RestoreSkill + || effect.mEffectID == ESM::MagicEffect::FortifySkill + || effect.mEffectID == ESM::MagicEffect::AbsorbSkill) + effect.mAttribute = -1; + else if (effect.mEffectID == ESM::MagicEffect::DrainAttribute + || effect.mEffectID == ESM::MagicEffect::DamageAttribute + || effect.mEffectID == ESM::MagicEffect::RestoreAttribute + || effect.mEffectID == ESM::MagicEffect::FortifyAttribute + || effect.mEffectID == ESM::MagicEffect::AbsorbAttribute) + effect.mSkill = -1; + else { - case ESM::MagicEffect::DrainSkill: - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::RestoreSkill: - case ESM::MagicEffect::FortifySkill: - case ESM::MagicEffect::AbsorbSkill: - effect.mAttribute = -1; - break; - case ESM::MagicEffect::DrainAttribute: - case ESM::MagicEffect::DamageAttribute: - case ESM::MagicEffect::RestoreAttribute: - case ESM::MagicEffect::FortifyAttribute: - case ESM::MagicEffect::AbsorbAttribute: - effect.mSkill = -1; - break; - default: - effect.mSkill = -1; - effect.mAttribute = -1; + effect.mSkill = -1; + effect.mAttribute = -1; } break; } diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index a6ea56a56d..87093934b3 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -144,37 +144,29 @@ QVariant CSMWorld::IngredEffectRefIdAdapter::getNestedData( if (subRowIndex < 0 || subRowIndex >= 4) throw std::runtime_error("index out of range"); + ESM::RefId effectId = record.get().mData.mEffectID[subRowIndex]; + switch (subColIndex) { case 0: - return record.get().mData.mEffectID[subRowIndex]; + return ESM::MagicEffect::refIdToIndex(effectId); case 1: { - switch (record.get().mData.mEffectID[subRowIndex]) - { - case ESM::MagicEffect::DrainSkill: - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::RestoreSkill: - case ESM::MagicEffect::FortifySkill: - case ESM::MagicEffect::AbsorbSkill: - return record.get().mData.mSkills[subRowIndex]; - default: - return QVariant(); - } + if (effectId == ESM::MagicEffect::DrainSkill || effectId == ESM::MagicEffect::DamageSkill + || effectId == ESM::MagicEffect::RestoreSkill || effectId == ESM::MagicEffect::FortifySkill + || effectId == ESM::MagicEffect::AbsorbSkill) + return record.get().mData.mSkills[subRowIndex]; + else + return QVariant(); } case 2: { - switch (record.get().mData.mEffectID[subRowIndex]) - { - case ESM::MagicEffect::DrainAttribute: - case ESM::MagicEffect::DamageAttribute: - case ESM::MagicEffect::RestoreAttribute: - case ESM::MagicEffect::FortifyAttribute: - case ESM::MagicEffect::AbsorbAttribute: - return record.get().mData.mAttributes[subRowIndex]; - default: - return QVariant(); - } + if (effectId == ESM::MagicEffect::DrainAttribute || effectId == ESM::MagicEffect::DamageAttribute + || effectId == ESM::MagicEffect::RestoreAttribute || effectId == ESM::MagicEffect::FortifyAttribute + || effectId == ESM::MagicEffect::AbsorbAttribute) + return record.get().mData.mAttributes[subRowIndex]; + else + return QVariant(); } default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); @@ -191,29 +183,24 @@ void CSMWorld::IngredEffectRefIdAdapter::setNestedData( if (subRowIndex < 0 || subRowIndex >= 4) throw std::runtime_error("index out of range"); + ESM::RefId effectId = ESM::MagicEffect::indexToRefId(value.toInt()); + switch (subColIndex) { case 0: - ingredient.mData.mEffectID[subRowIndex] = value.toInt(); - switch (ingredient.mData.mEffectID[subRowIndex]) + ingredient.mData.mEffectID[subRowIndex] = effectId; + if (effectId == ESM::MagicEffect::DrainSkill || effectId == ESM::MagicEffect::DamageSkill + || effectId == ESM::MagicEffect::RestoreSkill || effectId == ESM::MagicEffect::FortifySkill + || effectId == ESM::MagicEffect::AbsorbSkill) + ingredient.mData.mAttributes[subRowIndex] = -1; + else if (effectId == ESM::MagicEffect::DrainAttribute || effectId == ESM::MagicEffect::DamageAttribute + || effectId == ESM::MagicEffect::RestoreAttribute || effectId == ESM::MagicEffect::FortifyAttribute + || effectId == ESM::MagicEffect::AbsorbAttribute) + ingredient.mData.mSkills[subRowIndex] = -1; + else { - case ESM::MagicEffect::DrainSkill: - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::RestoreSkill: - case ESM::MagicEffect::FortifySkill: - case ESM::MagicEffect::AbsorbSkill: - ingredient.mData.mAttributes[subRowIndex] = -1; - break; - case ESM::MagicEffect::DrainAttribute: - case ESM::MagicEffect::DamageAttribute: - case ESM::MagicEffect::RestoreAttribute: - case ESM::MagicEffect::FortifyAttribute: - case ESM::MagicEffect::AbsorbAttribute: - ingredient.mData.mSkills[subRowIndex] = -1; - break; - default: - ingredient.mData.mSkills[subRowIndex] = -1; - ingredient.mData.mAttributes[subRowIndex] = -1; + ingredient.mData.mSkills[subRowIndex] = -1; + ingredient.mData.mAttributes[subRowIndex] = -1; } break; case 1: diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 235d3b14b7..071dc05f54 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -71,7 +71,7 @@ namespace MWClass std::unique_ptr Ingredient::use(const MWWorld::Ptr& ptr, bool force) const { - if (ptr.get()->mBase->mData.mEffectID[0] < 0) + if (ptr.get()->mBase->mData.mEffectID[0].empty()) return std::make_unique(); std::unique_ptr action = std::make_unique(ptr); @@ -131,10 +131,10 @@ namespace MWClass MWGui::Widgets::SpellEffectList list; for (int i = 0; i < 4; ++i) { - if (ref->mBase->mData.mEffectID[i] < 0) + if (ref->mBase->mData.mEffectID[i].empty()) continue; MWGui::Widgets::SpellEffectParams params; - params.mEffectID = static_cast(ref->mBase->mData.mEffectID[i]); + params.mEffectID = ref->mBase->mData.mEffectID[i]; params.mAttribute = ESM::Attribute::indexToRefId(ref->mBase->mData.mAttributes[i]); params.mSkill = ESM::Skill::indexToRefId(ref->mBase->mData.mSkills[i]); params.mKnown = alchemySkill >= fWortChanceValue * (i + 1); diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index da7e1dff78..b1a23f811e 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -459,7 +459,7 @@ namespace MWGui for (const MWMechanics::EffectKey& effectKey : effectIds) { Widgets::SpellEffectParams params; - params.mEffectID = static_cast(effectKey.mId); + params.mEffectID = effectKey.mId; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().find(effectKey.mId); if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index c5ece8ebe0..69ed7132bd 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -1,5 +1,7 @@ #include "spellcreationdialog.hpp" +#include + #include #include #include @@ -33,20 +35,20 @@ namespace { - bool sortMagicEffects(short id1, short id2) + bool sortMagicEffects(ESM::RefId id1, ESM::RefId id2) { const MWWorld::Store& gmst = MWBase::Environment::get().getESMStore()->get(); - return gmst.find(ESM::MagicEffect::indexToGmstString(id1))->mValue.getString() - < gmst.find(ESM::MagicEffect::indexToGmstString(id2))->mValue.getString(); + return gmst.find(ESM::MagicEffect::refIdToGmstString(id1))->mValue.getString() + < gmst.find(ESM::MagicEffect::refIdToGmstString(id2))->mValue.getString(); } void init(ESM::ENAMstruct& effect) { effect.mArea = 0; effect.mDuration = 0; - effect.mEffectID = -1; + effect.mEffectID = ESM::RefId(); effect.mMagnMax = 0; effect.mMagnMin = 0; effect.mRange = 0; @@ -222,9 +224,9 @@ namespace MWGui mEffectImage->setImageTexture(Misc::ResourceHelpers::correctIconPath( VFS::Path::toNormalized(effect->mIcon), *MWBase::Environment::get().getResourceSystem()->getVFS())); - mEffectName->setCaptionWithReplacing("#{" + ESM::MagicEffect::indexToGmstString(effect->mIndex) + "}"); + mEffectName->setCaptionWithReplacing(std::format("#{{{}}}", ESM::MagicEffect::refIdToGmstString(effect->mId))); - mEffect.mEffectID = static_cast(effect->mIndex); + mEffect.mEffectID = effect->mId; mMagicEffect = effect; @@ -761,7 +763,7 @@ namespace MWGui , mUsedEffectsView(nullptr) , mAddEffectDialog() , mSelectedEffect(0) - , mSelectedKnownEffectId(0) + , mSelectedKnownEffectId(ESM::RefId()) , mConstantEffect(false) , mType(type) { @@ -782,7 +784,7 @@ namespace MWGui MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); MWMechanics::Spells& spells = stats.getSpells(); - std::vector knownEffects; + std::vector knownEffects; for (const ESM::Spell* spell : spells) { @@ -792,7 +794,7 @@ namespace MWGui for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList) { - int16_t effectId = effectInfo.mData.mEffectID; + ESM::RefId effectId = effectInfo.mData.mEffectID; const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find(effectId); @@ -812,12 +814,12 @@ namespace MWGui mAvailableEffectsList->clear(); int i = 0; - for (const short effectId : knownEffects) + for (const auto effectId : knownEffects) { mAvailableEffectsList->addItem(MWBase::Environment::get() .getESMStore() ->get() - .find(ESM::MagicEffect::indexToGmstString(effectId)) + .find(ESM::MagicEffect::refIdToGmstString(effectId)) ->mValue.getString()); mButtonMapping[i] = effectId; ++i; @@ -826,12 +828,12 @@ namespace MWGui mAvailableEffectsList->scrollToTop(); mAvailableButtons.clear(); - for (const short effectId : knownEffects) + for (const auto effectId : knownEffects) { const std::string& name = MWBase::Environment::get() .getESMStore() ->get() - .find(ESM::MagicEffect::indexToGmstString(effectId)) + .find(ESM::MagicEffect::refIdToGmstString(effectId)) ->mValue.getString(); MyGUI::Button* w = mAvailableEffectsList->getItemWidget(name); mAvailableButtons.emplace_back(w); diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index f3382817e7..a54be05312 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -113,7 +113,7 @@ namespace MWGui void setConstantEffect(bool constant); protected: - std::map mButtonMapping; // maps button ID to effect ID + std::map mButtonMapping; // maps button ID to effect ID Gui::MWList* mAvailableEffectsList; MyGUI::ScrollView* mUsedEffectsView; @@ -123,7 +123,7 @@ namespace MWGui std::unique_ptr mSelectSkillDialog; int mSelectedEffect; - short mSelectedKnownEffectId; + ESM::RefId mSelectedKnownEffectId; bool mConstantEffect; diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index fae68389fd..468d147994 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -1,5 +1,6 @@ #include "spellicons.hpp" +#include #include #include @@ -28,7 +29,7 @@ namespace MWGui MWWorld::Ptr player = MWMechanics::getPlayer(); const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - std::map> effects; + std::map> effects; for (const auto& params : stats.getActiveSpells()) { for (const auto& effect : params.getEffects()) @@ -154,10 +155,8 @@ namespace MWGui Misc::ResourceHelpers::correctIconPath(VFS::Path::toNormalized(effect->mIcon), *MWBase::Environment::get().getResourceSystem()->getVFS())); - const std::string& name = ESM::MagicEffect::indexToGmstString(effectId); - ToolTipInfo tooltipInfo; - tooltipInfo.caption = "#{" + name + "}"; + tooltipInfo.caption = std::format("#{{{}}}", ESM::MagicEffect::refIdToGmstString(effectId)); tooltipInfo.icon = effect->mIcon; tooltipInfo.imageSize = 16; tooltipInfo.wordWrap = false; diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index e53fa98284..e1d1d169d5 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -44,7 +44,7 @@ namespace MWGui void updateWidgets(MyGUI::Widget* parent, bool adjustSize); private: - std::map mWidgetMap; + std::map mWidgetMap; }; } diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index 5a732d771c..f390e7ff50 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -48,9 +48,9 @@ namespace MWGui for (const auto& effect : effects.mList) { - short effectId = effect.mData.mEffectID; + ESM::RefId effectId = effect.mData.mEffectID; - if (effectId != -1) + if (!effectId.empty()) { const ESM::MagicEffect* magicEffect = store.get().find(effectId); const ESM::Attribute* attribute diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 88eedae99c..4922aefe4d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -954,11 +954,10 @@ namespace MWGui widget->setUserString("ToolTipLayout", "ClassToolTip"); } - void ToolTips::createMagicEffectToolTip(MyGUI::Widget* widget, short id) + void ToolTips::createMagicEffectToolTip(MyGUI::Widget* widget, ESM::RefId effectId) { const auto& store = MWBase::Environment::get().getESMStore(); - const ESM::MagicEffect* effect = store->get().find(id); - const std::string& name = ESM::MagicEffect::indexToGmstString(id); + const ESM::MagicEffect* effect = store->get().find(effectId); std::string icon = effect->mIcon; icon.insert(icon.rfind('\\') + 1, "b_"); @@ -967,7 +966,8 @@ namespace MWGui widget->setUserString("ToolTipType", "Layout"); widget->setUserString("ToolTipLayout", "MagicEffectToolTip"); - widget->setUserString("Caption_MagicEffectName", "#{" + name + "}"); + widget->setUserString( + "Caption_MagicEffectName", std::format("#{{{}}}", ESM::MagicEffect::refIdToGmstString(effectId))); widget->setUserString("Caption_MagicEffectDescription", effect->mDescription); widget->setUserString("Caption_MagicEffectSchool", "#{sSchool}: " diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index 80a555733c..3665e6a38c 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -98,7 +98,7 @@ namespace MWGui static void createBirthsignToolTip(MyGUI::Widget* widget, const ESM::RefId& birthsignId); static void createRaceToolTip(MyGUI::Widget* widget, const ESM::Race* playerRace); static void createClassToolTip(MyGUI::Widget* widget, const ESM::Class& playerClass); - static void createMagicEffectToolTip(MyGUI::Widget* widget, short id); + static void createMagicEffectToolTip(MyGUI::Widget* widget, ESM::RefId effectId); bool checkOwned(); /// Returns True if taking mFocusObject would be crime diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index d5dc2abc50..b45eb531f1 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include namespace MyGUI @@ -40,7 +41,6 @@ namespace MWGui , mIsConstant(false) , mNoMagnitude(false) , mKnown(true) - , mEffectID(-1) , mMagnMin(-1) , mMagnMax(-1) , mRange(-1) @@ -55,10 +55,8 @@ namespace MWGui bool mKnown; // is this effect known to the player? (If not, will display as a question mark instead) - // value of -1 here means the effect is unknown to the player - short mEffectID; - - ESM::RefId mSkill, mAttribute; + // value of EmptyRefId here means the effect is unknown to the player + ESM::RefId mEffectID, mSkill, mAttribute; // value of -1 here means the value is unavailable int mMagnMin, mMagnMax, mRange, mDuration; @@ -71,16 +69,13 @@ namespace MWGui if (mEffectID != other.mEffectID) return false; - bool involvesAttribute = (mEffectID == 74 // restore attribute - || mEffectID == 85 // absorb attribute - || mEffectID == 17 // drain attribute - || mEffectID == 79 // fortify attribute - || mEffectID == 22); // damage attribute - bool involvesSkill = (mEffectID == 78 // restore skill - || mEffectID == 89 // absorb skill - || mEffectID == 21 // drain skill - || mEffectID == 83 // fortify skill - || mEffectID == 26); // damage skill + bool involvesAttribute = (mEffectID == ESM::MagicEffect::RestoreAttribute + || mEffectID == ESM::MagicEffect::AbsorbAttribute || mEffectID == ESM::MagicEffect::DrainAttribute + || mEffectID == ESM::MagicEffect::FortifyAttribute + || mEffectID == ESM::MagicEffect::DamageAttribute); + bool involvesSkill = (mEffectID == ESM::MagicEffect::RestoreSkill + || mEffectID == ESM::MagicEffect::AbsorbSkill || mEffectID == ESM::MagicEffect::DrainSkill + || mEffectID == ESM::MagicEffect::FortifySkill || mEffectID == ESM::MagicEffect::DamageSkill); return ((other.mSkill == mSkill) || !involvesSkill) && ((other.mAttribute == mAttribute) && !involvesAttribute) && (other.mArea == mArea); } diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 97cf4dfb89..add12b37ad 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -1,5 +1,7 @@ #include "magicbindings.hpp" +#include + #include #include #include @@ -245,9 +247,10 @@ namespace MWLua sol::table effect(state, sol::create); magicApi["EFFECT_TYPE"] = LuaUtil::makeStrictReadOnly(effect); - for (const auto& name : ESM::MagicEffect::sIndexNames) + for (int i = 0; i < ESM::MagicEffect::Length; ++i) { - effect[name] = Misc::StringUtils::lowerCase(name); + std::string_view name = ESM::MagicEffect::indexToName(i); + effect[name] = ESM::MagicEffect::indexToRefId(i).serializeText(); } // Spell store @@ -262,43 +265,9 @@ namespace MWLua // MagicEffect store sol::table magicEffects(state, sol::create); + addRecordFunctionBinding(magicEffects, context); magicApi["effects"] = LuaUtil::makeReadOnly(magicEffects); - using MagicEffectStore = MWWorld::Store; - const MagicEffectStore* magicEffectStore - = &MWBase::Environment::get().getWorld()->getStore().get(); - auto magicEffectStoreT = state.new_usertype("ESM3_MagicEffectStore"); - magicEffectStoreT[sol::meta_function::to_string] = [](const MagicEffectStore& store) { - return "ESM3_MagicEffectStore{" + std::to_string(store.getSize()) + " effects}"; - }; - magicEffectStoreT[sol::meta_function::index] = sol::overload( - [](const MagicEffectStore& store, int id) -> const ESM::MagicEffect* { return store.search(id); }, - [](const MagicEffectStore& store, std::string_view id) -> const ESM::MagicEffect* { - int index = ESM::MagicEffect::indexNameToIndex(id); - return store.search(index); - }); - auto magicEffectsIter = [magicEffectStore](sol::this_state thisState, const sol::object& /*store*/, - sol::optional id) -> std::tuple { - MagicEffectStore::iterator iter; - if (id.has_value()) - { - iter = magicEffectStore->findIter(*id); - if (iter != magicEffectStore->end()) - iter++; - } - else - iter = magicEffectStore->begin(); - if (iter != magicEffectStore->end()) - return std::make_tuple( - sol::make_object(thisState, iter->first), sol::make_object(thisState, &iter->second)); - else - return std::make_tuple(sol::nil, sol::nil); - }; - magicEffectStoreT[sol::meta_function::pairs] - = [iter = sol::make_object(state, magicEffectsIter)] { return iter; }; - magicEffectStoreT[sol::meta_function::ipairs] - = [iter = sol::make_object(state, magicEffectsIter)] { return iter; }; - - magicEffects["records"] = magicEffectStore; + const auto* magicEffectStore = &MWBase::Environment::get().getWorld()->getStore().get(); // Spell record auto spellT = state.new_usertype("ESM3_Spell"); @@ -345,16 +314,14 @@ namespace MWLua auto effectParamsT = state.new_usertype("ESM3_EffectParams"); effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::IndexedENAMstruct& params) { const ESM::MagicEffect* const rec = magicEffectStore->find(params.mData.mEffectID); - return "ESM3_EffectParams[" + ESM::MagicEffect::indexToGmstString(rec->mIndex) + "]"; + return std::format("ESM3_EffectParams[{}]", ESM::MagicEffect::refIdToGmstString(rec->mId)); }; effectParamsT["effect"] = sol::readonly_property( [magicEffectStore](const ESM::IndexedENAMstruct& params) -> const ESM::MagicEffect* { return magicEffectStore->find(params.mData.mEffectID); }); - effectParamsT["id"] = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> std::string { - auto name = ESM::MagicEffect::indexToName(params.mData.mEffectID); - return Misc::StringUtils::lowerCase(name); - }); + effectParamsT["id"] = sol::readonly_property( + [](const ESM::IndexedENAMstruct& params) -> ESM::RefId { return params.mData.mEffectID; }); effectParamsT["affectedSkill"] = sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> sol::optional { ESM::RefId id = ESM::Skill::indexToRefId(params.mData.mSkill); @@ -386,12 +353,9 @@ namespace MWLua auto magicEffectT = state.new_usertype("ESM3_MagicEffect"); magicEffectT[sol::meta_function::to_string] = [](const ESM::MagicEffect& rec) { - return "ESM3_MagicEffect[" + ESM::MagicEffect::indexToGmstString(rec.mIndex) + "]"; + return std::format("ESM3_MagicEffect[{}]", ESM::MagicEffect::refIdToGmstString(rec.mId)); }; - magicEffectT["id"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string { - auto name = ESM::MagicEffect::indexToName(rec.mIndex); - return Misc::StringUtils::lowerCase(name); - }); + magicEffectT["id"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> ESM::RefId { return rec.mId; }); magicEffectT["icon"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string { auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); return Misc::ResourceHelpers::correctIconPath(VFS::Path::toNormalized(rec.mIcon), *vfs); @@ -422,7 +386,7 @@ namespace MWLua .getWorld() ->getStore() .get() - .find(ESM::MagicEffect::indexToGmstString(rec.mIndex)) + .find(ESM::MagicEffect::refIdToGmstString(rec.mId)) ->mValue.getString(); }); magicEffectT["school"] = sol::readonly_property( @@ -453,12 +417,10 @@ namespace MWLua auto activeSpellEffectT = state.new_usertype("ActiveSpellEffect"); activeSpellEffectT[sol::meta_function::to_string] = [](const ESM::ActiveEffect& self) { - return "ActiveSpellEffect[" + ESM::MagicEffect::indexToGmstString(self.mEffectId) + "]"; + return std::format("ActiveSpellEffect[{}]", ESM::MagicEffect::refIdToGmstString(self.mEffectId)); }; - activeSpellEffectT["id"] = sol::readonly_property([](const ESM::ActiveEffect& self) -> std::string { - auto name = ESM::MagicEffect::indexToName(self.mEffectId); - return Misc::StringUtils::lowerCase(name); - }); + activeSpellEffectT["id"] + = sol::readonly_property([](const ESM::ActiveEffect& self) -> ESM::RefId { return self.mEffectId; }); activeSpellEffectT["index"] = sol::readonly_property([](const ESM::ActiveEffect& self) -> int { return self.mEffectIndex; }); activeSpellEffectT["name"] = sol::readonly_property([](const ESM::ActiveEffect& self) -> std::string { @@ -588,12 +550,10 @@ namespace MWLua auto activeEffectT = state.new_usertype("ActiveEffect"); activeEffectT[sol::meta_function::to_string] = [](const ActiveEffect& self) { - return "ActiveEffect[" + ESM::MagicEffect::indexToGmstString(self.key.mId) + "]"; + return std::format("ActiveEffect[{}]", ESM::MagicEffect::refIdToGmstString(self.key.mId)); }; - activeEffectT["id"] = sol::readonly_property([](const ActiveEffect& self) -> std::string { - auto name = ESM::MagicEffect::indexToName(self.key.mId); - return Misc::StringUtils::lowerCase(name); - }); + activeEffectT["id"] + = sol::readonly_property([](const ActiveEffect& self) -> ESM::RefId { return self.key.mId; }); activeEffectT["name"] = sol::readonly_property([](const ActiveEffect& self) -> std::string { return self.key.toString(); }); @@ -1024,7 +984,7 @@ namespace MWLua auto getEffectKey = [](std::string_view idStr, sol::optional argStr) -> MWMechanics::EffectKey { - auto id = ESM::MagicEffect::indexNameToIndex(idStr); + auto id = ESM::RefId::deserializeText(idStr); auto* rec = MWBase::Environment::get().getWorld()->getStore().get().find(id); MWMechanics::EffectKey key = MWMechanics::EffectKey(id); diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index f1cb73bc5f..0089ce2457 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -46,10 +46,10 @@ namespace MWLua sol::table res(lua, sol::create); for (uint32_t i = 0; i < 4; ++i) { - if (rec.mData.mEffectID[i] < 0) + if (rec.mData.mEffectID[i].empty()) continue; ESM::IndexedENAMstruct effect; - effect.mData.mEffectID = static_cast(rec.mData.mEffectID[i]); + effect.mData.mEffectID = rec.mData.mEffectID[i]; effect.mData.mSkill = static_cast(rec.mData.mSkills[i]); effect.mData.mAttribute = static_cast(rec.mData.mAttributes[i]); effect.mData.mRange = ESM::RT_Self; @@ -63,4 +63,4 @@ namespace MWLua return res; }); } -} \ No newline at end of file +} diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index d2f4e7d2d4..ff52465180 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -358,7 +358,7 @@ namespace MWMechanics if (Settings::game().mClassicCalmSpellsBehavior) { - ESM::MagicEffect::Effects effect + ESM::RefId effect = ptr.getClass().isNpc() ? ESM::MagicEffect::CalmHumanoid : ESM::MagicEffect::CalmCreature; if (creatureStats.getMagicEffects().getOrDefault(effect).getMagnitude() > 0.f) creatureStats.getAiSequence().stopCombat(); @@ -426,8 +426,7 @@ namespace MWMechanics { const VFS::Path::Normalized reflectStaticModel = Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(reflectStatic->mModel)); - animation->addEffect( - reflectStaticModel, ESM::MagicEffect::indexToName(ESM::MagicEffect::Reflect), false); + animation->addEffect(reflectStaticModel, ESM::MagicEffect::Reflect.getValue(), false); } caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected); } @@ -646,7 +645,7 @@ namespace MWMechanics purge([=](const ActiveSpellParams& params) { return params.mActiveSpellId == id; }, ptr); } - void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, int effectId, ESM::RefId effectArg) + void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, ESM::RefId effectId, ESM::RefId effectArg) { purge( [=](const ActiveSpellParams&, const ESM::ActiveEffect& effect) { diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index c465e28556..dcc37521f0 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -152,7 +152,7 @@ namespace MWMechanics void removeEffectsByActiveSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id); /// Remove all active effects with this effect id - void purgeEffect(const MWWorld::Ptr& ptr, int effectId, ESM::RefId effectArg = {}); + void purgeEffect(const MWWorld::Ptr& ptr, ESM::RefId effectId, ESM::RefId effectArg = {}); void purge(EffectPredicate predicate, const MWWorld::Ptr& ptr); void purge(ParamsPredicate predicate, const MWWorld::Ptr& ptr); diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 0f1fe34701..796a46fda8 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -796,7 +796,7 @@ namespace MWMechanics continue; for (const auto& effect : spell.getEffects()) { - static const std::array damageEffects{ + static const std::array damageEffects{ ESM::MagicEffect::FireDamage, ESM::MagicEffect::ShockDamage, ESM::MagicEffect::FrostDamage, @@ -1817,7 +1817,7 @@ namespace MWMechanics // Make sure spell effects are removed purgeSpellEffects(actor.getPtr().getCellRef().getRefNum()); - stats.getMagicEffects().add(ESM::MagicEffect::Vampirism, vampirism); + stats.getMagicEffects().add(EffectKey(ESM::MagicEffect::Vampirism), vampirism); if (isPlayer) { diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 3c08279463..f748988e42 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -30,7 +31,7 @@ namespace std::optional toKey(const ESM::Ingredient& ingredient, size_t i) { - if (ingredient.mData.mEffectID[i] < 0) + if (ingredient.mData.mEffectID[i].empty()) return {}; ESM::RefId arg = ESM::Skill::indexToRefId(ingredient.mData.mSkills[i]); if (arg.empty()) @@ -176,7 +177,7 @@ void MWMechanics::Alchemy::updateEffects() if (magicEffect->mData.mBaseCost <= 0) { - const std::string os = "invalid base cost for magic effect " + std::to_string(effectKey.mId); + const std::string os = std::format("invalid base cost for magic effect {}", effectKey.mId.getRefIdString()); throw std::runtime_error(os); } @@ -217,7 +218,7 @@ void MWMechanics::Alchemy::updateEffects() if (magnitude > 0 && duration > 0) { ESM::ENAMstruct effect; - effect.mEffectID = static_cast(effectKey.mId); + effect.mEffectID = effectKey.mId; effect.mAttribute = -1; effect.mSkill = -1; @@ -621,7 +622,7 @@ std::vector MWMechanics::Alchemy::effectsDescription( if (alchemySkill < fWortChanceValue * static_cast(i + 1)) break; - if (effectID != -1) + if (!effectID.empty()) { const ESM::Attribute* attribute = store->get().search(ESM::Attribute::indexToRefId(data.mAttributes[i])); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c87ad8ef15..e141c917e5 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2803,19 +2803,19 @@ namespace MWMechanics // Stop any effects that are no longer active std::vector effects = mAnimation->getLoopingEffects(); - for (std::string_view effectId : effects) + for (std::string_view effectStr : effects) { - auto index = ESM::MagicEffect::indexNameToIndex(effectId); + auto effectId = ESM::RefId::deserializeText(effectStr); - if (index >= 0 + if (MWBase::Environment::get().getESMStore()->get().search(effectId) && (mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished() || mPtr.getClass() .getCreatureStats(mPtr) .getMagicEffects() - .getOrDefault(MWMechanics::EffectKey(index)) + .getOrDefault(MWMechanics::EffectKey(effectId)) .getMagnitude() <= 0)) - mAnimation->removeEffect(effectId); + mAnimation->removeEffect(effectStr); } } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index e82ff0c8d2..dbb31f2a34 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -350,12 +350,14 @@ namespace MWMechanics if (godmode) return; auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - for (int i = 0; i < 3; ++i) + static const std::array elementalShieldEffects{ ESM::MagicEffect::FireShield, + ESM::MagicEffect::LightningShield, ESM::MagicEffect::FrostShield }; + for (const auto elementalShieldEffect : elementalShieldEffects) { float magnitude = victim.getClass() .getCreatureStats(victim) .getMagicEffects() - .getOrDefault(ESM::MagicEffect::FireShield + i) + .getOrDefault(elementalShieldEffect) .getMagnitude(); if (!magnitude) @@ -375,10 +377,10 @@ namespace MWMechanics float x = std::max(0.f, saveTerm - Misc::Rng::roll0to99(prng)); - short element = ESM::MagicEffect::FireDamage; - if (i == 1) + ESM::RefId element = ESM::MagicEffect::FireDamage; + if (elementalShieldEffect == ESM::MagicEffect::LightningShield) element = ESM::MagicEffect::ShockDamage; - if (i == 2) + if (elementalShieldEffect == ESM::MagicEffect::FrostShield) element = ESM::MagicEffect::FrostDamage; float elementResistance diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index e52abefed6..3531b7fde1 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -649,7 +649,7 @@ namespace MWMechanics return mTimeOfDeath; } - std::multimap& CreatureStats::getSummonedCreatureMap() + std::multimap& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index 5441fdc28f..4cf4c2e421 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -82,7 +82,7 @@ namespace MWMechanics MWWorld::TimeStamp mTimeOfDeath; private: - std::multimap mSummonedCreatures; // + std::multimap mSummonedCreatures; // float mAwarenessTimer = 0.f; int mAwarenessRoll = -1; @@ -231,7 +231,7 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; - std::multimap& getSummonedCreatureMap(); // + std::multimap& getSummonedCreatureMap(); // enum Flag { diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index e4210ed8fc..f9a84cde17 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -26,7 +26,7 @@ namespace namespace MWMechanics { EffectKey::EffectKey() - : mId(0) + : mId(ESM::MagicEffect::WaterWalking) { } @@ -55,13 +55,7 @@ namespace MWMechanics 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; + return std::tie(left.mId, left.mArg) < std::tie(right.mId, right.mArg); } bool operator==(const EffectKey& left, const EffectKey& right) @@ -145,6 +139,11 @@ namespace MWMechanics return get(key).value_or(EffectParam()); } + EffectParam MagicEffects::getOrDefault(ESM::RefId effectId) const + { + return getOrDefault(EffectKey(effectId)); + } + std::optional MagicEffects::get(const EffectKey& key) const { Collection::const_iterator iter = mCollection.find(key); @@ -189,34 +188,21 @@ namespace MWMechanics 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 (effect.mId == ESM::MagicEffect::AbsorbAttribute || effect.mId == ESM::MagicEffect::AbsorbSkill) + spellLine = windowManager->getGameSettingString("sAbsorb", {}); + else if (effect.mId == ESM::MagicEffect::DamageAttribute || effect.mId == ESM::MagicEffect::DamageSkill) + spellLine = windowManager->getGameSettingString("sDamage", {}); + else if (effect.mId == ESM::MagicEffect::DrainAttribute || effect.mId == ESM::MagicEffect::DrainSkill) + spellLine = windowManager->getGameSettingString("sDrain", {}); + else if (effect.mId == ESM::MagicEffect::FortifyAttribute || effect.mId == ESM::MagicEffect::FortifySkill) + spellLine = windowManager->getGameSettingString("sFortify", {}); + else if (effect.mId == ESM::MagicEffect::RestoreAttribute || effect.mId == ESM::MagicEffect::RestoreSkill) + spellLine = windowManager->getGameSettingString("sRestore", {}); } if (spellLine.empty()) { - auto& effectIDStr = ESM::MagicEffect::indexToGmstString(effect.mIndex); + auto effectIDStr = ESM::MagicEffect::refIdToGmstString(effect.mId); spellLine = windowManager->getGameSettingString(effectIDStr, {}); } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 80b858644e..652bb13905 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -21,12 +21,12 @@ namespace MWMechanics { struct EffectKey { - int mId; + ESM::RefId mId; ESM::RefId mArg; // skill or ability EffectKey(); - EffectKey(int id, ESM::RefId arg = {}) + EffectKey(ESM::RefId id, ESM::RefId arg = {}) : mId(id) , mArg(arg) { @@ -107,6 +107,7 @@ namespace MWMechanics void modifyBase(const EffectKey& key, int diff); EffectParam getOrDefault(const EffectKey& key) const; + EffectParam getOrDefault(ESM::RefId effectId) const; std::optional get(const EffectKey& key) const; ///< This function can safely be used for keys that are not present. }; diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d17bd97744..c7571c35ac 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -320,7 +320,7 @@ namespace MWMechanics ESM::RefId school = ESM::Skill::Alteration; if (!enchantment->mEffects.mList.empty()) { - short effectId = enchantment->mEffects.mList.front().mData.mEffectID; + ESM::RefId effectId = enchantment->mEffects.mList.front().mData.mEffectID; const ESM::MagicEffect* magicEffect = store->get().find(effectId); school = magicEffect->mData.mSchool; } @@ -509,8 +509,8 @@ namespace MWMechanics MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (animation) { - animation->addEffect(castStaticModel.value(), ESM::MagicEffect::indexToName(effect->mIndex), false, {}, - effect->mParticle); + animation->addEffect( + castStaticModel.value(), effect->mId.getRefIdString(), false, {}, effect->mParticle); } else { @@ -585,8 +585,8 @@ namespace MWMechanics { const VFS::Path::Normalized castStaticModel = Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(castStatic->mModel)); - anim->addEffect(castStaticModel.value(), ESM::MagicEffect::indexToName(magicEffect.mIndex), loop, {}, - magicEffect.mParticle); + anim->addEffect( + castStaticModel.value(), magicEffect.mId.getRefIdString(), loop, {}, magicEffect.mParticle); } } } diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 13a84ba4a0..f62609d6e5 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -37,6 +37,13 @@ namespace { + enum Stats + { + Health = 0, + Magicka = 1, + Fatigue = 2 + }; + float roll(const ESM::ActiveEffect& effect) { if (effect.mMinMagnitude == effect.mMaxMagnitude) @@ -47,7 +54,7 @@ namespace } ESM::ActiveEffect::Flags modifyAiSetting(const MWWorld::Ptr& target, const ESM::ActiveEffect& effect, - ESM::MagicEffect::Effects creatureEffect, MWMechanics::AiSetting setting, float magnitude) + ESM::RefId creatureEffect, MWMechanics::AiSetting setting, float magnitude) { if (target == MWMechanics::getPlayer() || (effect.mEffectId == creatureEffect) == target.getClass().isNpc()) return ESM::ActiveEffect::Flag_Invalid; @@ -291,8 +298,7 @@ namespace { const VFS::Path::Normalized absorbStaticModel = Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(absorbStatic->mModel)); - animation->addEffect( - absorbStaticModel.value(), ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false); + animation->addEffect(absorbStaticModel.value(), ESM::MagicEffect::SpellAbsorption.getValue(), false); } int spellCost = 0; @@ -365,8 +371,8 @@ namespace { const ESM::Spell* spell = spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary) ? spellParams.getSpell() : nullptr; - float magnitudeMult = MWMechanics::getEffectMultiplier( - static_cast(effect.mEffectId), target, caster, spell, &magnitudes); + float magnitudeMult + = MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes); if (magnitudeMult == 0) { // Fully resisted, show message @@ -385,19 +391,22 @@ namespace return MWMechanics::MagicApplicationResult::Type::APPLIED; } - static const std::map sBoundItemsMap{ - { ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID" }, - { ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID" }, - { ESM::MagicEffect::BoundCuirass, "sMagicBoundCuirassID" }, - { ESM::MagicEffect::BoundDagger, "sMagicBoundDaggerID" }, - { ESM::MagicEffect::BoundGloves, "sMagicBoundLeftGauntletID" }, - { ESM::MagicEffect::BoundHelm, "sMagicBoundHelmID" }, - { ESM::MagicEffect::BoundLongbow, "sMagicBoundLongbowID" }, - { ESM::MagicEffect::BoundLongsword, "sMagicBoundLongswordID" }, - { ESM::MagicEffect::BoundMace, "sMagicBoundMaceID" }, - { ESM::MagicEffect::BoundShield, "sMagicBoundShieldID" }, - { ESM::MagicEffect::BoundSpear, "sMagicBoundSpearID" }, - }; + const std::unordered_map& getBoundItemsMap() + { + static const std::unordered_map sBoundItemsMap{ + { ESM::MagicEffect::BoundBattleAxe, "sMagicBoundBattleAxeID" }, + { ESM::MagicEffect::BoundBoots, "sMagicBoundBootsID" }, + { ESM::MagicEffect::BoundCuirass, "sMagicBoundCuirassID" }, + { ESM::MagicEffect::BoundDagger, "sMagicBoundDaggerID" }, + { ESM::MagicEffect::BoundHelm, "sMagicBoundHelmID" }, + { ESM::MagicEffect::BoundLongbow, "sMagicBoundLongbowID" }, + { ESM::MagicEffect::BoundLongsword, "sMagicBoundLongswordID" }, + { ESM::MagicEffect::BoundMace, "sMagicBoundMaceID" }, + { ESM::MagicEffect::BoundShield, "sMagicBoundShieldID" }, + { ESM::MagicEffect::BoundSpear, "sMagicBoundSpearID" }, + }; + return sBoundItemsMap; + } using SpellsPurge = void (MWMechanics::Spells::*)(); void purgePermanent(const MWWorld::Ptr& target, SpellsPurge method, ESM::Spell::SpellType type) @@ -426,318 +435,311 @@ namespace MWMechanics { const auto world = MWBase::Environment::get().getWorld(); const bool godmode = target == getPlayer() && world->getGodModeState(); - switch (effect.mEffectId) - { - case ESM::MagicEffect::CureCommonDisease: - purgePermanent(target, &Spells::purgeCommonDisease, ESM::Spell::ST_Disease); - break; - case ESM::MagicEffect::CureBlightDisease: - purgePermanent(target, &Spells::purgeBlightDisease, ESM::Spell::ST_Blight); - break; - case ESM::MagicEffect::RemoveCurse: - purgePermanent(target, &Spells::purgeCurses, ESM::Spell::ST_Curse); - break; - case ESM::MagicEffect::CureCorprusDisease: - target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( - target, ESM::MagicEffect::Corprus); - break; - case ESM::MagicEffect::CurePoison: - target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( - target, ESM::MagicEffect::Poison); - break; - case ESM::MagicEffect::CureParalyzation: - target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( - target, ESM::MagicEffect::Paralyze); - break; - case ESM::MagicEffect::Dispel: - // Dispel removes entire spells at once - target.getClass().getCreatureStats(target).getActiveSpells().purge( - [magnitude = effect.mMagnitude](const ActiveSpells::ActiveSpellParams& params) { - if (params.hasFlag(ESM::ActiveSpells::Flag_Temporary)) + if (effect.mEffectId == ESM::MagicEffect::CureCommonDisease) + purgePermanent(target, &Spells::purgeCommonDisease, ESM::Spell::ST_Disease); + else if (effect.mEffectId == ESM::MagicEffect::CureBlightDisease) + purgePermanent(target, &Spells::purgeBlightDisease, ESM::Spell::ST_Blight); + else if (effect.mEffectId == ESM::MagicEffect::RemoveCurse) + purgePermanent(target, &Spells::purgeCurses, ESM::Spell::ST_Curse); + else if (effect.mEffectId == ESM::MagicEffect::CureCorprusDisease) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Corprus); + else if (effect.mEffectId == ESM::MagicEffect::CurePoison) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect(target, ESM::MagicEffect::Poison); + else if (effect.mEffectId == ESM::MagicEffect::CureParalyzation) + target.getClass().getCreatureStats(target).getActiveSpells().purgeEffect( + target, ESM::MagicEffect::Paralyze); + else if (effect.mEffectId == ESM::MagicEffect::Dispel) + // Dispel removes entire spells at once + target.getClass().getCreatureStats(target).getActiveSpells().purge( + [magnitude = effect.mMagnitude](const ActiveSpells::ActiveSpellParams& params) { + if (params.hasFlag(ESM::ActiveSpells::Flag_Temporary)) + { + const ESM::Spell* spell = params.getSpell(); + if (spell && spell->mData.mType == ESM::Spell::ST_Spell) { - const ESM::Spell* spell = params.getSpell(); - if (spell && spell->mData.mType == ESM::Spell::ST_Spell) - { - auto& prng = MWBase::Environment::get().getWorld()->getPrng(); - return Misc::Rng::roll0to99(prng) < magnitude; - } + auto& prng = MWBase::Environment::get().getWorld()->getPrng(); + return Misc::Rng::roll0to99(prng) < magnitude; } - return false; - }, - target); - break; - case ESM::MagicEffect::AlmsiviIntervention: - case ESM::MagicEffect::DivineIntervention: - if (target != getPlayer()) - return ESM::ActiveEffect::Flag_Invalid; - else if (world->isTeleportingEnabled()) + } + return false; + }, + target); + else if (effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention + || effect.mEffectId == ESM::MagicEffect::DivineIntervention) + { + if (target != getPlayer()) + return ESM::ActiveEffect::Flag_Invalid; + else if (world->isTeleportingEnabled()) + { + std::string_view marker + = (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; + world->teleportToClosestMarker(target, ESM::RefId::stringRefId(marker)); + if (!caster.isEmpty()) { - std::string_view marker - = (effect.mEffectId == ESM::MagicEffect::DivineIntervention) ? "divinemarker" : "templemarker"; - world->teleportToClosestMarker(target, ESM::RefId::stringRefId(marker)); + MWRender::Animation* anim = world->getAnimation(caster); + anim->removeEffect(effect.mEffectId.getRefIdString()); + const ESM::Static* fx + = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_end")); + if (fx != nullptr) + { + const VFS::Path::Normalized fxModel + = Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(fx->mModel)); + anim->addEffect(fxModel.value(), ""); + } + } + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + } + else if (effect.mEffectId == ESM::MagicEffect::Mark) + { + if (target != getPlayer()) + return ESM::ActiveEffect::Flag_Invalid; + else if (world->isTeleportingEnabled()) + world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition()); + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + } + else if (effect.mEffectId == ESM::MagicEffect::Recall) + { + if (target != getPlayer()) + return ESM::ActiveEffect::Flag_Invalid; + else if (world->isTeleportingEnabled()) + { + MWWorld::CellStore* markedCell = nullptr; + ESM::Position markedPosition; + + world->getPlayer().getMarkedPosition(markedCell, markedPosition); + if (markedCell) + { + ESM::RefId dest = markedCell->getCell()->getId(); + MWWorld::ActionTeleport action(dest, markedPosition, false); + action.execute(target); if (!caster.isEmpty()) { MWRender::Animation* anim = world->getAnimation(caster); - anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId)); - const ESM::Static* fx - = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_end")); - if (fx != nullptr) - { - const VFS::Path::Normalized fxModel - = Misc::ResourceHelpers::correctMeshPath(VFS::Path::Normalized(fx->mModel)); - anim->addEffect(fxModel.value(), ""); - } + anim->removeEffect(effect.mEffectId.getRefIdString()); } } - else if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - break; - case ESM::MagicEffect::Mark: - if (target != getPlayer()) - return ESM::ActiveEffect::Flag_Invalid; - else if (world->isTeleportingEnabled()) - world->getPlayer().markPosition(target.getCell(), target.getRefData().getPosition()); - else if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - break; - case ESM::MagicEffect::Recall: - if (target != getPlayer()) - return ESM::ActiveEffect::Flag_Invalid; - else if (world->isTeleportingEnabled()) + } + else if (caster == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); + } + else if (effect.mEffectId == ESM::MagicEffect::CommandCreature + || effect.mEffectId == ESM::MagicEffect::CommandHumanoid) + { + if (caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() + || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc()) + return ESM::ActiveEffect::Flag_Invalid; + else if (effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) + { + MWMechanics::AiFollow package(caster, true); + target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); + } + } + else if (effect.mEffectId == ESM::MagicEffect::ExtraSpell) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + if (target != getPlayer()) + { + auto& store = target.getClass().getInventoryStore(target); + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { - MWWorld::CellStore* markedCell = nullptr; - ESM::Position markedPosition; - - world->getPlayer().getMarkedPosition(markedCell, markedPosition); - if (markedCell) + // Unequip everything except weapons, torches, and pants + switch (slot) { - ESM::RefId dest = markedCell->getCell()->getId(); - MWWorld::ActionTeleport action(dest, markedPosition, false); - action.execute(target); - if (!caster.isEmpty()) + case MWWorld::InventoryStore::Slot_Ammunition: + case MWWorld::InventoryStore::Slot_CarriedRight: + case MWWorld::InventoryStore::Slot_Pants: + continue; + case MWWorld::InventoryStore::Slot_CarriedLeft: { - MWRender::Animation* anim = world->getAnimation(caster); - anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId)); - } - } - } - else if (caster == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sTeleportDisabled}"); - break; - case ESM::MagicEffect::CommandCreature: - case ESM::MagicEffect::CommandHumanoid: - if (caster.isEmpty() || !caster.getClass().isActor() || target == getPlayer() - || (effect.mEffectId == ESM::MagicEffect::CommandCreature) == target.getClass().isNpc()) - return ESM::ActiveEffect::Flag_Invalid; - else if (effect.mMagnitude >= target.getClass().getCreatureStats(target).getLevel()) - { - MWMechanics::AiFollow package(caster, true); - target.getClass().getCreatureStats(target).getAiSequence().stack(package, target); - } - break; - case ESM::MagicEffect::ExtraSpell: - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - if (target != getPlayer()) - { - auto& store = target.getClass().getInventoryStore(target); - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - // Unequip everything except weapons, torches, and pants - switch (slot) - { - case MWWorld::InventoryStore::Slot_Ammunition: - case MWWorld::InventoryStore::Slot_CarriedRight: - case MWWorld::InventoryStore::Slot_Pants: + auto carried = store.getSlot(slot); + if (carried == store.end() || carried.getType() != MWWorld::ContainerStore::Type_Armor) continue; - case MWWorld::InventoryStore::Slot_CarriedLeft: - { - auto carried = store.getSlot(slot); - if (carried == store.end() || carried.getType() != MWWorld::ContainerStore::Type_Armor) - continue; - [[fallthrough]]; - } - default: - store.unequipSlot(slot); + [[fallthrough]]; } + default: + store.unequipSlot(slot); } } - break; - case ESM::MagicEffect::TurnUndead: - if (target.getClass().isNpc() - || target.get()->mBase->mData.mType != ESM::Creature::Undead) - return ESM::ActiveEffect::Flag_Invalid; + } + } + else if (effect.mEffectId == ESM::MagicEffect::TurnUndead) + { + if (target.getClass().isNpc() || target.get()->mBase->mData.mType != ESM::Creature::Undead) + return ESM::ActiveEffect::Flag_Invalid; + else + { + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(AiSetting::Flee); + stat.setModifier(static_cast(stat.getModifier() + effect.mMagnitude)); + creatureStats.setAiSetting(AiSetting::Flee, stat); + } + } + else if (effect.mEffectId == ESM::MagicEffect::FrenzyCreature + || effect.mEffectId == ESM::MagicEffect::FrenzyHumanoid) + return modifyAiSetting( + target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::CalmCreature + || effect.mEffectId == ESM::MagicEffect::CalmHumanoid) + { + ESM::ActiveEffect::Flags applied + = modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, -effect.mMagnitude); + if (applied != ESM::ActiveEffect::Flag_Applied) + return applied; + if (effect.mMagnitude > 0) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + creatureStats.getAiSequence().stopCombat(); + } + } + else if (effect.mEffectId == ESM::MagicEffect::DemoralizeCreature + || effect.mEffectId == ESM::MagicEffect::DemoralizeHumanoid) + return modifyAiSetting( + target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::RallyCreature + || effect.mEffectId == ESM::MagicEffect::RallyHumanoid) + return modifyAiSetting( + target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, -effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::Charm) + { + if (!target.getClass().isNpc()) + return ESM::ActiveEffect::Flag_Invalid; + } + else if (effect.mEffectId == ESM::MagicEffect::Sound) + { + if (target == getPlayer()) + { + const auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); + float volume = std::clamp( + (magnitudes.getOrDefault(effect.mEffectId).getModifier() + effect.mMagnitude) / 100.f, 0.f, 1.f); + MWBase::Environment::get().getSoundManager()->playSound3D(target, + ESM::RefId::stringRefId("magic sound"), volume, 1.f, MWSound::Type::Sfx, + MWSound::PlayMode::LoopNoEnv); + } + } + else if (effect.mEffectId == ESM::MagicEffect::SummonScamp + || effect.mEffectId == ESM::MagicEffect::SummonClannfear + || effect.mEffectId == ESM::MagicEffect::SummonDaedroth + || effect.mEffectId == ESM::MagicEffect::SummonDremora + || effect.mEffectId == ESM::MagicEffect::SummonAncestralGhost + || effect.mEffectId == ESM::MagicEffect::SummonSkeletalMinion + || effect.mEffectId == ESM::MagicEffect::SummonBonewalker + || effect.mEffectId == ESM::MagicEffect::SummonGreaterBonewalker + || effect.mEffectId == ESM::MagicEffect::SummonBonelord + || effect.mEffectId == ESM::MagicEffect::SummonWingedTwilight + || effect.mEffectId == ESM::MagicEffect::SummonHunger + || effect.mEffectId == ESM::MagicEffect::SummonGoldenSaint + || effect.mEffectId == ESM::MagicEffect::SummonFlameAtronach + || effect.mEffectId == ESM::MagicEffect::SummonFrostAtronach + || effect.mEffectId == ESM::MagicEffect::SummonStormAtronach + || effect.mEffectId == ESM::MagicEffect::SummonCenturionSphere + || effect.mEffectId == ESM::MagicEffect::SummonFabricant || effect.mEffectId == ESM::MagicEffect::SummonWolf + || effect.mEffectId == ESM::MagicEffect::SummonBear || effect.mEffectId == ESM::MagicEffect::SummonBonewolf + || effect.mEffectId == ESM::MagicEffect::SummonCreature04 + || effect.mEffectId == ESM::MagicEffect::SummonCreature05) + { + if (!target.isInCell()) + return ESM::ActiveEffect::Flag_Invalid; + effect.mArg = summonCreature(effect.mEffectId, target); + } + else if (effect.mEffectId == ESM::MagicEffect::BoundGloves) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + addBoundItem( + ESM::RefId::stringRefId( + world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString()), + target); + addBoundItem( + ESM::RefId::stringRefId( + world->getStore().get().find("sMagicBoundLeftGauntletID")->mValue.getString()), + target); + } + else if (effect.mEffectId == ESM::MagicEffect::BoundDagger + || effect.mEffectId == ESM::MagicEffect::BoundLongsword || effect.mEffectId == ESM::MagicEffect::BoundMace + || effect.mEffectId == ESM::MagicEffect::BoundBattleAxe || effect.mEffectId == ESM::MagicEffect::BoundSpear + || effect.mEffectId == ESM::MagicEffect::BoundLongbow || effect.mEffectId == ESM::MagicEffect::BoundCuirass + || effect.mEffectId == ESM::MagicEffect::BoundHelm || effect.mEffectId == ESM::MagicEffect::BoundBoots + || effect.mEffectId == ESM::MagicEffect::BoundShield) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + std::string_view item = getBoundItemsMap().at(effect.mEffectId); + const MWWorld::Store& gmst = world->getStore().get(); + const ESM::RefId itemId = ESM::RefId::stringRefId(gmst.find(item)->mValue.getString()); + if (!addBoundItem(itemId, target)) + effect.mTimeLeft = 0.f; + } + else if (effect.mEffectId == ESM::MagicEffect::FireDamage || effect.mEffectId == ESM::MagicEffect::ShockDamage + || effect.mEffectId == ESM::MagicEffect::FrostDamage || effect.mEffectId == ESM::MagicEffect::DamageHealth + || effect.mEffectId == ESM::MagicEffect::Poison || effect.mEffectId == ESM::MagicEffect::DamageMagicka + || effect.mEffectId == ESM::MagicEffect::DamageFatigue) + { + if (!godmode) + { + auto targetStat = Stats::Health; + if (effect.mEffectId == ESM::MagicEffect::DamageMagicka) + targetStat = Stats::Magicka; + else if (effect.mEffectId == ESM::MagicEffect::DamageFatigue) + targetStat = Stats::Fatigue; + // Damage "Dynamic" abilities reduce the base value + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, targetStat, -effect.mMagnitude); else { - auto& creatureStats = target.getClass().getCreatureStats(target); - Stat stat = creatureStats.getAiSetting(AiSetting::Flee); - stat.setModifier(static_cast(stat.getModifier() + effect.mMagnitude)); - creatureStats.setAiSetting(AiSetting::Flee, stat); + adjustDynamicStat(target, targetStat, -effect.mMagnitude, + targetStat == Stats::Fatigue && Settings::game().mUncappedDamageFatigue); + if (targetStat == Stats::Health) + receivedMagicDamage = affectedHealth = true; } - break; - case ESM::MagicEffect::FrenzyCreature: - case ESM::MagicEffect::FrenzyHumanoid: - return modifyAiSetting( - target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, effect.mMagnitude); - case ESM::MagicEffect::CalmCreature: - case ESM::MagicEffect::CalmHumanoid: - { - ESM::ActiveEffect::Flags applied = modifyAiSetting( - target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, -effect.mMagnitude); - if (applied != ESM::ActiveEffect::Flag_Applied) - return applied; - if (effect.mMagnitude > 0) - { - auto& creatureStats = target.getClass().getCreatureStats(target); - creatureStats.getAiSequence().stopCombat(); - } - break; } - case ESM::MagicEffect::DemoralizeCreature: - case ESM::MagicEffect::DemoralizeHumanoid: - return modifyAiSetting( - target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, effect.mMagnitude); - case ESM::MagicEffect::RallyCreature: - case ESM::MagicEffect::RallyHumanoid: - return modifyAiSetting( - target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, -effect.mMagnitude); - case ESM::MagicEffect::Charm: - if (!target.getClass().isNpc()) - return ESM::ActiveEffect::Flag_Invalid; - break; - case ESM::MagicEffect::Sound: - if (target == getPlayer()) - { - const auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); - float volume = std::clamp( - (magnitudes.getOrDefault(effect.mEffectId).getModifier() + effect.mMagnitude) / 100.f, 0.f, - 1.f); - MWBase::Environment::get().getSoundManager()->playSound3D(target, - ESM::RefId::stringRefId("magic sound"), volume, 1.f, MWSound::Type::Sfx, - MWSound::PlayMode::LoopNoEnv); - } - break; - case ESM::MagicEffect::SummonScamp: - case ESM::MagicEffect::SummonClannfear: - case ESM::MagicEffect::SummonDaedroth: - case ESM::MagicEffect::SummonDremora: - case ESM::MagicEffect::SummonAncestralGhost: - case ESM::MagicEffect::SummonSkeletalMinion: - case ESM::MagicEffect::SummonBonewalker: - case ESM::MagicEffect::SummonGreaterBonewalker: - case ESM::MagicEffect::SummonBonelord: - case ESM::MagicEffect::SummonWingedTwilight: - case ESM::MagicEffect::SummonHunger: - case ESM::MagicEffect::SummonGoldenSaint: - case ESM::MagicEffect::SummonFlameAtronach: - case ESM::MagicEffect::SummonFrostAtronach: - case ESM::MagicEffect::SummonStormAtronach: - case ESM::MagicEffect::SummonCenturionSphere: - case ESM::MagicEffect::SummonFabricant: - case ESM::MagicEffect::SummonWolf: - case ESM::MagicEffect::SummonBear: - case ESM::MagicEffect::SummonBonewolf: - case ESM::MagicEffect::SummonCreature04: - case ESM::MagicEffect::SummonCreature05: - if (!target.isInCell()) - return ESM::ActiveEffect::Flag_Invalid; - effect.mArg = summonCreature(effect.mEffectId, target); - break; - case ESM::MagicEffect::BoundGloves: - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - addBoundItem(ESM::RefId::stringRefId(world->getStore() - .get() - .find("sMagicBoundRightGauntletID") - ->mValue.getString()), - target); - // left gauntlet added below - [[fallthrough]]; - case ESM::MagicEffect::BoundDagger: - case ESM::MagicEffect::BoundLongsword: - case ESM::MagicEffect::BoundMace: - case ESM::MagicEffect::BoundBattleAxe: - case ESM::MagicEffect::BoundSpear: - case ESM::MagicEffect::BoundLongbow: - case ESM::MagicEffect::BoundCuirass: - case ESM::MagicEffect::BoundHelm: - case ESM::MagicEffect::BoundBoots: - case ESM::MagicEffect::BoundShield: + } + else if (effect.mEffectId == ESM::MagicEffect::DamageAttribute) + { + if (!godmode) + damageAttribute(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::DamageSkill) + { + if (!godmode && target.getClass().isNpc()) { - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - const std::string& item = sBoundItemsMap.at(effect.mEffectId); - const MWWorld::Store& gmst = world->getStore().get(); - const ESM::RefId itemId = ESM::RefId::stringRefId(gmst.find(item)->mValue.getString()); - if (!addBoundItem(itemId, target)) - effect.mTimeLeft = 0.f; - break; - } - case ESM::MagicEffect::FireDamage: - case ESM::MagicEffect::ShockDamage: - case ESM::MagicEffect::FrostDamage: - case ESM::MagicEffect::DamageHealth: - case ESM::MagicEffect::Poison: - case ESM::MagicEffect::DamageMagicka: - case ESM::MagicEffect::DamageFatigue: - if (!godmode) - { - int index = 0; - if (effect.mEffectId == ESM::MagicEffect::DamageMagicka) - index = 1; - else if (effect.mEffectId == ESM::MagicEffect::DamageFatigue) - index = 2; - // Damage "Dynamic" abilities reduce the base value - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, index, -effect.mMagnitude); - else - { - adjustDynamicStat( - target, index, -effect.mMagnitude, index == 2 && Settings::game().mUncappedDamageFatigue); - if (index == 0) - receivedMagicDamage = affectedHealth = true; - } - } - break; - case ESM::MagicEffect::DamageAttribute: - if (!godmode) - damageAttribute(target, effect, effect.mMagnitude); - break; - case ESM::MagicEffect::DamageSkill: - if (!godmode && target.getClass().isNpc()) + // Damage Skill abilities reduce base skill :todd: + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { + auto& npcStats = target.getClass().getNpcStats(target); + SkillValue& skill = npcStats.getSkill(effect.getSkillOrAttribute()); // Damage Skill abilities reduce base skill :todd: - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - { - auto& npcStats = target.getClass().getNpcStats(target); - SkillValue& skill = npcStats.getSkill(effect.getSkillOrAttribute()); - // Damage Skill abilities reduce base skill :todd: - skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f)); - } - else - damageSkill(target, effect, effect.mMagnitude); + skill.setBase(std::max(skill.getBase() - effect.mMagnitude, 0.f)); } - break; - case ESM::MagicEffect::RestoreAttribute: - restoreAttribute(target, effect, effect.mMagnitude); - break; - case ESM::MagicEffect::RestoreSkill: - if (target.getClass().isNpc()) - restoreSkill(target, effect, effect.mMagnitude); - break; - case ESM::MagicEffect::RestoreHealth: - affectedHealth = true; - [[fallthrough]]; - case ESM::MagicEffect::RestoreMagicka: - case ESM::MagicEffect::RestoreFatigue: - adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::RestoreHealth, effect.mMagnitude); - break; - case ESM::MagicEffect::SunDamage: + else + damageSkill(target, effect, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::RestoreAttribute) + restoreAttribute(target, effect, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::RestoreSkill) + { + if (target.getClass().isNpc()) + restoreSkill(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::RestoreHealth) + { + affectedHealth = true; + adjustDynamicStat(target, Stats::Health, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::RestoreMagicka) + adjustDynamicStat(target, Stats::Magicka, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::RestoreFatigue) + adjustDynamicStat(target, Stats::Fatigue, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::SunDamage) + { + //// isInCell shouldn't be needed, but updateActor called during game start + if (!godmode && target.isInCell() + && (target.getCell()->isExterior() || target.getCell()->isQuasiExterior())) { - // isInCell shouldn't be needed, but updateActor called during game start - if (!target.isInCell() || !(target.getCell()->isExterior() || target.getCell()->isQuasiExterior()) - || godmode) - break; const float sunRisen = world->getSunPercentage(); static float fMagicSunBlockedMult = world->getStore().get().find("fMagicSunBlockedMult")->mValue.getFloat(); @@ -748,112 +750,167 @@ namespace MWMechanics if (damage > 0.f) receivedMagicDamage = affectedHealth = true; } - break; - case ESM::MagicEffect::DrainHealth: - case ESM::MagicEffect::DrainMagicka: - case ESM::MagicEffect::DrainFatigue: - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else - { - int index = effect.mEffectId - ESM::MagicEffect::DrainHealth; - // Unlike Absorb and Damage effects Drain effects can bring stats below zero - adjustDynamicStat(target, index, -effect.mMagnitude, true); - if (index == 0) - receivedMagicDamage = affectedHealth = true; - } - break; - case ESM::MagicEffect::FortifyHealth: - case ESM::MagicEffect::FortifyMagicka: - case ESM::MagicEffect::FortifyFatigue: - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude); - else - adjustDynamicStat( - target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude, false, true); - break; - case ESM::MagicEffect::DrainAttribute: - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - damageAttribute(target, effect, effect.mMagnitude); - break; - case ESM::MagicEffect::FortifyAttribute: - // Abilities affect base stats, but not for drain - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - { - auto& creatureStats = target.getClass().getCreatureStats(target); - auto attribute = effect.getSkillOrAttribute(); - AttributeValue attr = creatureStats.getAttribute(attribute); - attr.setBase(attr.getBase() + effect.mMagnitude); - creatureStats.setAttribute(attribute, attr); - } - else - fortifyAttribute(target, effect, effect.mMagnitude); - break; - case ESM::MagicEffect::DrainSkill: - if (godmode || !target.getClass().isNpc()) - return ESM::ActiveEffect::Flag_Remove; - damageSkill(target, effect, effect.mMagnitude); - break; - case ESM::MagicEffect::FortifySkill: - if (target.getClass().isNpc()) - { - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - { - // Abilities affect base stats, but not for drain - auto& npcStats = target.getClass().getNpcStats(target); - auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); - skill.setBase(skill.getBase() + effect.mMagnitude); - } - else - fortifySkill(target, effect, effect.mMagnitude); - } - break; - case ESM::MagicEffect::FortifyMaximumMagicka: - recalculateMagicka = true; - break; - case ESM::MagicEffect::AbsorbHealth: - case ESM::MagicEffect::AbsorbMagicka: - case ESM::MagicEffect::AbsorbFatigue: - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else - { - int index = effect.mEffectId - ESM::MagicEffect::AbsorbHealth; - adjustDynamicStat(target, index, -effect.mMagnitude); - if (!caster.isEmpty()) - adjustDynamicStat(caster, index, effect.mMagnitude); - if (index == 0) - receivedMagicDamage = affectedHealth = true; - } - break; - case ESM::MagicEffect::AbsorbAttribute: - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else - { - damageAttribute(target, effect, effect.mMagnitude); - if (!caster.isEmpty()) - fortifyAttribute(caster, effect, effect.mMagnitude); - } - break; - case ESM::MagicEffect::AbsorbSkill: - if (godmode) - return ESM::ActiveEffect::Flag_Remove; - else - { - if (target.getClass().isNpc()) - damageSkill(target, effect, effect.mMagnitude); - if (!caster.isEmpty() && caster.getClass().isNpc()) - fortifySkill(caster, effect, effect.mMagnitude); - } - break; - case ESM::MagicEffect::DisintegrateArmor: + } + else if (effect.mEffectId == ESM::MagicEffect::DrainHealth) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + // Unlike Absorb and Damage effects Drain effects can bring stats below zero + adjustDynamicStat(target, Stats::Health, -effect.mMagnitude, true); + receivedMagicDamage = affectedHealth = true; + } + } + else if (effect.mEffectId == ESM::MagicEffect::DrainMagicka) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + // Unlike Absorb and Damage effects Drain effects can bring stats below zero + adjustDynamicStat(target, Stats::Magicka, -effect.mMagnitude, true); + } + } + else if (effect.mEffectId == ESM::MagicEffect::DrainFatigue) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + // Unlike Absorb and Damage effects Drain effects can bring stats below zero + adjustDynamicStat(target, Stats::Fatigue, -effect.mMagnitude, true); + } + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyHealth) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Health, effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Health, effect.mMagnitude, false, true); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyMagicka) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Magicka, effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Magicka, effect.mMagnitude, false, true); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyFatigue) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Fatigue, effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Fatigue, effect.mMagnitude, false, true); + } + else if (effect.mEffectId == ESM::MagicEffect::DrainAttribute) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + damageAttribute(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyAttribute) + { + // Abilities affect base stats, but not for drain + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + auto attribute = effect.getSkillOrAttribute(); + AttributeValue attr = creatureStats.getAttribute(attribute); + attr.setBase(attr.getBase() + effect.mMagnitude); + creatureStats.setAttribute(attribute, attr); + } + else + fortifyAttribute(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::DrainSkill) + { + if (godmode || !target.getClass().isNpc()) + return ESM::ActiveEffect::Flag_Remove; + damageSkill(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifySkill) + { + if (target.getClass().isNpc()) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + { + // Abilities affect base stats, but not for drain + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); + skill.setBase(skill.getBase() + effect.mMagnitude); + } + else + fortifySkill(target, effect, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyMaximumMagicka) + recalculateMagicka = true; + else if (effect.mEffectId == ESM::MagicEffect::AbsorbHealth) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + adjustDynamicStat(target, Stats::Health, -effect.mMagnitude); + if (!caster.isEmpty()) + { + adjustDynamicStat(caster, Stats::Health, effect.mMagnitude); + } + receivedMagicDamage = affectedHealth = true; + } + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbMagicka) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + adjustDynamicStat(target, Stats::Magicka, -effect.mMagnitude); + if (!caster.isEmpty()) + adjustDynamicStat(caster, Stats::Magicka, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbFatigue) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + adjustDynamicStat(target, Stats::Fatigue, -effect.mMagnitude); + if (!caster.isEmpty()) + adjustDynamicStat(caster, Stats::Fatigue, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbAttribute) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + damageAttribute(target, effect, effect.mMagnitude); + if (!caster.isEmpty()) + fortifyAttribute(caster, effect, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbSkill) + { + if (godmode) + return ESM::ActiveEffect::Flag_Remove; + else + { + if (target.getClass().isNpc()) + damageSkill(target, effect, effect.mMagnitude); + if (!caster.isEmpty() && caster.getClass().isNpc()) + fortifySkill(caster, effect, effect.mMagnitude); + } + } + else if (effect.mEffectId == ESM::MagicEffect::DisintegrateArmor) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + if (!godmode) { - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - if (godmode) - break; static const std::array priorities{ MWWorld::InventoryStore::Slot_CarriedLeft, MWWorld::InventoryStore::Slot_Cuirass, @@ -870,14 +927,14 @@ namespace MWMechanics if (disintegrateSlot(target, priority, effect.mMagnitude)) break; } - break; } - case ESM::MagicEffect::DisintegrateWeapon: - if (!target.getClass().hasInventoryStore(target)) - return ESM::ActiveEffect::Flag_Invalid; - if (!godmode) - disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude); - break; + } + else if (effect.mEffectId == ESM::MagicEffect::DisintegrateWeapon) + { + if (!target.getClass().hasInventoryStore(target)) + return ESM::ActiveEffect::Flag_Invalid; + if (!godmode) + disintegrateSlot(target, MWWorld::InventoryStore::Slot_CarriedRight, effect.mMagnitude); } return ESM::ActiveEffect::Flag_Applied; } @@ -887,37 +944,32 @@ namespace MWMechanics if (effect.mFlags & ESM::ActiveEffect::Flag_Invalid) return true; const auto world = MWBase::Environment::get().getWorld(); - switch (effect.mEffectId) + if (effect.mEffectId == ESM::MagicEffect::Levitate) { - case ESM::MagicEffect::Levitate: + if (!world->isLevitationEnabled()) { - if (!world->isLevitationEnabled()) - { - if (target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); - return true; - } - break; + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sLevitateDisabled}"); + return true; } - case ESM::MagicEffect::Recall: - case ESM::MagicEffect::DivineIntervention: - case ESM::MagicEffect::AlmsiviIntervention: + } + else if (effect.mEffectId == ESM::MagicEffect::DivineIntervention + || effect.mEffectId == ESM::MagicEffect::Recall + || effect.mEffectId == ESM::MagicEffect::AlmsiviIntervention) + { + return effect.mFlags & ESM::ActiveEffect::Flag_Applied; + } + else if (effect.mEffectId == ESM::MagicEffect::WaterWalking) + { + if (target.getClass().isPureWaterCreature(target) && world->isSwimming(target)) + return true; + if (effect.mFlags & ESM::ActiveEffect::Flag_Applied) + return false; + if (!world->isWaterWalkingCastableOnTarget(target)) { - return effect.mFlags & ESM::ActiveEffect::Flag_Applied; - } - case ESM::MagicEffect::WaterWalking: - { - if (target.getClass().isPureWaterCreature(target) && world->isSwimming(target)) - return true; - if (effect.mFlags & ESM::ActiveEffect::Flag_Applied) - break; - if (!world->isWaterWalkingCastableOnTarget(target)) - { - if (target == getPlayer()) - MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidEffect}"); - return true; - } - break; + if (target == getPlayer()) + MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicInvalidEffect}"); + return true; } } return false; @@ -1094,7 +1146,7 @@ namespace MWMechanics effect.mTimeLeft = 0; auto anim = world->getAnimation(target); if (anim) - anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId)); + anim->removeEffect(effect.mEffectId.getRefIdString()); // Note that we can't return REMOVED here because the effect still needs to be detectable } effect.mFlags |= applied; @@ -1109,220 +1161,220 @@ namespace MWMechanics const auto world = MWBase::Environment::get().getWorld(); const auto worldModel = MWBase::Environment::get().getWorldModel(); auto& magnitudes = target.getClass().getCreatureStats(target).getMagicEffects(); - switch (effect.mEffectId) + if (effect.mEffectId == ESM::MagicEffect::CommandCreature + || effect.mEffectId == ESM::MagicEffect::CommandHumanoid) { - case ESM::MagicEffect::CommandCreature: - case ESM::MagicEffect::CommandHumanoid: - if (magnitudes.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f) + if (magnitudes.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f) + { + auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); + seq.erasePackageIf([&](const auto& package) { + return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow + && static_cast(package.get())->isCommanded(); + }); + } + } + else if (effect.mEffectId == ESM::MagicEffect::ExtraSpell) + { + if (magnitudes.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f && target != getPlayer()) + target.getClass().getInventoryStore(target).autoEquip(); + } + else if (effect.mEffectId == ESM::MagicEffect::TurnUndead) + { + auto& creatureStats = target.getClass().getCreatureStats(target); + Stat stat = creatureStats.getAiSetting(AiSetting::Flee); + stat.setModifier(static_cast(stat.getModifier() - effect.mMagnitude)); + creatureStats.setAiSetting(AiSetting::Flee, stat); + } + else if (effect.mEffectId == ESM::MagicEffect::FrenzyCreature + || effect.mEffectId == ESM::MagicEffect::FrenzyHumanoid) + modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, -effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::CalmCreature + || effect.mEffectId == ESM::MagicEffect::CalmHumanoid) + modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::DemoralizeCreature + || effect.mEffectId == ESM::MagicEffect::DemoralizeHumanoid) + modifyAiSetting(target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, -effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::NightEye) + { + const MWMechanics::EffectParam nightEye = magnitudes.getOrDefault(effect.mEffectId); + if (nightEye.getMagnitude() < 0.f && nightEye.getBase() < 0) + { + // The PCVisionBonus functions are different from every other magic effect function in that they + // clamp the value to [0, 1]. Morrowind.exe applies the same clamping to the night-eye effect, which + // can create situations where an effect is still active (i.e. shown in the menu) but the screen is + // no longer bright. Modifying the base value here should prevent that while preserving their + // function. + float delta = std::clamp(-nightEye.getMagnitude(), 0.f, -static_cast(nightEye.getBase())); + magnitudes.modifyBase(effect.mEffectId, static_cast(delta)); + } + } + else if (effect.mEffectId == ESM::MagicEffect::RallyCreature + || effect.mEffectId == ESM::MagicEffect::RallyHumanoid) + modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::Sound) + { + if (magnitudes.getOrDefault(effect.mEffectId).getModifier() <= 0.f && target == getPlayer()) + MWBase::Environment::get().getSoundManager()->stopSound3D( + target, ESM::RefId::stringRefId("magic sound")); + } + else if (effect.mEffectId == ESM::MagicEffect::SummonScamp + || effect.mEffectId == ESM::MagicEffect::SummonClannfear + || effect.mEffectId == ESM::MagicEffect::SummonDaedroth + || effect.mEffectId == ESM::MagicEffect::SummonDremora + || effect.mEffectId == ESM::MagicEffect::SummonAncestralGhost + || effect.mEffectId == ESM::MagicEffect::SummonSkeletalMinion + || effect.mEffectId == ESM::MagicEffect::SummonBonewalker + || effect.mEffectId == ESM::MagicEffect::SummonGreaterBonewalker + || effect.mEffectId == ESM::MagicEffect::SummonBonelord + || effect.mEffectId == ESM::MagicEffect::SummonWingedTwilight + || effect.mEffectId == ESM::MagicEffect::SummonHunger + || effect.mEffectId == ESM::MagicEffect::SummonGoldenSaint + || effect.mEffectId == ESM::MagicEffect::SummonFlameAtronach + || effect.mEffectId == ESM::MagicEffect::SummonFrostAtronach + || effect.mEffectId == ESM::MagicEffect::SummonStormAtronach + || effect.mEffectId == ESM::MagicEffect::SummonCenturionSphere + || effect.mEffectId == ESM::MagicEffect::SummonFabricant || effect.mEffectId == ESM::MagicEffect::SummonWolf + || effect.mEffectId == ESM::MagicEffect::SummonBear || effect.mEffectId == ESM::MagicEffect::SummonBonewolf + || effect.mEffectId == ESM::MagicEffect::SummonCreature04 + || effect.mEffectId == ESM::MagicEffect::SummonCreature05) + { + ESM::RefNum actor = effect.getActor(); + if (actor.isSet()) + MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(actor); + auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap(); + auto [begin, end] = summons.equal_range(effect.mEffectId); + for (auto it = begin; it != end; ++it) + { + if (it->second == actor) { - auto& seq = target.getClass().getCreatureStats(target).getAiSequence(); - seq.erasePackageIf([&](const auto& package) { - return package->getTypeId() == MWMechanics::AiPackageTypeId::Follow - && static_cast(package.get())->isCommanded(); - }); + summons.erase(it); + break; } - break; - case ESM::MagicEffect::ExtraSpell: - if (magnitudes.getOrDefault(effect.mEffectId).getMagnitude() <= 0.f && target != getPlayer()) - target.getClass().getInventoryStore(target).autoEquip(); - break; - case ESM::MagicEffect::TurnUndead: + } + } + else if (effect.mEffectId == ESM::MagicEffect::BoundGloves) + { + removeBoundItem( + ESM::RefId::stringRefId( + world->getStore().get().find("sMagicBoundRightGauntletID")->mValue.getString()), + target); + removeBoundItem( + ESM::RefId::stringRefId( + world->getStore().get().find("sMagicBoundLeftGauntletID")->mValue.getString()), + target); + } + else if (effect.mEffectId == ESM::MagicEffect::BoundDagger + || effect.mEffectId == ESM::MagicEffect::BoundLongsword || effect.mEffectId == ESM::MagicEffect::BoundMace + || effect.mEffectId == ESM::MagicEffect::BoundBattleAxe || effect.mEffectId == ESM::MagicEffect::BoundSpear + || effect.mEffectId == ESM::MagicEffect::BoundLongbow || effect.mEffectId == ESM::MagicEffect::BoundCuirass + || effect.mEffectId == ESM::MagicEffect::BoundHelm || effect.mEffectId == ESM::MagicEffect::BoundBoots + || effect.mEffectId == ESM::MagicEffect::BoundShield) + { + std::string_view item = getBoundItemsMap().at(effect.mEffectId); + removeBoundItem( + ESM::RefId::stringRefId(world->getStore().get().find(item)->mValue.getString()), + target); + } + else if (effect.mEffectId == ESM::MagicEffect::DrainHealth) + adjustDynamicStat(target, Stats::Health, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::DrainMagicka) + adjustDynamicStat(target, Stats::Magicka, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::DrainFatigue) + adjustDynamicStat(target, Stats::Fatigue, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::FortifyHealth) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Health, -effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Health, -effect.mMagnitude, true); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyMagicka) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Magicka, -effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Magicka, -effect.mMagnitude, true); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyFatigue) + { + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) + modDynamicStat(target, Stats::Fatigue, -effect.mMagnitude); + else + adjustDynamicStat(target, Stats::Fatigue, -effect.mMagnitude, true); + } + else if (effect.mEffectId == ESM::MagicEffect::DrainAttribute) + restoreAttribute(target, effect, effect.mMagnitude); + else if (effect.mEffectId == ESM::MagicEffect::FortifyAttribute) + { + // Abilities affect base stats, but not for drain + if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { auto& creatureStats = target.getClass().getCreatureStats(target); - Stat stat = creatureStats.getAiSetting(AiSetting::Flee); - stat.setModifier(static_cast(stat.getModifier() - effect.mMagnitude)); - creatureStats.setAiSetting(AiSetting::Flee, stat); + auto attribute = effect.getSkillOrAttribute(); + AttributeValue attr = creatureStats.getAttribute(attribute); + attr.setBase(attr.getBase() - effect.mMagnitude); + creatureStats.setAttribute(attribute, attr); } - break; - case ESM::MagicEffect::FrenzyCreature: - case ESM::MagicEffect::FrenzyHumanoid: - modifyAiSetting(target, effect, ESM::MagicEffect::FrenzyCreature, AiSetting::Fight, -effect.mMagnitude); - break; - case ESM::MagicEffect::CalmCreature: - case ESM::MagicEffect::CalmHumanoid: - modifyAiSetting(target, effect, ESM::MagicEffect::CalmCreature, AiSetting::Fight, effect.mMagnitude); - break; - case ESM::MagicEffect::DemoralizeCreature: - case ESM::MagicEffect::DemoralizeHumanoid: - modifyAiSetting( - target, effect, ESM::MagicEffect::DemoralizeCreature, AiSetting::Flee, -effect.mMagnitude); - break; - case ESM::MagicEffect::NightEye: + else + fortifyAttribute(target, effect, -effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::DrainSkill) + { + if (target.getClass().isNpc()) + restoreSkill(target, effect, effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::FortifySkill) + { + if (target.getClass().isNpc()) { - const MWMechanics::EffectParam nightEye = magnitudes.getOrDefault(effect.mEffectId); - if (nightEye.getMagnitude() < 0.f && nightEye.getBase() < 0) - { - // The PCVisionBonus functions are different from every other magic effect function in that they - // clamp the value to [0, 1]. Morrowind.exe applies the same clamping to the night-eye effect, which - // can create situations where an effect is still active (i.e. shown in the menu) but the screen is - // no longer bright. Modifying the base value here should prevent that while preserving their - // function. - float delta = std::clamp(-nightEye.getMagnitude(), 0.f, -static_cast(nightEye.getBase())); - magnitudes.modifyBase(effect.mEffectId, static_cast(delta)); - } - } - break; - case ESM::MagicEffect::RallyCreature: - case ESM::MagicEffect::RallyHumanoid: - modifyAiSetting(target, effect, ESM::MagicEffect::RallyCreature, AiSetting::Flee, effect.mMagnitude); - break; - case ESM::MagicEffect::Sound: - if (magnitudes.getOrDefault(effect.mEffectId).getModifier() <= 0.f && target == getPlayer()) - MWBase::Environment::get().getSoundManager()->stopSound3D( - target, ESM::RefId::stringRefId("magic sound")); - break; - case ESM::MagicEffect::SummonScamp: - case ESM::MagicEffect::SummonClannfear: - case ESM::MagicEffect::SummonDaedroth: - case ESM::MagicEffect::SummonDremora: - case ESM::MagicEffect::SummonAncestralGhost: - case ESM::MagicEffect::SummonSkeletalMinion: - case ESM::MagicEffect::SummonBonewalker: - case ESM::MagicEffect::SummonGreaterBonewalker: - case ESM::MagicEffect::SummonBonelord: - case ESM::MagicEffect::SummonWingedTwilight: - case ESM::MagicEffect::SummonHunger: - case ESM::MagicEffect::SummonGoldenSaint: - case ESM::MagicEffect::SummonFlameAtronach: - case ESM::MagicEffect::SummonFrostAtronach: - case ESM::MagicEffect::SummonStormAtronach: - case ESM::MagicEffect::SummonCenturionSphere: - case ESM::MagicEffect::SummonFabricant: - case ESM::MagicEffect::SummonWolf: - case ESM::MagicEffect::SummonBear: - case ESM::MagicEffect::SummonBonewolf: - case ESM::MagicEffect::SummonCreature04: - case ESM::MagicEffect::SummonCreature05: - { - ESM::RefNum actor = effect.getActor(); - if (actor.isSet()) - MWBase::Environment::get().getMechanicsManager()->cleanupSummonedCreature(actor); - auto& summons = target.getClass().getCreatureStats(target).getSummonedCreatureMap(); - auto [begin, end] = summons.equal_range(effect.mEffectId); - for (auto it = begin; it != end; ++it) - { - if (it->second == actor) - { - summons.erase(it); - break; - } - } - } - break; - case ESM::MagicEffect::BoundGloves: - removeBoundItem(ESM::RefId::stringRefId(world->getStore() - .get() - .find("sMagicBoundRightGauntletID") - ->mValue.getString()), - target); - [[fallthrough]]; - case ESM::MagicEffect::BoundDagger: - case ESM::MagicEffect::BoundLongsword: - case ESM::MagicEffect::BoundMace: - case ESM::MagicEffect::BoundBattleAxe: - case ESM::MagicEffect::BoundSpear: - case ESM::MagicEffect::BoundLongbow: - case ESM::MagicEffect::BoundCuirass: - case ESM::MagicEffect::BoundHelm: - case ESM::MagicEffect::BoundBoots: - case ESM::MagicEffect::BoundShield: - { - const std::string& item = sBoundItemsMap.at(effect.mEffectId); - removeBoundItem( - ESM::RefId::stringRefId(world->getStore().get().find(item)->mValue.getString()), - target); - } - break; - case ESM::MagicEffect::DrainHealth: - case ESM::MagicEffect::DrainMagicka: - case ESM::MagicEffect::DrainFatigue: - adjustDynamicStat(target, effect.mEffectId - ESM::MagicEffect::DrainHealth, effect.mMagnitude); - break; - case ESM::MagicEffect::FortifyHealth: - case ESM::MagicEffect::FortifyMagicka: - case ESM::MagicEffect::FortifyFatigue: - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude); - else - adjustDynamicStat( - target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude, true); - break; - case ESM::MagicEffect::DrainAttribute: - restoreAttribute(target, effect, effect.mMagnitude); - break; - case ESM::MagicEffect::FortifyAttribute: // Abilities affect base stats, but not for drain if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) { - auto& creatureStats = target.getClass().getCreatureStats(target); - auto attribute = effect.getSkillOrAttribute(); - AttributeValue attr = creatureStats.getAttribute(attribute); - attr.setBase(attr.getBase() - effect.mMagnitude); - creatureStats.setAttribute(attribute, attr); + auto& npcStats = target.getClass().getNpcStats(target); + auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); + skill.setBase(skill.getBase() - effect.mMagnitude); } else - fortifyAttribute(target, effect, -effect.mMagnitude); - break; - case ESM::MagicEffect::DrainSkill: - if (target.getClass().isNpc()) - restoreSkill(target, effect, effect.mMagnitude); - break; - case ESM::MagicEffect::FortifySkill: - if (target.getClass().isNpc()) - { - // Abilities affect base stats, but not for drain - if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)) - { - auto& npcStats = target.getClass().getNpcStats(target); - auto& skill = npcStats.getSkill(effect.getSkillOrAttribute()); - skill.setBase(skill.getBase() - effect.mMagnitude); - } - else - fortifySkill(target, effect, -effect.mMagnitude); - } - break; - case ESM::MagicEffect::FortifyMaximumMagicka: - target.getClass().getCreatureStats(target).recalculateMagicka(); - break; - case ESM::MagicEffect::AbsorbAttribute: - { - const auto caster = worldModel->getPtr(spellParams.getCaster()); - restoreAttribute(target, effect, effect.mMagnitude); - if (!caster.isEmpty()) - fortifyAttribute(caster, effect, -effect.mMagnitude); + fortifySkill(target, effect, -effect.mMagnitude); } - break; - case ESM::MagicEffect::AbsorbSkill: + } + else if (effect.mEffectId == ESM::MagicEffect::FortifyMaximumMagicka) + target.getClass().getCreatureStats(target).recalculateMagicka(); + else if (effect.mEffectId == ESM::MagicEffect::AbsorbAttribute) + { + const auto caster = worldModel->getPtr(spellParams.getCaster()); + restoreAttribute(target, effect, effect.mMagnitude); + if (!caster.isEmpty()) + fortifyAttribute(caster, effect, -effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::AbsorbSkill) + { + if (target.getClass().isNpc()) + restoreSkill(target, effect, effect.mMagnitude); + const auto caster = worldModel->getPtr(spellParams.getCaster()); + if (!caster.isEmpty() && caster.getClass().isNpc()) + fortifySkill(caster, effect, -effect.mMagnitude); + } + else if (effect.mEffectId == ESM::MagicEffect::Corprus) + { + int worsenings = spellParams.getWorsenings(); + spellParams.resetWorsenings(); + if (worsenings > 0) { - if (target.getClass().isNpc()) - restoreSkill(target, effect, effect.mMagnitude); - const auto caster = worldModel->getPtr(spellParams.getCaster()); - if (!caster.isEmpty() && caster.getClass().isNpc()) - fortifySkill(caster, effect, -effect.mMagnitude); - } - break; - case ESM::MagicEffect::Corprus: - { - int worsenings = spellParams.getWorsenings(); - spellParams.resetWorsenings(); - if (worsenings > 0) + for (const auto& otherEffect : spellParams.getEffects()) { - for (const auto& otherEffect : spellParams.getEffects()) + if (isCorprusEffect(otherEffect, true)) { - if (isCorprusEffect(otherEffect, true)) - { - for (int i = 0; i < worsenings; i++) - removeMagicEffect(target, spellParams, otherEffect); - } + for (int i = 0; i < worsenings; i++) + removeMagicEffect(target, spellParams, otherEffect); } } - // Note that we remove the effects, but keep the params - target.getClass().getCreatureStats(target).getActiveSpells().purge( - [&spellParams]( - const ActiveSpells::ActiveSpellParams& params, const auto&) { return &spellParams == ¶ms; }, - target); } - break; + // Note that we remove the effects, but keep the params + target.getClass().getCreatureStats(target).getActiveSpells().purge( + [&spellParams]( + const ActiveSpells::ActiveSpellParams& params, const auto&) { return &spellParams == ¶ms; }, + target); } } @@ -1338,7 +1390,7 @@ namespace MWMechanics { auto anim = MWBase::Environment::get().getWorld()->getAnimation(target); if (anim) - anim->removeEffect(ESM::MagicEffect::indexToName(effect.mEffectId)); + anim->removeEffect(effect.mEffectId.getRefIdString()); } } diff --git a/apps/openmw/mwmechanics/spellpriority.cpp b/apps/openmw/mwmechanics/spellpriority.cpp index 1e811a0658..e93fb6f217 100644 --- a/apps/openmw/mwmechanics/spellpriority.cpp +++ b/apps/openmw/mwmechanics/spellpriority.cpp @@ -23,7 +23,14 @@ namespace { - int numEffectsToDispel(const MWWorld::Ptr& actor, int effectFilter = -1, bool negative = true) + enum Stats + { + Health = 0, + Magicka = 1, + Fatigue = 2 + }; + + int numEffectsToDispel(const MWWorld::Ptr& actor, ESM::RefId effectFilter = ESM::RefId(), bool negative = true) { int toCure = 0; const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells(); @@ -31,7 +38,7 @@ namespace { // if the effect filter is not specified, take in account only spells effects. Leave potions, enchanted // items etc. - if (effectFilter == -1) + if (effectFilter.empty()) { const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().search(it->getSourceSpellId()); @@ -42,8 +49,8 @@ namespace const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it; for (const auto& effect : params.getEffects()) { - int effectId = effect.mEffectId; - if (effectFilter != -1 && effectId != effectFilter) + ESM::RefId effectId = effect.mEffectId; + if (!effectFilter.empty() && effectId != effectFilter) continue; const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().find(effectId); @@ -207,404 +214,363 @@ namespace MWMechanics // NOTE: enemy may be empty float rating = 1; - switch (effect.mEffectID) + if (effect.mEffectID == ESM::MagicEffect::Soultrap || effect.mEffectID == ESM::MagicEffect::AlmsiviIntervention + || effect.mEffectID == ESM::MagicEffect::DivineIntervention + || effect.mEffectID == ESM::MagicEffect::CalmHumanoid || effect.mEffectID == ESM::MagicEffect::CalmCreature + || effect.mEffectID == ESM::MagicEffect::FrenzyHumanoid + || effect.mEffectID == ESM::MagicEffect::FrenzyCreature + || effect.mEffectID == ESM::MagicEffect::DemoralizeHumanoid + || effect.mEffectID == ESM::MagicEffect::DemoralizeCreature + || effect.mEffectID == ESM::MagicEffect::RallyHumanoid + || effect.mEffectID == ESM::MagicEffect::RallyCreature || effect.mEffectID == ESM::MagicEffect::Charm + || effect.mEffectID == ESM::MagicEffect::DetectAnimal + || effect.mEffectID == ESM::MagicEffect::DetectEnchantment + || effect.mEffectID == ESM::MagicEffect::DetectKey || effect.mEffectID == ESM::MagicEffect::Telekinesis + || effect.mEffectID == ESM::MagicEffect::Mark || effect.mEffectID == ESM::MagicEffect::Recall + || effect.mEffectID == ESM::MagicEffect::Jump || effect.mEffectID == ESM::MagicEffect::WaterBreathing + || effect.mEffectID == ESM::MagicEffect::SwiftSwim || effect.mEffectID == ESM::MagicEffect::WaterWalking + || effect.mEffectID == ESM::MagicEffect::SlowFall || effect.mEffectID == ESM::MagicEffect::Light + || effect.mEffectID == ESM::MagicEffect::Lock || effect.mEffectID == ESM::MagicEffect::Open + || effect.mEffectID == ESM::MagicEffect::TurnUndead + || effect.mEffectID == ESM::MagicEffect::WeaknessToCommonDisease + || effect.mEffectID == ESM::MagicEffect::WeaknessToBlightDisease + || effect.mEffectID == ESM::MagicEffect::WeaknessToCorprusDisease + || effect.mEffectID == ESM::MagicEffect::CureCommonDisease + || effect.mEffectID == ESM::MagicEffect::CureBlightDisease + || effect.mEffectID == ESM::MagicEffect::CureCorprusDisease + || effect.mEffectID == ESM::MagicEffect::ResistBlightDisease + || effect.mEffectID == ESM::MagicEffect::ResistCommonDisease + || effect.mEffectID == ESM::MagicEffect::ResistCorprusDisease + || effect.mEffectID == ESM::MagicEffect::Invisibility || effect.mEffectID == ESM::MagicEffect::Chameleon + || effect.mEffectID == ESM::MagicEffect::NightEye || effect.mEffectID == ESM::MagicEffect::Vampirism + || effect.mEffectID == ESM::MagicEffect::StuntedMagicka || effect.mEffectID == ESM::MagicEffect::ExtraSpell + || effect.mEffectID == ESM::MagicEffect::RemoveCurse + || effect.mEffectID == ESM::MagicEffect::CommandCreature + || effect.mEffectID == ESM::MagicEffect::CommandHumanoid) + return 0.f; + else if (effect.mEffectID == ESM::MagicEffect::Blind) { - case ESM::MagicEffect::Soultrap: - case ESM::MagicEffect::AlmsiviIntervention: - case ESM::MagicEffect::DivineIntervention: - case ESM::MagicEffect::CalmHumanoid: - case ESM::MagicEffect::CalmCreature: - case ESM::MagicEffect::FrenzyHumanoid: - case ESM::MagicEffect::FrenzyCreature: - case ESM::MagicEffect::DemoralizeHumanoid: - case ESM::MagicEffect::DemoralizeCreature: - case ESM::MagicEffect::RallyHumanoid: - case ESM::MagicEffect::RallyCreature: - case ESM::MagicEffect::Charm: - case ESM::MagicEffect::DetectAnimal: - case ESM::MagicEffect::DetectEnchantment: - case ESM::MagicEffect::DetectKey: - case ESM::MagicEffect::Telekinesis: - case ESM::MagicEffect::Mark: - case ESM::MagicEffect::Recall: - case ESM::MagicEffect::Jump: - case ESM::MagicEffect::WaterBreathing: - case ESM::MagicEffect::SwiftSwim: - case ESM::MagicEffect::WaterWalking: - case ESM::MagicEffect::SlowFall: - case ESM::MagicEffect::Light: - case ESM::MagicEffect::Lock: - case ESM::MagicEffect::Open: - case ESM::MagicEffect::TurnUndead: - case ESM::MagicEffect::WeaknessToCommonDisease: - case ESM::MagicEffect::WeaknessToBlightDisease: - case ESM::MagicEffect::WeaknessToCorprusDisease: - case ESM::MagicEffect::CureCommonDisease: - case ESM::MagicEffect::CureBlightDisease: - case ESM::MagicEffect::CureCorprusDisease: - case ESM::MagicEffect::ResistBlightDisease: - case ESM::MagicEffect::ResistCommonDisease: - case ESM::MagicEffect::ResistCorprusDisease: - case ESM::MagicEffect::Invisibility: - case ESM::MagicEffect::Chameleon: - case ESM::MagicEffect::NightEye: - case ESM::MagicEffect::Vampirism: - case ESM::MagicEffect::StuntedMagicka: - case ESM::MagicEffect::ExtraSpell: - case ESM::MagicEffect::RemoveCurse: - case ESM::MagicEffect::CommandCreature: - case ESM::MagicEffect::CommandHumanoid: + if (enemy.isEmpty()) return 0.f; - case ESM::MagicEffect::Blind: - { - if (enemy.isEmpty()) - return 0.f; + const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); - const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); - - // Enemy can't attack - if (stats.isParalyzed() || stats.getKnockedDown()) - return 0.f; - - // Enemy doesn't attack - if (stats.getDrawState() != MWMechanics::DrawState::Weapon) - return 0.f; - - break; - } - - case ESM::MagicEffect::Sound: - { - if (enemy.isEmpty()) - return 0.f; - - const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); - - // Enemy can't cast spells - if (stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Silence).getMagnitude() > 0) - return 0.f; - - if (stats.isParalyzed() || stats.getKnockedDown()) - return 0.f; - - // Enemy doesn't cast spells - if (stats.getDrawState() != MWMechanics::DrawState::Spell) - return 0.f; - - break; - } - - case ESM::MagicEffect::Silence: - { - if (enemy.isEmpty()) - return 0.f; - - const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); - - // Enemy can't cast spells - if (stats.isParalyzed() || stats.getKnockedDown()) - return 0.f; - - // Enemy doesn't cast spells - if (stats.getDrawState() != MWMechanics::DrawState::Spell) - return 0.f; - break; - } - - case ESM::MagicEffect::RestoreAttribute: - return 0.f; // TODO: implement based on attribute damage - case ESM::MagicEffect::RestoreSkill: - return 0.f; // TODO: implement based on skill damage - - case ESM::MagicEffect::ResistFire: - case ESM::MagicEffect::ResistFrost: - case ESM::MagicEffect::ResistMagicka: - case ESM::MagicEffect::ResistNormalWeapons: - case ESM::MagicEffect::ResistParalysis: - case ESM::MagicEffect::ResistPoison: - case ESM::MagicEffect::ResistShock: - case ESM::MagicEffect::SpellAbsorption: - case ESM::MagicEffect::Reflect: - return 0.f; // probably useless since we don't know in advance what the enemy will cast - - // don't cast these for now as they would make the NPC cast the same effect over and over again, especially - // when they have potions - case ESM::MagicEffect::FortifyAttribute: - case ESM::MagicEffect::FortifyHealth: - case ESM::MagicEffect::FortifyMagicka: - case ESM::MagicEffect::FortifyFatigue: - case ESM::MagicEffect::FortifySkill: - case ESM::MagicEffect::FortifyMaximumMagicka: - case ESM::MagicEffect::FortifyAttack: + // Enemy can't attack + if (stats.isParalyzed() || stats.getKnockedDown()) return 0.f; - case ESM::MagicEffect::Burden: + // Enemy doesn't attack + if (stats.getDrawState() != MWMechanics::DrawState::Weapon) + return 0.f; + } + else if (effect.mEffectID == ESM::MagicEffect::Sound) + { + if (enemy.isEmpty()) + return 0.f; + + const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + + // Enemy can't cast spells + if (stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Silence).getMagnitude() > 0) + return 0.f; + + if (stats.isParalyzed() || stats.getKnockedDown()) + return 0.f; + + // Enemy doesn't cast spells + if (stats.getDrawState() != MWMechanics::DrawState::Spell) + return 0.f; + } + else if (effect.mEffectID == ESM::MagicEffect::Silence) + { + if (enemy.isEmpty()) + return 0.f; + + const CreatureStats& stats = enemy.getClass().getCreatureStats(enemy); + + // Enemy can't cast spells + if (stats.isParalyzed() || stats.getKnockedDown()) + return 0.f; + + // Enemy doesn't cast spells + if (stats.getDrawState() != MWMechanics::DrawState::Spell) + return 0.f; + } + else if (effect.mEffectID == ESM::MagicEffect::RestoreAttribute) + return 0.f; // TODO: implement based on attribute damage + else if (effect.mEffectID == ESM::MagicEffect::RestoreSkill) + return 0.f; // TODO: implement based on skill damage + else if (effect.mEffectID == ESM::MagicEffect::ResistFire || effect.mEffectID == ESM::MagicEffect::ResistFrost + || effect.mEffectID == ESM::MagicEffect::ResistMagicka + || effect.mEffectID == ESM::MagicEffect::ResistNormalWeapons + || effect.mEffectID == ESM::MagicEffect::ResistParalysis + || effect.mEffectID == ESM::MagicEffect::ResistPoison || effect.mEffectID == ESM::MagicEffect::ResistShock + || effect.mEffectID == ESM::MagicEffect::SpellAbsorption || effect.mEffectID == ESM::MagicEffect::Reflect) + return 0.f; // probably useless since we don't know in advance what the enemy will cast + + // don't cast these for now as they would make the NPC cast the same effect over and over again, especially + // when they have potions + else if (effect.mEffectID == ESM::MagicEffect::FortifyAttribute + || effect.mEffectID == ESM::MagicEffect::FortifyHealth + || effect.mEffectID == ESM::MagicEffect::FortifyMagicka + || effect.mEffectID == ESM::MagicEffect::FortifyFatigue + || effect.mEffectID == ESM::MagicEffect::FortifySkill + || effect.mEffectID == ESM::MagicEffect::FortifyMaximumMagicka + || effect.mEffectID == ESM::MagicEffect::FortifyAttack) + return 0.f; + else if (effect.mEffectID == ESM::MagicEffect::Burden) + { + if (enemy.isEmpty()) + return 0.f; + + // Ignore enemy without inventory + if (!enemy.getClass().hasInventoryStore(enemy)) + return 0.f; + + // burden makes sense only to overburden an enemy + float burden = enemy.getClass().getEncumbrance(enemy) - enemy.getClass().getCapacity(enemy); + if (burden > 0) + return 0.f; + + if ((effect.mMagnMin + effect.mMagnMax) / 2.f > -burden) + rating *= 3; + else + return 0.f; + } + else if (effect.mEffectID == ESM::MagicEffect::Feather) + { + // Ignore actors without inventory + if (!actor.getClass().hasInventoryStore(actor)) + return 0.f; + + // feather makes sense only for overburden actors + float burden = actor.getClass().getEncumbrance(actor) - actor.getClass().getCapacity(actor); + if (burden <= 0) + return 0.f; + + if ((effect.mMagnMin + effect.mMagnMax) / 2.f >= burden) + rating *= 3; + else + return 0.f; + } + else if (effect.mEffectID == ESM::MagicEffect::Levitate) + return 0.f; // AI isn't designed to take advantage of this, and could be perceived as unfair anyway + else if (effect.mEffectID == ESM::MagicEffect::BoundBoots || effect.mEffectID == ESM::MagicEffect::BoundHelm) + { + if (actor.getClass().isNpc()) + { + // Beast races can't wear helmets or boots + const ESM::RefId& raceid = actor.get()->mBase->mRace; + const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(raceid); + if (race->mData.mFlags & ESM::Race::Beast) + return 0.f; + } + else + return 0.f; + } + else if (effect.mEffectID == ESM::MagicEffect::BoundShield) + { + if (!actor.getClass().hasInventoryStore(actor)) + return 0.f; + else if (!actor.getClass().isNpc()) + { + // If the actor is an NPC they can benefit from the armor rating, otherwise check if we've got a + // one-handed weapon to use with the shield + const auto& store = actor.getClass().getInventoryStore(actor); + auto oneHanded = std::find_if(store.cbegin(MWWorld::ContainerStore::Type_Weapon), store.cend(), + [](const MWWorld::ConstPtr& weapon) { + if (weapon.getClass().getItemHealth(weapon) <= 0.f) + return false; + short type = weapon.get()->mBase->mData.mType; + return !(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded); + }); + if (oneHanded == store.cend()) + return 0.f; + } + } + // Creatures cannot wear armor + else if (effect.mEffectID == ESM::MagicEffect::BoundCuirass + || effect.mEffectID == ESM::MagicEffect::BoundGloves) + { + if (!actor.getClass().isNpc()) + return 0.f; + } + else if (effect.mEffectID == ESM::MagicEffect::AbsorbMagicka) + { + if (!enemy.isEmpty() && enemy.getClass().getCreatureStats(enemy).getMagicka().getCurrent() <= 0.f) + { + rating = 0.5f; + rating *= getRestoreMagickaPriority(actor); + } + } + else if (effect.mEffectID == ESM::MagicEffect::RestoreHealth + || effect.mEffectID == ESM::MagicEffect::RestoreMagicka + || effect.mEffectID == ESM::MagicEffect::RestoreFatigue) + { + if (effect.mRange == ESM::RT_Self) + { + auto targetStat = Stats::Health; + if (effect.mEffectID == ESM::MagicEffect::RestoreMagicka) + targetStat = Stats::Magicka; + else if (effect.mEffectID == ESM::MagicEffect::RestoreFatigue) + targetStat = Stats::Fatigue; + + const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); + const DynamicStat& current = stats.getDynamic(targetStat); + // NB: this currently assumes the hardcoded magic effect flags are used + const float magnitude = (effect.mMagnMin + effect.mMagnMax) / 2.f; + const float toHeal = magnitude * std::max(1, effect.mDuration); + const float damage = std::max(current.getModified() - current.getCurrent(), 0.f); + float priority = 0.f; + if (effect.mEffectID == ESM::MagicEffect::RestoreHealth) + priority = 4.f; + else if (effect.mEffectID == ESM::MagicEffect::RestoreMagicka) + priority = getRestoreMagickaPriority(actor); + else if (effect.mEffectID == ESM::MagicEffect::RestoreFatigue) + priority = 2.f; + float overheal = 0.f; + float heal = toHeal; + if (damage < toHeal && current.getCurrent() > current.getModified() * 0.5) + { + overheal = toHeal - damage; + heal = damage; + } + + priority = (priority - 1.f) / 2.f * std::pow((damage / current.getModified() + 0.6f), priority * 2) + + priority * (heal - 2.f * overheal) / current.getModified() - 0.5f; + rating = priority; + } + } + else if (effect.mEffectID == ESM::MagicEffect::Dispel) + { + int numPositive = 0; + int numNegative = 0; + int diff = 0; + + if (effect.mRange == ESM::RT_Self) + { + numPositive = numEffectsToDispel(actor, ESM::RefId(), false); + numNegative = numEffectsToDispel(actor); + + diff = numNegative - numPositive; + } + else { if (enemy.isEmpty()) return 0.f; - // Ignore enemy without inventory - if (!enemy.getClass().hasInventoryStore(enemy)) - return 0.f; + numPositive = numEffectsToDispel(enemy, ESM::RefId(), false); + numNegative = numEffectsToDispel(enemy); - // burden makes sense only to overburden an enemy - float burden = enemy.getClass().getEncumbrance(enemy) - enemy.getClass().getCapacity(enemy); - if (burden > 0) - return 0.f; + diff = numPositive - numNegative; - if ((effect.mMagnMin + effect.mMagnMax) / 2.f > -burden) - rating *= 3; - else - return 0.f; - - break; + // if rating < 0 here, the spell will be considered as negative later + rating *= -1; } - case ESM::MagicEffect::Feather: + if (diff <= 0) + return 0.f; + + rating *= (diff) / 5.f; + } + + // Prefer Cure effects over Dispel, because Dispel also removes positive effects + else if (effect.mEffectID == ESM::MagicEffect::CureParalyzation) + return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Paralyze); + else if (effect.mEffectID == ESM::MagicEffect::CurePoison) + return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Poison); + else if (effect.mEffectID == ESM::MagicEffect::DisintegrateArmor) + { + if (enemy.isEmpty()) + return 0.f; + + // Ignore enemy without inventory + if (!enemy.getClass().hasInventoryStore(enemy)) + return 0.f; + + MWWorld::InventoryStore& inv = enemy.getClass().getInventoryStore(enemy); + + // According to UESP + static const int armorSlots[] = { + MWWorld::InventoryStore::Slot_CarriedLeft, + MWWorld::InventoryStore::Slot_Cuirass, + MWWorld::InventoryStore::Slot_LeftPauldron, + MWWorld::InventoryStore::Slot_RightPauldron, + MWWorld::InventoryStore::Slot_LeftGauntlet, + MWWorld::InventoryStore::Slot_RightGauntlet, + MWWorld::InventoryStore::Slot_Helmet, + MWWorld::InventoryStore::Slot_Greaves, + MWWorld::InventoryStore::Slot_Boots, + }; + + bool enemyHasArmor = false; + + // Ignore enemy without armor + for (unsigned int i = 0; i < sizeof(armorSlots) / sizeof(int); ++i) { - // Ignore actors without inventory - if (!actor.getClass().hasInventoryStore(actor)) - return 0.f; + MWWorld::ContainerStoreIterator item = inv.getSlot(armorSlots[i]); - // feather makes sense only for overburden actors - float burden = actor.getClass().getEncumbrance(actor) - actor.getClass().getCapacity(actor); - if (burden <= 0) - return 0.f; - - if ((effect.mMagnMin + effect.mMagnMax) / 2.f >= burden) - rating *= 3; - else - return 0.f; - - break; + if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor)) + { + enemyHasArmor = true; + break; + } } - case ESM::MagicEffect::Levitate: - return 0.f; // AI isn't designed to take advantage of this, and could be perceived as unfair anyway - case ESM::MagicEffect::BoundBoots: - case ESM::MagicEffect::BoundHelm: - if (actor.getClass().isNpc()) - { - // Beast races can't wear helmets or boots - const ESM::RefId& raceid = actor.get()->mBase->mRace; - const ESM::Race* race = MWBase::Environment::get().getESMStore()->get().find(raceid); - if (race->mData.mFlags & ESM::Race::Beast) - return 0.f; - } - else - return 0.f; + if (!enemyHasArmor) + return 0.f; + } + else if (effect.mEffectID == ESM::MagicEffect::DisintegrateWeapon) + { + if (enemy.isEmpty()) + return 0.f; - break; - case ESM::MagicEffect::BoundShield: - if (!actor.getClass().hasInventoryStore(actor)) - return 0.f; - else if (!actor.getClass().isNpc()) - { - // If the actor is an NPC they can benefit from the armor rating, otherwise check if we've got a - // one-handed weapon to use with the shield - const auto& store = actor.getClass().getInventoryStore(actor); - auto oneHanded = std::find_if(store.cbegin(MWWorld::ContainerStore::Type_Weapon), store.cend(), - [](const MWWorld::ConstPtr& weapon) { - if (weapon.getClass().getItemHealth(weapon) <= 0.f) - return false; - short type = weapon.get()->mBase->mData.mType; - return !(MWMechanics::getWeaponType(type)->mFlags & ESM::WeaponType::TwoHanded); - }); - if (oneHanded == store.cend()) - return 0.f; - } - break; - // Creatures can not wear armor - case ESM::MagicEffect::BoundCuirass: - case ESM::MagicEffect::BoundGloves: - if (!actor.getClass().isNpc()) - return 0.f; - break; + // Ignore enemy without inventory + if (!enemy.getClass().hasInventoryStore(enemy)) + return 0.f; - case ESM::MagicEffect::AbsorbMagicka: - if (!enemy.isEmpty() && enemy.getClass().getCreatureStats(enemy).getMagicka().getCurrent() <= 0.f) - { - rating = 0.5f; - rating *= getRestoreMagickaPriority(actor); - } - break; - case ESM::MagicEffect::RestoreHealth: - case ESM::MagicEffect::RestoreMagicka: - case ESM::MagicEffect::RestoreFatigue: - if (effect.mRange == ESM::RT_Self) - { - const MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor); - const DynamicStat& current - = stats.getDynamic(effect.mEffectID - ESM::MagicEffect::RestoreHealth); - // NB: this currently assumes the hardcoded magic effect flags are used - const float magnitude = (effect.mMagnMin + effect.mMagnMax) / 2.f; - const float toHeal = magnitude * std::max(1, effect.mDuration); - const float damage = std::max(current.getModified() - current.getCurrent(), 0.f); - float priority = 0.f; - if (effect.mEffectID == ESM::MagicEffect::RestoreHealth) - priority = 4.f; - else if (effect.mEffectID == ESM::MagicEffect::RestoreMagicka) - priority = getRestoreMagickaPriority(actor); - else if (effect.mEffectID == ESM::MagicEffect::RestoreFatigue) - priority = 2.f; - float overheal = 0.f; - float heal = toHeal; - if (damage < toHeal && current.getCurrent() > current.getModified() * 0.5) - { - overheal = toHeal - damage; - heal = damage; - } + MWWorld::InventoryStore& inv = enemy.getClass().getInventoryStore(enemy); + MWWorld::ContainerStoreIterator item = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - priority = (priority - 1.f) / 2.f * std::pow((damage / current.getModified() + 0.6f), priority * 2) - + priority * (heal - 2.f * overheal) / current.getModified() - 0.5f; - rating = priority; - } - break; - - case ESM::MagicEffect::Dispel: + // Ignore enemy without weapons + if (item == inv.end() || (item.getType() != MWWorld::ContainerStore::Type_Weapon)) + return 0.f; + } + else if (effect.mEffectID == ESM::MagicEffect::AbsorbAttribute + || effect.mEffectID == ESM::MagicEffect::DamageAttribute + || effect.mEffectID == ESM::MagicEffect::DrainAttribute) + { + if (!enemy.isEmpty() + && enemy.getClass() + .getCreatureStats(enemy) + .getAttribute(ESM::Attribute::indexToRefId(effect.mAttribute)) + .getModified() + <= 0) + return 0.f; { - int numPositive = 0; - int numNegative = 0; - int diff = 0; - - if (effect.mRange == ESM::RT_Self) + if (effect.mAttribute >= 0 && effect.mAttribute < ESM::Attribute::Length) { - numPositive = numEffectsToDispel(actor, -1, false); - numNegative = numEffectsToDispel(actor); - - diff = numNegative - numPositive; + const float attributePriorities[ESM::Attribute::Length] = { + 1.0f, // Strength + 0.5f, // Intelligence + 0.6f, // Willpower + 0.7f, // Agility + 0.5f, // Speed + 0.8f, // Endurance + 0.7f, // Personality + 0.3f // Luck + }; + rating *= attributePriorities[effect.mAttribute]; } - else - { - if (enemy.isEmpty()) - return 0.f; - - numPositive = numEffectsToDispel(enemy, -1, false); - numNegative = numEffectsToDispel(enemy); - - diff = numPositive - numNegative; - - // if rating < 0 here, the spell will be considered as negative later - rating *= -1; - } - - if (diff <= 0) - return 0.f; - - rating *= (diff) / 5.f; - - break; } - - // Prefer Cure effects over Dispel, because Dispel also removes positive effects - case ESM::MagicEffect::CureParalyzation: - return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Paralyze); - - case ESM::MagicEffect::CurePoison: - return 1001.f * numEffectsToDispel(actor, ESM::MagicEffect::Poison); - case ESM::MagicEffect::DisintegrateArmor: - { - if (enemy.isEmpty()) - return 0.f; - - // Ignore enemy without inventory - if (!enemy.getClass().hasInventoryStore(enemy)) - return 0.f; - - MWWorld::InventoryStore& inv = enemy.getClass().getInventoryStore(enemy); - - // According to UESP - static const int armorSlots[] = { - MWWorld::InventoryStore::Slot_CarriedLeft, - MWWorld::InventoryStore::Slot_Cuirass, - MWWorld::InventoryStore::Slot_LeftPauldron, - MWWorld::InventoryStore::Slot_RightPauldron, - MWWorld::InventoryStore::Slot_LeftGauntlet, - MWWorld::InventoryStore::Slot_RightGauntlet, - MWWorld::InventoryStore::Slot_Helmet, - MWWorld::InventoryStore::Slot_Greaves, - MWWorld::InventoryStore::Slot_Boots, - }; - - bool enemyHasArmor = false; - - // Ignore enemy without armor - for (unsigned int i = 0; i < sizeof(armorSlots) / sizeof(int); ++i) - { - MWWorld::ContainerStoreIterator item = inv.getSlot(armorSlots[i]); - - if (item != inv.end() && (item.getType() == MWWorld::ContainerStore::Type_Armor)) - { - enemyHasArmor = true; - break; - } - } - - if (!enemyHasArmor) - return 0.f; - - break; - } - - case ESM::MagicEffect::DisintegrateWeapon: - { - if (enemy.isEmpty()) - return 0.f; - - // Ignore enemy without inventory - if (!enemy.getClass().hasInventoryStore(enemy)) - return 0.f; - - MWWorld::InventoryStore& inv = enemy.getClass().getInventoryStore(enemy); - MWWorld::ContainerStoreIterator item = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - - // Ignore enemy without weapons - if (item == inv.end() || (item.getType() != MWWorld::ContainerStore::Type_Weapon)) - return 0.f; - - break; - } - - case ESM::MagicEffect::AbsorbAttribute: - case ESM::MagicEffect::DamageAttribute: - case ESM::MagicEffect::DrainAttribute: - if (!enemy.isEmpty() - && enemy.getClass() - .getCreatureStats(enemy) - .getAttribute(ESM::Attribute::indexToRefId(effect.mAttribute)) - .getModified() - <= 0) - return 0.f; - { - if (effect.mAttribute >= 0 && effect.mAttribute < ESM::Attribute::Length) - { - const float attributePriorities[ESM::Attribute::Length] = { - 1.0f, // Strength - 0.5f, // Intelligence - 0.6f, // Willpower - 0.7f, // Agility - 0.5f, // Speed - 0.8f, // Endurance - 0.7f, // Personality - 0.3f // Luck - }; - rating *= attributePriorities[effect.mAttribute]; - } - } - break; - - case ESM::MagicEffect::AbsorbSkill: - case ESM::MagicEffect::DamageSkill: - case ESM::MagicEffect::DrainSkill: - if (enemy.isEmpty() || !enemy.getClass().isNpc()) - return 0.f; - if (enemy.getClass().getSkill(enemy, ESM::Skill::indexToRefId(effect.mSkill)) <= 0) - return 0.f; - break; - - default: - break; + } + else if (effect.mEffectID == ESM::MagicEffect::AbsorbSkill || effect.mEffectID == ESM::MagicEffect::DamageSkill + || effect.mEffectID == ESM::MagicEffect::DrainSkill) + { + if (enemy.isEmpty() || !enemy.getClass().isNpc()) + return 0.f; + if (enemy.getClass().getSkill(enemy, ESM::Skill::indexToRefId(effect.mSkill)) <= 0) + return 0.f; } // Allow only one summoned creature at time @@ -617,7 +583,27 @@ namespace MWMechanics // But rate summons higher than other effects rating = 3.f; } - if (effect.mEffectID >= ESM::MagicEffect::BoundDagger && effect.mEffectID <= ESM::MagicEffect::BoundGloves) + + static const std::array boundWeapons{ + ESM::MagicEffect::BoundDagger, + ESM::MagicEffect::BoundLongsword, + ESM::MagicEffect::BoundMace, + ESM::MagicEffect::BoundBattleAxe, + ESM::MagicEffect::BoundSpear, + ESM::MagicEffect::BoundLongbow, + }; + + static const std::array boundArmor{ + ESM::MagicEffect::ExtraSpell, + ESM::MagicEffect::BoundCuirass, + ESM::MagicEffect::BoundHelm, + ESM::MagicEffect::BoundBoots, + ESM::MagicEffect::BoundShield, + ESM::MagicEffect::BoundGloves, + }; + + if (std::ranges::find(boundWeapons, effect.mEffectID) != boundWeapons.end() + || std::ranges::find(boundArmor, effect.mEffectID) != boundArmor.end()) { // Prefer casting bound items over other spells rating = 2.f; @@ -627,9 +613,9 @@ namespace MWMechanics // summon another of a different kind unless what we have is a bow and the actor is out of ammo. // FIXME: This code assumes the summoned item is of the usual type (i.e. a mod hasn't changed Bound Bow to // summon an Axe instead) - if (effect.mEffectID <= ESM::MagicEffect::BoundLongbow) + if (std::ranges::find(boundWeapons, effect.mEffectID) != boundWeapons.end()) { - for (int e = ESM::MagicEffect::BoundDagger; e <= ESM::MagicEffect::BoundLongbow; ++e) + for (const auto e : boundWeapons) if (actor.getClass().getCreatureStats(actor).getMagicEffects().getOrDefault(e).getMagnitude() > 0.f && (e != ESM::MagicEffect::BoundLongbow || effect.mEffectID == e || rateAmmo(actor, enemy, getWeaponType(ESM::Weapon::MarksmanBow)->mAmmoType) <= 0.f)) diff --git a/apps/openmw/mwmechanics/spellresistance.cpp b/apps/openmw/mwmechanics/spellresistance.cpp index e55d851ec6..abb3977340 100644 --- a/apps/openmw/mwmechanics/spellresistance.cpp +++ b/apps/openmw/mwmechanics/spellresistance.cpp @@ -16,7 +16,7 @@ namespace MWMechanics { - float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + float getEffectMultiplier(ESM::RefId effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell, const MagicEffects* effects) { if (!actor.getClass().isActor()) @@ -26,11 +26,11 @@ namespace MWMechanics return 1 - resistance / 100.f; } - float getEffectResistance(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + float getEffectResistance(ESM::RefId effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell, const MagicEffects* effects) { // Effects with no resistance attribute belonging to them can not be resisted - if (ESM::MagicEffect::getResistanceEffect(effectId) == -1) + if (ESM::MagicEffect::getResistanceEffect(effectId).empty()) return 0.f; const auto magicEffect = MWBase::Environment::get().getESMStore()->get().find(effectId); @@ -72,15 +72,16 @@ namespace MWMechanics return x; } - float getEffectResistanceAttribute(short effectId, const MagicEffects* actorEffects) + float getEffectResistanceAttribute(ESM::RefId effectId, const MagicEffects* actorEffects) { - short resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); - short weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); - float resistance = 0; - if (resistanceEffect != -1) + + ESM::RefId resistanceEffect = ESM::MagicEffect::getResistanceEffect(effectId); + ESM::RefId weaknessEffect = ESM::MagicEffect::getWeaknessEffect(effectId); + + if (!resistanceEffect.empty()) resistance += actorEffects->getOrDefault(resistanceEffect).getMagnitude(); - if (weaknessEffect != -1) + if (!weaknessEffect.empty()) resistance -= actorEffects->getOrDefault(weaknessEffect).getMagnitude(); if (effectId == ESM::MagicEffect::FireDamage) @@ -92,5 +93,4 @@ namespace MWMechanics return resistance; } - } diff --git a/apps/openmw/mwmechanics/spellresistance.hpp b/apps/openmw/mwmechanics/spellresistance.hpp index 6966a456d1..0d2ad45523 100644 --- a/apps/openmw/mwmechanics/spellresistance.hpp +++ b/apps/openmw/mwmechanics/spellresistance.hpp @@ -4,6 +4,7 @@ namespace ESM { struct Spell; + class RefId; } namespace MWWorld @@ -19,19 +20,19 @@ namespace MWMechanics /// @return effect multiplier from 0 to 2. (100% net resistance to 100% net weakness) /// @param effects Override the actor's current magicEffects. Useful if there are effects currently /// being applied (but not applied yet) that should also be considered. - float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + float getEffectMultiplier(ESM::RefId effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); /// Get the effective resistance against an effect casted by the given actor in the given spell (optional). /// @return >=100 for fully resisted. can also return negative value for damage amplification. /// @param effects Override the actor's current magicEffects. Useful if there are effects currently /// being applied (but not applied yet) that should also be considered. - float getEffectResistance(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, + float getEffectResistance(ESM::RefId effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = nullptr, const MagicEffects* effects = nullptr); /// Get the resistance attribute against an effect for a given actor. This will add together /// ResistX and Weakness to X effects relevant against the given effect. - float getEffectResistanceAttribute(short effectId, const MagicEffects* actorEffects); + float getEffectResistanceAttribute(ESM::RefId effectId, const MagicEffects* actorEffects); } #endif diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index f336688c1a..203575e7ab 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -257,7 +257,7 @@ namespace MWMechanics for (const ESM::SpellState::PermanentSpellEffectInfo& info : it->second) { // Applied corprus effects are already in loaded stats modifiers - if (info.mId == ESM::MagicEffect::FortifyAttribute) + if (info.mId == ESM::MagicEffect::refIdToIndex(ESM::MagicEffect::FortifyAttribute)) { auto id = ESM::Attribute::indexToRefId(info.mArg); AttributeValue attr = creatureStats->getAttribute(id); @@ -265,7 +265,7 @@ namespace MWMechanics attr.damage(-info.mMagnitude); creatureStats->setAttribute(id, attr); } - else if (info.mId == ESM::MagicEffect::DrainAttribute) + else if (info.mId == ESM::MagicEffect::refIdToIndex(ESM::MagicEffect::DrainAttribute)) { auto id = ESM::Attribute::indexToRefId(info.mArg); AttributeValue attr = creatureStats->getAttribute(id); diff --git a/apps/openmw/mwmechanics/spellutil.cpp b/apps/openmw/mwmechanics/spellutil.cpp index 8dca5f3a00..67e9084b29 100644 --- a/apps/openmw/mwmechanics/spellutil.cpp +++ b/apps/openmw/mwmechanics/spellutil.cpp @@ -166,13 +166,13 @@ namespace MWMechanics throw std::range_error("Index out of range"); ESM::ENAMstruct effect; - effect.mEffectID = static_cast(ingredient->mData.mEffectID[index]); + effect.mEffectID = ingredient->mData.mEffectID[index]; effect.mSkill = static_cast(ingredient->mData.mSkills[index]); effect.mAttribute = static_cast(ingredient->mData.mAttributes[index]); effect.mRange = ESM::RT_Self; effect.mArea = 0; - if (effect.mEffectID < 0) + if (effect.mEffectID.empty()) return std::nullopt; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 9f7b59b030..047e1ce810 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -23,21 +23,45 @@ namespace MWMechanics { - bool isSummoningEffect(int effectId) + bool isSummoningEffect(ESM::RefId effectId) { - return ((effectId >= ESM::MagicEffect::SummonScamp && effectId <= ESM::MagicEffect::SummonStormAtronach) - || (effectId == ESM::MagicEffect::SummonCenturionSphere) - || (effectId >= ESM::MagicEffect::SummonFabricant && effectId <= ESM::MagicEffect::SummonCreature05)); + if (effectId.empty()) + return false; + static const std::array summonEffects{ + ESM::MagicEffect::SummonAncestralGhost, + ESM::MagicEffect::SummonBonelord, + ESM::MagicEffect::SummonBonewalker, + ESM::MagicEffect::SummonCenturionSphere, + ESM::MagicEffect::SummonClannfear, + ESM::MagicEffect::SummonDaedroth, + ESM::MagicEffect::SummonDremora, + ESM::MagicEffect::SummonFabricant, + ESM::MagicEffect::SummonFlameAtronach, + ESM::MagicEffect::SummonFrostAtronach, + ESM::MagicEffect::SummonGoldenSaint, + ESM::MagicEffect::SummonGreaterBonewalker, + ESM::MagicEffect::SummonHunger, + ESM::MagicEffect::SummonScamp, + ESM::MagicEffect::SummonSkeletalMinion, + ESM::MagicEffect::SummonStormAtronach, + ESM::MagicEffect::SummonWingedTwilight, + ESM::MagicEffect::SummonWolf, + ESM::MagicEffect::SummonBear, + ESM::MagicEffect::SummonBonewolf, + ESM::MagicEffect::SummonCreature04, + ESM::MagicEffect::SummonCreature05, + }; + return (std::find(summonEffects.begin(), summonEffects.end(), effectId) != summonEffects.end()); } - static const std::map& getSummonMap() + static const std::map& getSummonMap() { - static std::map summonMap; + static std::map summonMap; if (summonMap.size() > 0) return summonMap; - const std::map summonMapToGameSetting{ + const std::map summonMapToGameSetting{ { ESM::MagicEffect::SummonAncestralGhost, "sMagicAncestralGhostID" }, { ESM::MagicEffect::SummonBonelord, "sMagicBonelordID" }, { ESM::MagicEffect::SummonBonewalker, "sMagicLeastBonewalkerID" }, @@ -70,7 +94,7 @@ namespace MWMechanics return summonMap; } - ESM::RefId getSummonedCreature(int effectId) + ESM::RefId getSummonedCreature(ESM::RefId effectId) { const auto& summonMap = getSummonMap(); auto it = summonMap.find(effectId); @@ -81,7 +105,7 @@ namespace MWMechanics return ESM::RefId(); } - ESM::RefNum summonCreature(int effectId, const MWWorld::Ptr& summoner) + ESM::RefNum summonCreature(ESM::RefId effectId, const MWWorld::Ptr& summoner) { const ESM::RefId& creatureID = getSummonedCreature(effectId); ESM::RefNum creature; @@ -152,7 +176,7 @@ namespace MWMechanics } } - void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon) { auto& creatureStats = summoner.getClass().getCreatureStats(summoner); creatureStats.getActiveSpells().purge( diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp index 3992f8399f..88ab6d0e42 100644 --- a/apps/openmw/mwmechanics/summoning.hpp +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -17,13 +17,13 @@ namespace MWWorld namespace MWMechanics { - bool isSummoningEffect(int effectId); + bool isSummoningEffect(ESM::RefId effectId); - ESM::RefId getSummonedCreature(int effectId); + ESM::RefId getSummonedCreature(ESM::RefId effectId); - void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); + void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair& summon); - ESM::RefNum summonCreature(int effectId, const MWWorld::Ptr& summoner); + ESM::RefNum summonCreature(ESM::RefId effectId, const MWWorld::Ptr& summoner); void updateSummons(const MWWorld::Ptr& summoner, bool cleanup); } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index cbc76e8743..a810baa3e9 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -613,13 +613,13 @@ namespace MWScript return; } - long key; + ESM::RefId key; if (const auto k = ::Misc::StringUtils::toNumeric(effectName); k.has_value() && *k >= 0 && *k <= 32767) - key = *k; + key = ESM::MagicEffect::indexToRefId(*k); else - key = ESM::MagicEffect::effectGmstIdToIndex(effectName); + key = ESM::MagicEffect::effectGmstIdToRefId(effectName); const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); for (const auto& spell : stats.getActiveSpells()) diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 195e6a5404..edb43be22d 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -580,7 +580,8 @@ namespace MWScript runtime.pop(); if (ptr.getClass().isActor()) - ptr.getClass().getCreatureStats(ptr).getActiveSpells().purgeEffect(ptr, effectId); + ptr.getClass().getCreatureStats(ptr).getActiveSpells().purgeEffect( + ptr, ESM::MagicEffect::indexToRefId(static_cast(effectId))); } }; @@ -1262,13 +1263,13 @@ namespace MWScript template class OpGetMagicEffect : public Interpreter::Opcode0 { - int mPositiveEffect; - int mNegativeEffect; + ESM::RefId mPositiveEffect; + ESM::RefId mNegativeEffect; public: OpGetMagicEffect(int positiveEffect, int negativeEffect) - : mPositiveEffect(positiveEffect) - , mNegativeEffect(negativeEffect) + : mPositiveEffect(ESM::MagicEffect::indexToRefId(positiveEffect)) + , mNegativeEffect(ESM::MagicEffect::indexToRefId(negativeEffect)) { } @@ -1284,7 +1285,7 @@ namespace MWScript const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); - if (mNegativeEffect != -1) + if (!mNegativeEffect.empty()) currentValue -= effects.getOrDefault(mNegativeEffect).getMagnitude(); // GetResist* should take in account elemental shields @@ -1303,13 +1304,13 @@ namespace MWScript template class OpSetMagicEffect : public Interpreter::Opcode0 { - int mPositiveEffect; - int mNegativeEffect; + ESM::RefId mPositiveEffect; + ESM::RefId mNegativeEffect; public: OpSetMagicEffect(int positiveEffect, int negativeEffect) - : mPositiveEffect(positiveEffect) - , mNegativeEffect(negativeEffect) + : mPositiveEffect(ESM::MagicEffect::indexToRefId(positiveEffect)) + , mNegativeEffect(ESM::MagicEffect::indexToRefId(negativeEffect)) { } @@ -1325,7 +1326,7 @@ namespace MWScript MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); - if (mNegativeEffect != -1) + if (!mNegativeEffect.empty()) currentValue -= effects.getOrDefault(mNegativeEffect).getMagnitude(); // SetResist* should take in account elemental shields @@ -1343,13 +1344,13 @@ namespace MWScript template class OpModMagicEffect : public Interpreter::Opcode0 { - int mPositiveEffect; - int mNegativeEffect; + ESM::RefId mPositiveEffect; + ESM::RefId mNegativeEffect; public: OpModMagicEffect(int positiveEffect, int negativeEffect) - : mPositiveEffect(positiveEffect) - , mNegativeEffect(negativeEffect) + : mPositiveEffect(ESM::MagicEffect::indexToRefId(positiveEffect)) + , mNegativeEffect(ESM::MagicEffect::indexToRefId(negativeEffect)) { } @@ -1392,7 +1393,7 @@ namespace MWScript auto& effects = player.getClass().getCreatureStats(player).getMagicEffects(); float delta = std::clamp(arg * 100.f, 0.f, 100.f) - effects.getOrDefault(ESM::MagicEffect::NightEye).getMagnitude(); - effects.modifyBase(ESM::MagicEffect::NightEye, static_cast(delta)); + effects.modifyBase(MWMechanics::EffectKey(ESM::MagicEffect::NightEye), static_cast(delta)); } }; @@ -1409,14 +1410,14 @@ namespace MWScript float newBase = std::clamp(nightEye.getMagnitude() + arg * 100.f, 0.f, 100.f); newBase -= nightEye.getModifier(); float delta = std::clamp(newBase, 0.f, 100.f) - nightEye.getMagnitude(); - effects.modifyBase(ESM::MagicEffect::NightEye, static_cast(delta)); + effects.modifyBase(MWMechanics::EffectKey(ESM::MagicEffect::NightEye), static_cast(delta)); } }; struct MagicEffect { - int mPositiveEffect; - int mNegativeEffect; + ESM::RefId mPositiveEffect; + ESM::RefId mNegativeEffect; }; void installOpcodes(Interpreter::Interpreter& interpreter) @@ -1577,28 +1578,28 @@ namespace MWScript { ESM::MagicEffect::ResistBlightDisease, ESM::MagicEffect::WeaknessToBlightDisease }, { ESM::MagicEffect::ResistCorprusDisease, ESM::MagicEffect::WeaknessToCorprusDisease }, { ESM::MagicEffect::ResistPoison, ESM::MagicEffect::WeaknessToPoison }, - { ESM::MagicEffect::ResistParalysis, -1 }, + { ESM::MagicEffect::ResistParalysis, ESM::RefId() }, { ESM::MagicEffect::ResistNormalWeapons, ESM::MagicEffect::WeaknessToNormalWeapons }, - { ESM::MagicEffect::WaterBreathing, -1 }, - { ESM::MagicEffect::Chameleon, -1 }, - { ESM::MagicEffect::WaterWalking, -1 }, - { ESM::MagicEffect::SwiftSwim, -1 }, - { ESM::MagicEffect::Jump, -1 }, - { ESM::MagicEffect::Levitate, -1 }, - { ESM::MagicEffect::Shield, -1 }, - { ESM::MagicEffect::Sound, -1 }, - { ESM::MagicEffect::Silence, -1 }, - { ESM::MagicEffect::Blind, -1 }, - { ESM::MagicEffect::Paralyze, -1 }, - { ESM::MagicEffect::Invisibility, -1 }, - { ESM::MagicEffect::FortifyAttack, -1 }, - { ESM::MagicEffect::Sanctuary, -1 }, + { ESM::MagicEffect::WaterBreathing, ESM::RefId() }, + { ESM::MagicEffect::Chameleon, ESM::RefId() }, + { ESM::MagicEffect::WaterWalking, ESM::RefId() }, + { ESM::MagicEffect::SwiftSwim, ESM::RefId() }, + { ESM::MagicEffect::Jump, ESM::RefId() }, + { ESM::MagicEffect::Levitate, ESM::RefId() }, + { ESM::MagicEffect::Shield, ESM::RefId() }, + { ESM::MagicEffect::Sound, ESM::RefId() }, + { ESM::MagicEffect::Silence, ESM::RefId() }, + { ESM::MagicEffect::Blind, ESM::RefId() }, + { ESM::MagicEffect::Paralyze, ESM::RefId() }, + { ESM::MagicEffect::Invisibility, ESM::RefId() }, + { ESM::MagicEffect::FortifyAttack, ESM::RefId() }, + { ESM::MagicEffect::Sanctuary, ESM::RefId() }, }; for (int i = 0; i < 24; ++i) { - int positive = sMagicEffects[i].mPositiveEffect; - int negative = sMagicEffects[i].mNegativeEffect; + int positive = ESM::MagicEffect::refIdToIndex(sMagicEffects[i].mPositiveEffect); + int negative = ESM::MagicEffect::refIdToIndex(sMagicEffects[i].mNegativeEffect); interpreter.installSegment5>( Compiler::Stats::opcodeGetMagicEffect + i, positive, negative); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 773e4a80e8..275e36686d 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -185,8 +185,8 @@ namespace { iter->mData.mAttribute = -1; Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId - << ": dropping unexpected attribute argument of " - << ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect"; + << ": dropping unexpected attribute argument of " << iter->mData.mEffectID + << " effect"; changed = true; } @@ -194,8 +194,8 @@ namespace { iter->mData.mSkill = -1; Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId - << ": dropping unexpected skill argument of " - << ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect"; + << ": dropping unexpected skill argument of " << iter->mData.mEffectID + << " effect"; changed = true; } diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 9d030dcea9..a55c0c8350 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -110,9 +110,6 @@ namespace MWWorld return ptr; } - // Need to instantiate these before they're used - template class IndexedStore; - template TypedDynamicStore::TypedDynamicStore() { @@ -976,10 +973,6 @@ namespace MWWorld TypedDynamicStore::setUp(); } - // Magic effect - //========================================================================= - Store::Store() {} - // Attribute //========================================================================= @@ -1260,7 +1253,7 @@ template class MWWorld::TypedDynamicStore; // template class MWWorld::Store; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; -// template class MWWorld::Store; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; // template class MWWorld::Store; diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index 2ccf26f734..dd23495bba 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -31,7 +32,6 @@ namespace ESM { struct LandTexture; - struct MagicEffect; struct WeaponType; class ESMReader; class ESMWriter; @@ -452,13 +452,6 @@ namespace MWWorld void setUp(const MWWorld::Store& settings); }; - template <> - class Store : public IndexedStore - { - public: - Store(); - }; - template <> class Store : public TypedDynamicStore { diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 94bfaa3997..7aaefa3d76 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3108,7 +3108,7 @@ namespace MWWorld const MWWorld::Class& cls = ptr.getClass(); if (cls.isActor()) { - std::set playing; + std::set playing; for (const auto& params : cls.getCreatureStats(ptr).getActiveSpells()) { for (const auto& effect : params.getEffects()) diff --git a/apps/openmw_tests/mwworld/teststore.cpp b/apps/openmw_tests/mwworld/teststore.cpp index 70f094379a..8c9d1060b3 100644 --- a/apps/openmw_tests/mwworld/teststore.cpp +++ b/apps/openmw_tests/mwworld/teststore.cpp @@ -447,6 +447,8 @@ namespace refId = ESM::Attribute::Strength; else if constexpr (std::is_same_v) refId = ESM::Skill::Block; + else if constexpr (std::is_same_v) + refId = ESM::MagicEffect::WaterBreathing; else refId = ESM::StringRefId(stringId); @@ -501,7 +503,6 @@ namespace } } - static_assert(ESM::hasIndex); static_assert(ESM::hasStringId); template > diff --git a/components/esm3/activespells.cpp b/components/esm3/activespells.cpp index ea6c4fe8ea..657808e60b 100644 --- a/components/esm3/activespells.cpp +++ b/components/esm3/activespells.cpp @@ -13,61 +13,57 @@ namespace ESM { namespace { - bool isSummon(int effectId) + bool isSummon(ESM::RefId effectId) { - switch (effectId) - { - case MagicEffect::SummonScamp: - case MagicEffect::SummonClannfear: - case MagicEffect::SummonDaedroth: - case MagicEffect::SummonDremora: - case MagicEffect::SummonAncestralGhost: - case MagicEffect::SummonSkeletalMinion: - case MagicEffect::SummonBonewalker: - case MagicEffect::SummonGreaterBonewalker: - case MagicEffect::SummonBonelord: - case MagicEffect::SummonWingedTwilight: - case MagicEffect::SummonHunger: - case MagicEffect::SummonGoldenSaint: - case MagicEffect::SummonFlameAtronach: - case MagicEffect::SummonFrostAtronach: - case MagicEffect::SummonStormAtronach: - case MagicEffect::SummonCenturionSphere: - case MagicEffect::SummonFabricant: - case MagicEffect::SummonWolf: - case MagicEffect::SummonBear: - case MagicEffect::SummonBonewolf: - case MagicEffect::SummonCreature04: - case MagicEffect::SummonCreature05: - return true; - } - return false; + static const std::array summonEffects{ + MagicEffect::SummonScamp, + MagicEffect::SummonClannfear, + MagicEffect::SummonDaedroth, + MagicEffect::SummonDremora, + MagicEffect::SummonAncestralGhost, + MagicEffect::SummonSkeletalMinion, + MagicEffect::SummonBonewalker, + MagicEffect::SummonGreaterBonewalker, + MagicEffect::SummonBonelord, + MagicEffect::SummonWingedTwilight, + MagicEffect::SummonHunger, + MagicEffect::SummonGoldenSaint, + MagicEffect::SummonFlameAtronach, + MagicEffect::SummonFrostAtronach, + MagicEffect::SummonStormAtronach, + MagicEffect::SummonCenturionSphere, + MagicEffect::SummonFabricant, + MagicEffect::SummonWolf, + MagicEffect::SummonBear, + MagicEffect::SummonBonewolf, + MagicEffect::SummonCreature04, + MagicEffect::SummonCreature05, + }; + return std::find(summonEffects.begin(), summonEffects.end(), effectId) != summonEffects.end(); } - bool affectsAttribute(int effectId) + bool affectsAttribute(ESM::RefId effectId) { - switch (effectId) - { - case MagicEffect::DrainAttribute: - case MagicEffect::DamageAttribute: - case MagicEffect::RestoreAttribute: - case MagicEffect::FortifyAttribute: - case MagicEffect::AbsorbAttribute: - return true; - } - return false; + static const std::array affectsAttributeEffects{ + MagicEffect::DrainAttribute, + MagicEffect::DamageAttribute, + MagicEffect::RestoreAttribute, + MagicEffect::FortifyAttribute, + MagicEffect::AbsorbAttribute, + }; + return std::find(affectsAttributeEffects.begin(), affectsAttributeEffects.end(), effectId) + != affectsAttributeEffects.end(); } - bool affectsSkill(int effectId) + bool affectsSkill(ESM::RefId effectId) { - switch (effectId) - { - case MagicEffect::DrainSkill: - case MagicEffect::DamageSkill: - case MagicEffect::RestoreSkill: - case MagicEffect::FortifySkill: - case MagicEffect::AbsorbSkill: - return true; - } - return false; + static const std::array affectsSkillEffects{ + MagicEffect::DrainSkill, + MagicEffect::DamageSkill, + MagicEffect::RestoreSkill, + MagicEffect::FortifySkill, + MagicEffect::AbsorbSkill, + }; + return std::find(affectsSkillEffects.begin(), affectsSkillEffects.end(), effectId) + != affectsSkillEffects.end(); } void saveImpl(ESMWriter& esm, const std::vector& spells, NAME tag) @@ -90,7 +86,7 @@ namespace ESM for (auto& effect : params.mEffects) { - esm.writeHNT("MGEF", effect.mEffectId); + esm.writeHNT("MGEF", ESM::MagicEffect::refIdToIndex(effect.mEffectId)); if (const ESM::RefId* id = std::get_if(&effect.mArg)) { if (!id->empty()) @@ -175,8 +171,10 @@ namespace ESM while (esm.isNextSub("MGEF")) { + int32_t effectId; ActiveEffect effect; - esm.getHT(effect.mEffectId); + esm.getHT(effectId); + effect.mEffectId = ESM::MagicEffect::indexToRefId(effectId); if (format <= MaxActorIdSaveGameFormatVersion) { int32_t arg = -1; diff --git a/components/esm3/activespells.hpp b/components/esm3/activespells.hpp index 0db1b0e61b..3cd96e9a16 100644 --- a/components/esm3/activespells.hpp +++ b/components/esm3/activespells.hpp @@ -31,7 +31,7 @@ namespace ESM Flag_Invalid = 1 << 5 }; - int32_t mEffectId; + RefId mEffectId; float mMagnitude; float mMinMagnitude; float mMaxMagnitude; diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index ba61a8c895..c034a6a0d1 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -2,6 +2,8 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + #include namespace ESM @@ -117,9 +119,10 @@ namespace ESM esm.getHNOT(effectIndex, "EIND"); int32_t actorId; esm.getHNT(actorId, "ACID"); - mSummonedCreatureMap[SummonKey(magicEffect, source, effectIndex)] = actorId; - mSummonedCreatures.emplace( - magicEffect, RefNum{ .mIndex = static_cast(actorId), .mContentFile = -1 }); + mSummonedCreatureMap[SummonKey(ESM::MagicEffect::indexToRefId(magicEffect), source, effectIndex)] + = actorId; + mSummonedCreatures.emplace(ESM::MagicEffect::indexToRefId(magicEffect), + RefNum{ .mIndex = static_cast(actorId), .mContentFile = -1 }); } } else @@ -133,7 +136,7 @@ namespace ESM esm.getHNT(actor.mIndex, "ACID"); else actor = esm.getFormId(true, "ACID"); - mSummonedCreatures.emplace(magicEffect, actor); + mSummonedCreatures.emplace(ESM::MagicEffect::indexToRefId(magicEffect), actor); } } @@ -248,7 +251,7 @@ namespace ESM for (const auto& [effectId, actor] : mSummonedCreatures) { - esm.writeHNT("SUMM", effectId); + esm.writeHNT("SUMM", ESM::MagicEffect::refIdToIndex(effectId)); esm.writeFormId(actor, true, "ACID"); } diff --git a/components/esm3/creaturestats.hpp b/components/esm3/creaturestats.hpp index cba74e065d..058c402b30 100644 --- a/components/esm3/creaturestats.hpp +++ b/components/esm3/creaturestats.hpp @@ -43,7 +43,7 @@ namespace ESM std::array, 4> mAiSettings; std::map mSummonedCreatureMap; - std::multimap mSummonedCreatures; + std::multimap mSummonedCreatures; std::vector mSummonGraveyard; TimeStamp mTradeTime; diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index 5a1fa91f28..f82f956c90 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -3,11 +3,55 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + +#include #include namespace ESM { - template T> + namespace + { + // ENAM format defined by Morrowind.esm + struct EsmENAMstruct + { + int16_t mEffectID; + signed char mSkill, mAttribute; + int32_t mRange, mArea, mDuration, mMagnMin, mMagnMax; + }; + + void toBinary(const ENAMstruct& src, EsmENAMstruct& dst) + { + int16_t index = static_cast(ESM::MagicEffect::refIdToIndex(src.mEffectID)); + if (index < 0 || index >= ESM::MagicEffect::Length) + throw std::runtime_error(std::format("Cannot serialize effect {}", src.mEffectID.toDebugString())); + dst.mEffectID = index; + dst.mSkill = src.mSkill; + dst.mAttribute = src.mAttribute; + dst.mRange = src.mRange; + dst.mArea = src.mArea; + dst.mDuration = src.mDuration; + dst.mMagnMin = src.mMagnMin; + dst.mMagnMax = src.mMagnMax; + } + + void fromBinary(const EsmENAMstruct& src, ENAMstruct& dst) + { + int16_t index = src.mEffectID; + if (index < 0 || index >= ESM::MagicEffect::Length) + throw std::runtime_error(std::format("Cannot deserialize effect with index {}", index)); + dst.mEffectID = ESM::MagicEffect::indexToRefId(index); + dst.mSkill = src.mSkill; + dst.mAttribute = src.mAttribute; + dst.mRange = src.mRange; + dst.mArea = src.mArea; + dst.mDuration = src.mDuration; + dst.mMagnMin = src.mMagnMin; + dst.mMagnMax = src.mMagnMax; + } + } + + template T> void decompose(T&& v, const auto& f) { f(v.mEffectID, v.mSkill, v.mAttribute, v.mRange, v.mArea, v.mDuration, v.mMagnMin, v.mMagnMax); @@ -37,8 +81,11 @@ namespace ESM void EffectList::add(ESMReader& esm) { + EsmENAMstruct bin; + esm.getSubComposite(bin); + ENAMstruct s; - esm.getSubComposite(s); + fromBinary(bin, s); mList.push_back({ s, static_cast(mList.size()) }); } @@ -46,7 +93,9 @@ namespace ESM { for (const IndexedENAMstruct& enam : mList) { - esm.writeNamedComposite("ENAM", enam.mData); + EsmENAMstruct bin; + toBinary(enam.mData, bin); + esm.writeNamedComposite("ENAM", bin); } } diff --git a/components/esm3/effectlist.hpp b/components/esm3/effectlist.hpp index 8eb347d6c8..f7350f40fc 100644 --- a/components/esm3/effectlist.hpp +++ b/components/esm3/effectlist.hpp @@ -4,6 +4,8 @@ #include #include +#include + namespace ESM { class ESMReader; @@ -14,8 +16,8 @@ namespace ESM */ struct ENAMstruct { - // Magical effect, hard-coded ID - int16_t mEffectID; + // Magical effect, serialized to int16 + ESM::RefId mEffectID; // Which skills/attributes are affected (for restore/drain spells // etc.) diff --git a/components/esm3/loadingr.cpp b/components/esm3/loadingr.cpp index 6a4753d8e4..76a47f5178 100644 --- a/components/esm3/loadingr.cpp +++ b/components/esm3/loadingr.cpp @@ -3,11 +3,46 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include #include namespace ESM { - template T> + namespace + { + // IRDT format defined by Morrowind.esm + struct EsmIRDTstruct + { + float mWeight; + int32_t mValue, mEffectID[4], mSkills[4], mAttributes[4]; + }; + + void toBinary(const Ingredient::IRDTstruct& src, EsmIRDTstruct& dst) + { + dst.mWeight = src.mWeight; + dst.mValue = src.mValue; + for (int i = 0; i < 4; ++i) + { + dst.mEffectID[i] = ESM::MagicEffect::refIdToIndex(src.mEffectID[i]); + dst.mSkills[i] = src.mSkills[i]; + dst.mAttributes[i] = src.mAttributes[i]; + } + } + + void fromBinary(const EsmIRDTstruct& src, Ingredient::IRDTstruct& dst) + { + dst.mWeight = src.mWeight; + dst.mValue = src.mValue; + for (int i = 0; i < 4; ++i) + { + dst.mEffectID[i] = ESM::MagicEffect::indexToRefId(src.mEffectID[i]); + dst.mSkills[i] = src.mSkills[i]; + dst.mAttributes[i] = src.mAttributes[i]; + } + } + } + + template T> void decompose(T&& v, const auto& f) { f(v.mWeight, v.mValue, v.mEffectID, v.mSkills, v.mAttributes); @@ -36,7 +71,9 @@ namespace ESM mName = esm.getHString(); break; case fourCC("IRDT"): - esm.getSubComposite(mData); + EsmIRDTstruct bin; + esm.getSubComposite(bin); + fromBinary(bin, mData); hasData = true; break; case fourCC("SCRI"): @@ -63,15 +100,21 @@ namespace ESM // horrible hack to fix broken data in records for (int i = 0; i < 4; ++i) { - if (mData.mEffectID[i] != 85 && mData.mEffectID[i] != 22 && mData.mEffectID[i] != 17 - && mData.mEffectID[i] != 79 && mData.mEffectID[i] != 74) + if (mData.mEffectID[i] != ESM::MagicEffect::AbsorbAttribute + && mData.mEffectID[i] != ESM::MagicEffect::DamageAttribute + && mData.mEffectID[i] != ESM::MagicEffect::DrainAttribute + && mData.mEffectID[i] != ESM::MagicEffect::FortifyAttribute + && mData.mEffectID[i] != ESM::MagicEffect::RestoreAttribute) { mData.mAttributes[i] = -1; } // is this relevant in cycle from 0 to 4? - if (mData.mEffectID[i] != 89 && mData.mEffectID[i] != 26 && mData.mEffectID[i] != 21 - && mData.mEffectID[i] != 83 && mData.mEffectID[i] != 78) + if (mData.mEffectID[i] != ESM::MagicEffect::AbsorbSkill + && mData.mEffectID[i] != ESM::MagicEffect::DamageSkill + && mData.mEffectID[i] != ESM::MagicEffect::DrainSkill + && mData.mEffectID[i] != ESM::MagicEffect::FortifySkill + && mData.mEffectID[i] != ESM::MagicEffect::RestoreSkill) { mData.mSkills[i] = -1; } @@ -90,7 +133,9 @@ namespace ESM esm.writeHNCString("MODL", mModel); esm.writeHNOCString("FNAM", mName); - esm.writeNamedComposite("IRDT", mData); + EsmIRDTstruct bin; + toBinary(mData, bin); + esm.writeNamedComposite("IRDT", bin); esm.writeHNOCRefId("SCRI", mScript); esm.writeHNOCString("ITEX", mIcon); } @@ -102,7 +147,7 @@ namespace ESM mData.mValue = 0; for (int i = 0; i < 4; ++i) { - mData.mEffectID[i] = 0; + mData.mEffectID[i] = ESM::MagicEffect::WaterBreathing; mData.mSkills[i] = 0; mData.mAttributes[i] = 0; } diff --git a/components/esm3/loadingr.hpp b/components/esm3/loadingr.hpp index 2a8748e067..289cf839a4 100644 --- a/components/esm3/loadingr.hpp +++ b/components/esm3/loadingr.hpp @@ -27,7 +27,7 @@ namespace ESM { float mWeight; int32_t mValue; - int32_t mEffectID[4]; // Effect, -1 means none + RefId mEffectID[4]; // Effect, EmptyRefId means none int32_t mSkills[4]; // SkillEnum related to effect int32_t mAttributes[4]; // Attribute related to effect }; diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index fd75d071cb..0119ad821a 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -25,14 +25,165 @@ namespace ESM 0x1048, 0x1048, 0x1048, 0x1048 }; } + const StringRefId MagicEffect::WaterBreathing("WaterBreathing"); + const StringRefId MagicEffect::SwiftSwim("SwiftSwim"); + const StringRefId MagicEffect::WaterWalking("WaterWalking"); + const StringRefId MagicEffect::Shield("Shield"); + const StringRefId MagicEffect::FireShield("FireShield"); + const StringRefId MagicEffect::LightningShield("LightningShield"); + const StringRefId MagicEffect::FrostShield("FrostShield"); + const StringRefId MagicEffect::Burden("Burden"); + const StringRefId MagicEffect::Feather("Feather"); + const StringRefId MagicEffect::Jump("Jump"); + const StringRefId MagicEffect::Levitate("Levitate"); + const StringRefId MagicEffect::SlowFall("SlowFall"); + const StringRefId MagicEffect::Lock("Lock"); + const StringRefId MagicEffect::Open("Open"); + const StringRefId MagicEffect::FireDamage("FireDamage"); + const StringRefId MagicEffect::ShockDamage("ShockDamage"); + const StringRefId MagicEffect::FrostDamage("FrostDamage"); + const StringRefId MagicEffect::DrainAttribute("DrainAttribute"); + const StringRefId MagicEffect::DrainHealth("DrainHealth"); + const StringRefId MagicEffect::DrainMagicka("DrainMagicka"); + const StringRefId MagicEffect::DrainFatigue("DrainFatigue"); + const StringRefId MagicEffect::DrainSkill("DrainSkill"); + const StringRefId MagicEffect::DamageAttribute("DamageAttribute"); + const StringRefId MagicEffect::DamageHealth("DamageHealth"); + const StringRefId MagicEffect::DamageMagicka("DamageMagicka"); + const StringRefId MagicEffect::DamageFatigue("DamageFatigue"); + const StringRefId MagicEffect::DamageSkill("DamageSkill"); + const StringRefId MagicEffect::Poison("Poison"); + const StringRefId MagicEffect::WeaknessToFire("WeaknessToFire"); + const StringRefId MagicEffect::WeaknessToFrost("WeaknessToFrost"); + const StringRefId MagicEffect::WeaknessToShock("WeaknessToShock"); + const StringRefId MagicEffect::WeaknessToMagicka("WeaknessToMagicka"); + const StringRefId MagicEffect::WeaknessToCommonDisease("WeaknessToCommonDisease"); + const StringRefId MagicEffect::WeaknessToBlightDisease("WeaknessToBlightDisease"); + const StringRefId MagicEffect::WeaknessToCorprusDisease("WeaknessToCorprusDisease"); + const StringRefId MagicEffect::WeaknessToPoison("WeaknessToPoison"); + const StringRefId MagicEffect::WeaknessToNormalWeapons("WeaknessToNormalWeapons"); + const StringRefId MagicEffect::DisintegrateWeapon("DisintegrateWeapon"); + const StringRefId MagicEffect::DisintegrateArmor("DisintegrateArmor"); + const StringRefId MagicEffect::Invisibility("Invisibility"); + const StringRefId MagicEffect::Chameleon("Chameleon"); + const StringRefId MagicEffect::Light("Light"); + const StringRefId MagicEffect::Sanctuary("Sanctuary"); + const StringRefId MagicEffect::NightEye("NightEye"); + const StringRefId MagicEffect::Charm("Charm"); + const StringRefId MagicEffect::Paralyze("Paralyze"); + const StringRefId MagicEffect::Silence("Silence"); + const StringRefId MagicEffect::Blind("Blind"); + const StringRefId MagicEffect::Sound("Sound"); + const StringRefId MagicEffect::CalmHumanoid("CalmHumanoid"); + const StringRefId MagicEffect::CalmCreature("CalmCreature"); + const StringRefId MagicEffect::FrenzyHumanoid("FrenzyHumanoid"); + const StringRefId MagicEffect::FrenzyCreature("FrenzyCreature"); + const StringRefId MagicEffect::DemoralizeHumanoid("DemoralizeHumanoid"); + const StringRefId MagicEffect::DemoralizeCreature("DemoralizeCreature"); + const StringRefId MagicEffect::RallyHumanoid("RallyHumanoid"); + const StringRefId MagicEffect::RallyCreature("RallyCreature"); + const StringRefId MagicEffect::Dispel("Dispel"); + const StringRefId MagicEffect::Soultrap("Soultrap"); + const StringRefId MagicEffect::Telekinesis("Telekinesis"); + const StringRefId MagicEffect::Mark("Mark"); + const StringRefId MagicEffect::Recall("Recall"); + const StringRefId MagicEffect::DivineIntervention("DivineIntervention"); + const StringRefId MagicEffect::AlmsiviIntervention("AlmsiviIntervention"); + const StringRefId MagicEffect::DetectAnimal("DetectAnimal"); + const StringRefId MagicEffect::DetectEnchantment("DetectEnchantment"); + const StringRefId MagicEffect::DetectKey("DetectKey"); + const StringRefId MagicEffect::SpellAbsorption("SpellAbsorption"); + const StringRefId MagicEffect::Reflect("Reflect"); + const StringRefId MagicEffect::CureCommonDisease("CureCommonDisease"); + const StringRefId MagicEffect::CureBlightDisease("CureBlightDisease"); + const StringRefId MagicEffect::CureCorprusDisease("CureCorprusDisease"); + const StringRefId MagicEffect::CurePoison("CurePoison"); + const StringRefId MagicEffect::CureParalyzation("CureParalyzation"); + const StringRefId MagicEffect::RestoreAttribute("RestoreAttribute"); + const StringRefId MagicEffect::RestoreHealth("RestoreHealth"); + const StringRefId MagicEffect::RestoreMagicka("RestoreMagicka"); + const StringRefId MagicEffect::RestoreFatigue("RestoreFatigue"); + const StringRefId MagicEffect::RestoreSkill("RestoreSkill"); + const StringRefId MagicEffect::FortifyAttribute("FortifyAttribute"); + const StringRefId MagicEffect::FortifyHealth("FortifyHealth"); + const StringRefId MagicEffect::FortifyMagicka("FortifyMagicka"); + const StringRefId MagicEffect::FortifyFatigue("FortifyFatigue"); + const StringRefId MagicEffect::FortifySkill("FortifySkill"); + const StringRefId MagicEffect::FortifyMaximumMagicka("FortifyMaximumMagicka"); + const StringRefId MagicEffect::AbsorbAttribute("AbsorbAttribute"); + const StringRefId MagicEffect::AbsorbHealth("AbsorbHealth"); + const StringRefId MagicEffect::AbsorbMagicka("AbsorbMagicka"); + const StringRefId MagicEffect::AbsorbFatigue("AbsorbFatigue"); + const StringRefId MagicEffect::AbsorbSkill("AbsorbSkill"); + const StringRefId MagicEffect::ResistFire("ResistFire"); + const StringRefId MagicEffect::ResistFrost("ResistFrost"); + const StringRefId MagicEffect::ResistShock("ResistShock"); + const StringRefId MagicEffect::ResistMagicka("ResistMagicka"); + const StringRefId MagicEffect::ResistCommonDisease("ResistCommonDisease"); + const StringRefId MagicEffect::ResistBlightDisease("ResistBlightDisease"); + const StringRefId MagicEffect::ResistCorprusDisease("ResistCorprusDisease"); + const StringRefId MagicEffect::ResistPoison("ResistPoison"); + const StringRefId MagicEffect::ResistNormalWeapons("ResistNormalWeapons"); + const StringRefId MagicEffect::ResistParalysis("ResistParalysis"); + const StringRefId MagicEffect::RemoveCurse("RemoveCurse"); + const StringRefId MagicEffect::TurnUndead("TurnUndead"); + const StringRefId MagicEffect::SummonScamp("SummonScamp"); + const StringRefId MagicEffect::SummonClannfear("SummonClannfear"); + const StringRefId MagicEffect::SummonDaedroth("SummonDaedroth"); + const StringRefId MagicEffect::SummonDremora("SummonDremora"); + const StringRefId MagicEffect::SummonAncestralGhost("SummonAncestralGhost"); + const StringRefId MagicEffect::SummonSkeletalMinion("SummonSkeletalMinion"); + const StringRefId MagicEffect::SummonBonewalker("SummonBonewalker"); + const StringRefId MagicEffect::SummonGreaterBonewalker("SummonGreaterBonewalker"); + const StringRefId MagicEffect::SummonBonelord("SummonBonelord"); + const StringRefId MagicEffect::SummonWingedTwilight("SummonWingedTwilight"); + const StringRefId MagicEffect::SummonHunger("SummonHunger"); + const StringRefId MagicEffect::SummonGoldenSaint("SummonGoldenSaint"); + const StringRefId MagicEffect::SummonFlameAtronach("SummonFlameAtronach"); + const StringRefId MagicEffect::SummonFrostAtronach("SummonFrostAtronach"); + const StringRefId MagicEffect::SummonStormAtronach("SummonStormAtronach"); + const StringRefId MagicEffect::FortifyAttack("FortifyAttack"); + const StringRefId MagicEffect::CommandCreature("CommandCreature"); + const StringRefId MagicEffect::CommandHumanoid("CommandHumanoid"); + const StringRefId MagicEffect::BoundDagger("BoundDagger"); + const StringRefId MagicEffect::BoundLongsword("BoundLongsword"); + const StringRefId MagicEffect::BoundMace("BoundMace"); + const StringRefId MagicEffect::BoundBattleAxe("BoundBattleAxe"); + const StringRefId MagicEffect::BoundSpear("BoundSpear"); + const StringRefId MagicEffect::BoundLongbow("BoundLongbow"); + const StringRefId MagicEffect::ExtraSpell("ExtraSpell"); + const StringRefId MagicEffect::BoundCuirass("BoundCuirass"); + const StringRefId MagicEffect::BoundHelm("BoundHelm"); + const StringRefId MagicEffect::BoundBoots("BoundBoots"); + const StringRefId MagicEffect::BoundShield("BoundShield"); + const StringRefId MagicEffect::BoundGloves("BoundGloves"); + const StringRefId MagicEffect::Corprus("Corprus"); + const StringRefId MagicEffect::Vampirism("Vampirism"); + const StringRefId MagicEffect::SummonCenturionSphere("SummonCenturionSphere"); + const StringRefId MagicEffect::SunDamage("SunDamage"); + const StringRefId MagicEffect::StuntedMagicka("StuntedMagicka"); + + // Tribunal only + const StringRefId MagicEffect::SummonFabricant("SummonFabricant"); + + // Bloodmoon only + const StringRefId MagicEffect::SummonWolf("SummonWolf"); + const StringRefId MagicEffect::SummonBear("SummonBear"); + const StringRefId MagicEffect::SummonBonewolf("SummonBonewolf"); + const StringRefId MagicEffect::SummonCreature04("SummonCreature04"); + const StringRefId MagicEffect::SummonCreature05("SummonCreature05"); + void MagicEffect::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; // MagicEffect record can't be deleted now (may be changed in the future) mRecordFlags = esm.getRecordFlags(); - esm.getHNT(mIndex, "INDX"); + int32_t index = -1; + esm.getHNT(index, "INDX"); + if (index < 0 || index >= Length) + esm.fail("Invalid Index!"); - mId = indexToRefId(mIndex); + mId = indexToRefId(index); esm.getSubNameIs("MEDT"); esm.getSubHeader(); @@ -52,8 +203,8 @@ namespace ESM { // don't allow mods to change fixed flags in the legacy format mData.mFlags &= (AllowSpellmaking | AllowEnchanting | NegativeLight); - if (mIndex >= 0 && mIndex < NumberOfHardcodedFlags) - mData.mFlags |= HardcodedFlags[mIndex]; + if (index >= 0 && index < NumberOfHardcodedFlags) + mData.mFlags |= HardcodedFlags[index]; } // vanilla MW accepts the _SND subrecords before or after DESC... I hope @@ -103,7 +254,7 @@ namespace ESM } void MagicEffect::save(ESMWriter& esm, bool /*isDeleted*/) const { - esm.writeHNT("INDX", mIndex); + esm.writeHNT("INDX", refIdToIndex(mId)); esm.startSubRecord("MEDT"); esm.writeT(MagicSchool::skillRefIdToIndex(mData.mSchool)); @@ -134,109 +285,153 @@ namespace ESM namespace { - std::map makeEffectsMap() + std::unordered_map makeResistancesMap() { - std::map effects; + std::unordered_map effects{ + { MagicEffect::DisintegrateArmor, MagicEffect::Sanctuary }, + { MagicEffect::DisintegrateWeapon, MagicEffect::Sanctuary }, - effects[MagicEffect::Effects::DisintegrateArmor] = MagicEffect::Effects::Sanctuary; - effects[MagicEffect::Effects::DisintegrateWeapon] = MagicEffect::Effects::Sanctuary; + { MagicEffect::DrainAttribute, MagicEffect::ResistMagicka }, + { MagicEffect::DrainHealth, MagicEffect::ResistMagicka }, + { MagicEffect::DrainMagicka, MagicEffect::ResistMagicka }, + { MagicEffect::DrainFatigue, MagicEffect::ResistMagicka }, + { MagicEffect::DrainSkill, MagicEffect::ResistMagicka }, + { MagicEffect::DamageAttribute, MagicEffect::ResistMagicka }, + { MagicEffect::DamageHealth, MagicEffect::ResistMagicka }, + { MagicEffect::DamageMagicka, MagicEffect::ResistMagicka }, + { MagicEffect::DamageFatigue, MagicEffect::ResistMagicka }, + { MagicEffect::DamageSkill, MagicEffect::ResistMagicka }, - for (short i = MagicEffect::Effects::DrainAttribute; i <= MagicEffect::Effects::DamageSkill; ++i) - effects[i] = MagicEffect::Effects::ResistMagicka; - for (short i = MagicEffect::Effects::AbsorbAttribute; i <= MagicEffect::Effects::AbsorbSkill; ++i) - effects[i] = MagicEffect::Effects::ResistMagicka; - for (short i = MagicEffect::Effects::WeaknessToFire; i <= MagicEffect::Effects::WeaknessToNormalWeapons; - ++i) - effects[i] = MagicEffect::Effects::ResistMagicka; + { MagicEffect::AbsorbAttribute, MagicEffect::ResistMagicka }, + { MagicEffect::AbsorbHealth, MagicEffect::ResistMagicka }, + { MagicEffect::AbsorbMagicka, MagicEffect::ResistMagicka }, + { MagicEffect::AbsorbFatigue, MagicEffect::ResistMagicka }, + { MagicEffect::AbsorbSkill, MagicEffect::ResistMagicka }, - effects[MagicEffect::Effects::Burden] = MagicEffect::Effects::ResistMagicka; - effects[MagicEffect::Effects::Charm] = MagicEffect::Effects::ResistMagicka; - effects[MagicEffect::Effects::Silence] = MagicEffect::Effects::ResistMagicka; - effects[MagicEffect::Effects::Blind] = MagicEffect::Effects::ResistMagicka; - effects[MagicEffect::Effects::Sound] = MagicEffect::Effects::ResistMagicka; + { MagicEffect::WeaknessToFire, MagicEffect::ResistMagicka }, + { MagicEffect::WeaknessToFrost, MagicEffect::ResistMagicka }, + { MagicEffect::WeaknessToShock, MagicEffect::ResistMagicka }, + { MagicEffect::WeaknessToMagicka, MagicEffect::ResistMagicka }, + { MagicEffect::WeaknessToCommonDisease, MagicEffect::ResistMagicka }, + { MagicEffect::WeaknessToBlightDisease, MagicEffect::ResistMagicka }, + { MagicEffect::WeaknessToCorprusDisease, MagicEffect::ResistMagicka }, + { MagicEffect::WeaknessToPoison, MagicEffect::ResistMagicka }, + { MagicEffect::WeaknessToNormalWeapons, MagicEffect::ResistMagicka }, - for (short i = 0; i < 2; ++i) - { - effects[MagicEffect::Effects::CalmHumanoid + i] = MagicEffect::Effects::ResistMagicka; - effects[MagicEffect::Effects::FrenzyHumanoid + i] = MagicEffect::Effects::ResistMagicka; - effects[MagicEffect::Effects::DemoralizeHumanoid + i] = MagicEffect::Effects::ResistMagicka; - effects[MagicEffect::Effects::RallyHumanoid + i] = MagicEffect::Effects::ResistMagicka; - } + { MagicEffect::Burden, MagicEffect::ResistMagicka }, + { MagicEffect::Charm, MagicEffect::ResistMagicka }, + { MagicEffect::Silence, MagicEffect::ResistMagicka }, + { MagicEffect::Blind, MagicEffect::ResistMagicka }, + { MagicEffect::Sound, MagicEffect::ResistMagicka }, - effects[MagicEffect::Effects::TurnUndead] = MagicEffect::Effects::ResistMagicka; + { MagicEffect::CalmHumanoid, MagicEffect::ResistMagicka }, + { MagicEffect::CalmCreature, MagicEffect::ResistMagicka }, + { MagicEffect::FrenzyHumanoid, MagicEffect::ResistMagicka }, + { MagicEffect::FrenzyCreature, MagicEffect::ResistMagicka }, + { MagicEffect::DemoralizeHumanoid, MagicEffect::ResistMagicka }, + { MagicEffect::DemoralizeCreature, MagicEffect::ResistMagicka }, + { MagicEffect::RallyHumanoid, MagicEffect::ResistMagicka }, + { MagicEffect::RallyCreature, MagicEffect::ResistMagicka }, - effects[MagicEffect::Effects::FireDamage] = MagicEffect::Effects::ResistFire; - effects[MagicEffect::Effects::FrostDamage] = MagicEffect::Effects::ResistFrost; - effects[MagicEffect::Effects::ShockDamage] = MagicEffect::Effects::ResistShock; - effects[MagicEffect::Effects::Vampirism] = MagicEffect::Effects::ResistCommonDisease; - effects[MagicEffect::Effects::Corprus] = MagicEffect::Effects::ResistCorprusDisease; - effects[MagicEffect::Effects::Poison] = MagicEffect::Effects::ResistPoison; - effects[MagicEffect::Effects::Paralyze] = MagicEffect::Effects::ResistParalysis; + { MagicEffect::TurnUndead, MagicEffect::ResistMagicka }, + { MagicEffect::FireDamage, MagicEffect::ResistFire }, + { MagicEffect::FrostDamage, MagicEffect::ResistFrost }, + { MagicEffect::ShockDamage, MagicEffect::ResistShock }, + { MagicEffect::Vampirism, MagicEffect::ResistCommonDisease }, + { MagicEffect::Corprus, MagicEffect::ResistCorprusDisease }, + { MagicEffect::Poison, MagicEffect::ResistPoison }, + { MagicEffect::Paralyze, MagicEffect::ResistParalysis }, + }; return effects; } } - short MagicEffect::getResistanceEffect(short effect) + RefId MagicEffect::getResistanceEffect(RefId effectId) { // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute // - static const std::map effects = makeEffectsMap(); + static const std::unordered_map effects = makeResistancesMap(); - if (const auto it = effects.find(effect); it != effects.end()) + if (const auto it = effects.find(effectId); it != effects.end()) return it->second; - return -1; + return {}; } - short MagicEffect::getWeaknessEffect(short effect) + namespace { - static std::map effects; - if (effects.empty()) + std::unordered_map makeWeaknessesMap() { - for (short i = DrainAttribute; i <= DamageSkill; ++i) - effects[i] = WeaknessToMagicka; - for (short i = AbsorbAttribute; i <= AbsorbSkill; ++i) - effects[i] = WeaknessToMagicka; - for (short i = WeaknessToFire; i <= WeaknessToNormalWeapons; ++i) - effects[i] = WeaknessToMagicka; + std::unordered_map effects{ + { MagicEffect::DrainAttribute, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DrainHealth, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DrainMagicka, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DrainFatigue, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DrainSkill, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DamageAttribute, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DamageHealth, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DamageMagicka, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DamageFatigue, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DamageSkill, MagicEffect::WeaknessToMagicka }, - effects[Burden] = WeaknessToMagicka; - effects[Charm] = WeaknessToMagicka; - effects[Silence] = WeaknessToMagicka; - effects[Blind] = WeaknessToMagicka; - effects[Sound] = WeaknessToMagicka; + { MagicEffect::AbsorbAttribute, MagicEffect::WeaknessToMagicka }, + { MagicEffect::AbsorbHealth, MagicEffect::WeaknessToMagicka }, + { MagicEffect::AbsorbMagicka, MagicEffect::WeaknessToMagicka }, + { MagicEffect::AbsorbFatigue, MagicEffect::WeaknessToMagicka }, + { MagicEffect::AbsorbSkill, MagicEffect::WeaknessToMagicka }, - for (short i = 0; i < 2; ++i) - { - effects[CalmHumanoid + i] = WeaknessToMagicka; - effects[FrenzyHumanoid + i] = WeaknessToMagicka; - effects[DemoralizeHumanoid + i] = WeaknessToMagicka; - effects[RallyHumanoid + i] = WeaknessToMagicka; - } + { MagicEffect::WeaknessToFire, MagicEffect::WeaknessToMagicka }, + { MagicEffect::WeaknessToFrost, MagicEffect::WeaknessToMagicka }, + { MagicEffect::WeaknessToShock, MagicEffect::WeaknessToMagicka }, + { MagicEffect::WeaknessToMagicka, MagicEffect::WeaknessToMagicka }, + { MagicEffect::WeaknessToCommonDisease, MagicEffect::WeaknessToMagicka }, + { MagicEffect::WeaknessToBlightDisease, MagicEffect::WeaknessToMagicka }, + { MagicEffect::WeaknessToCorprusDisease, MagicEffect::WeaknessToMagicka }, + { MagicEffect::WeaknessToPoison, MagicEffect::WeaknessToMagicka }, + { MagicEffect::WeaknessToNormalWeapons, MagicEffect::WeaknessToMagicka }, - effects[TurnUndead] = WeaknessToMagicka; + { MagicEffect::Burden, MagicEffect::WeaknessToMagicka }, + { MagicEffect::Charm, MagicEffect::WeaknessToMagicka }, + { MagicEffect::Silence, MagicEffect::WeaknessToMagicka }, + { MagicEffect::Blind, MagicEffect::WeaknessToMagicka }, + { MagicEffect::Sound, MagicEffect::WeaknessToMagicka }, - effects[FireDamage] = WeaknessToFire; - effects[FrostDamage] = WeaknessToFrost; - effects[ShockDamage] = WeaknessToShock; - effects[Vampirism] = WeaknessToCommonDisease; - effects[Corprus] = WeaknessToCorprusDisease; - effects[Poison] = WeaknessToPoison; + { MagicEffect::CalmHumanoid, MagicEffect::WeaknessToMagicka }, + { MagicEffect::CalmCreature, MagicEffect::WeaknessToMagicka }, + { MagicEffect::FrenzyHumanoid, MagicEffect::WeaknessToMagicka }, + { MagicEffect::FrenzyCreature, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DemoralizeHumanoid, MagicEffect::WeaknessToMagicka }, + { MagicEffect::DemoralizeCreature, MagicEffect::WeaknessToMagicka }, + { MagicEffect::RallyHumanoid, MagicEffect::WeaknessToMagicka }, + { MagicEffect::RallyCreature, MagicEffect::WeaknessToMagicka }, - effects[Paralyze] = -1; + { MagicEffect::TurnUndead, MagicEffect::WeaknessToMagicka }, + + { MagicEffect::FireDamage, MagicEffect::WeaknessToFire }, + { MagicEffect::FrostDamage, MagicEffect::WeaknessToFrost }, + { MagicEffect::ShockDamage, MagicEffect::WeaknessToShock }, + { MagicEffect::Vampirism, MagicEffect::WeaknessToCommonDisease }, + { MagicEffect::Corprus, MagicEffect::WeaknessToCorprusDisease }, + { MagicEffect::Poison, MagicEffect::WeaknessToPoison }, + }; + return effects; } - - if (effects.find(effect) != effects.end()) - return effects[effect]; - else - return -1; } - // Map effect ID to GMST name - const std::array MagicEffect::sGmstEffectIds = { + RefId MagicEffect::getWeaknessEffect(RefId effectId) + { + static const std::unordered_map effects = makeWeaknessesMap(); + if (const auto it = effects.find(effectId); it != effects.end()) + return it->second; + + return {}; + } + + // Map effect index to GMST name + static constexpr std::array sGmstEffectIds = { "sEffectWaterBreathing", "sEffectSwiftSwim", "sEffectWaterWalking", @@ -386,203 +581,184 @@ namespace ESM "sEffectSummonCreature05", }; - // Map effect ID to identifying name - const std::array MagicEffect::sIndexNames = { - "WaterBreathing", - "SwiftSwim", - "WaterWalking", - "Shield", - "FireShield", - "LightningShield", - "FrostShield", - "Burden", - "Feather", - "Jump", - "Levitate", - "SlowFall", - "Lock", - "Open", - "FireDamage", - "ShockDamage", - "FrostDamage", - "DrainAttribute", - "DrainHealth", - "DrainMagicka", - "DrainFatigue", - "DrainSkill", - "DamageAttribute", - "DamageHealth", - "DamageMagicka", - "DamageFatigue", - "DamageSkill", - "Poison", - "WeaknessToFire", - "WeaknessToFrost", - "WeaknessToShock", - "WeaknessToMagicka", - "WeaknessToCommonDisease", - "WeaknessToBlightDisease", - "WeaknessToCorprusDisease", - "WeaknessToPoison", - "WeaknessToNormalWeapons", - "DisintegrateWeapon", - "DisintegrateArmor", - "Invisibility", - "Chameleon", - "Light", - "Sanctuary", - "NightEye", - "Charm", - "Paralyze", - "Silence", - "Blind", - "Sound", - "CalmHumanoid", - "CalmCreature", - "FrenzyHumanoid", - "FrenzyCreature", - "DemoralizeHumanoid", - "DemoralizeCreature", - "RallyHumanoid", - "RallyCreature", - "Dispel", - "Soultrap", - "Telekinesis", - "Mark", - "Recall", - "DivineIntervention", - "AlmsiviIntervention", - "DetectAnimal", - "DetectEnchantment", - "DetectKey", - "SpellAbsorption", - "Reflect", - "CureCommonDisease", - "CureBlightDisease", - "CureCorprusDisease", - "CurePoison", - "CureParalyzation", - "RestoreAttribute", - "RestoreHealth", - "RestoreMagicka", - "RestoreFatigue", - "RestoreSkill", - "FortifyAttribute", - "FortifyHealth", - "FortifyMagicka", - "FortifyFatigue", - "FortifySkill", - "FortifyMaximumMagicka", - "AbsorbAttribute", - "AbsorbHealth", - "AbsorbMagicka", - "AbsorbFatigue", - "AbsorbSkill", - "ResistFire", - "ResistFrost", - "ResistShock", - "ResistMagicka", - "ResistCommonDisease", - "ResistBlightDisease", - "ResistCorprusDisease", - "ResistPoison", - "ResistNormalWeapons", - "ResistParalysis", - "RemoveCurse", - "TurnUndead", - "SummonScamp", - "SummonClannfear", - "SummonDaedroth", - "SummonDremora", - "SummonAncestralGhost", - "SummonSkeletalMinion", - "SummonBonewalker", - "SummonGreaterBonewalker", - "SummonBonelord", - "SummonWingedTwilight", - "SummonHunger", - "SummonGoldenSaint", - "SummonFlameAtronach", - "SummonFrostAtronach", - "SummonStormAtronach", - "FortifyAttack", - "CommandCreature", - "CommandHumanoid", - "BoundDagger", - "BoundLongsword", - "BoundMace", - "BoundBattleAxe", - "BoundSpear", - "BoundLongbow", - "ExtraSpell", - "BoundCuirass", - "BoundHelm", - "BoundBoots", - "BoundShield", - "BoundGloves", - "Corprus", - "Vampirism", - "SummonCenturionSphere", - "SunDamage", - "StuntedMagicka", + static const std::array sMagicEffectIds{ + MagicEffect::WaterBreathing, + MagicEffect::SwiftSwim, + MagicEffect::WaterWalking, + MagicEffect::Shield, + MagicEffect::FireShield, + MagicEffect::LightningShield, + MagicEffect::FrostShield, + MagicEffect::Burden, + MagicEffect::Feather, + MagicEffect::Jump, + MagicEffect::Levitate, + MagicEffect::SlowFall, + MagicEffect::Lock, + MagicEffect::Open, + MagicEffect::FireDamage, + MagicEffect::ShockDamage, + MagicEffect::FrostDamage, + MagicEffect::DrainAttribute, + MagicEffect::DrainHealth, + MagicEffect::DrainMagicka, + MagicEffect::DrainFatigue, + MagicEffect::DrainSkill, + MagicEffect::DamageAttribute, + MagicEffect::DamageHealth, + MagicEffect::DamageMagicka, + MagicEffect::DamageFatigue, + MagicEffect::DamageSkill, + MagicEffect::Poison, + MagicEffect::WeaknessToFire, + MagicEffect::WeaknessToFrost, + MagicEffect::WeaknessToShock, + MagicEffect::WeaknessToMagicka, + MagicEffect::WeaknessToCommonDisease, + MagicEffect::WeaknessToBlightDisease, + MagicEffect::WeaknessToCorprusDisease, + MagicEffect::WeaknessToPoison, + MagicEffect::WeaknessToNormalWeapons, + MagicEffect::DisintegrateWeapon, + MagicEffect::DisintegrateArmor, + MagicEffect::Invisibility, + MagicEffect::Chameleon, + MagicEffect::Light, + MagicEffect::Sanctuary, + MagicEffect::NightEye, + MagicEffect::Charm, + MagicEffect::Paralyze, + MagicEffect::Silence, + MagicEffect::Blind, + MagicEffect::Sound, + MagicEffect::CalmHumanoid, + MagicEffect::CalmCreature, + MagicEffect::FrenzyHumanoid, + MagicEffect::FrenzyCreature, + MagicEffect::DemoralizeHumanoid, + MagicEffect::DemoralizeCreature, + MagicEffect::RallyHumanoid, + MagicEffect::RallyCreature, + MagicEffect::Dispel, + MagicEffect::Soultrap, + MagicEffect::Telekinesis, + MagicEffect::Mark, + MagicEffect::Recall, + MagicEffect::DivineIntervention, + MagicEffect::AlmsiviIntervention, + MagicEffect::DetectAnimal, + MagicEffect::DetectEnchantment, + MagicEffect::DetectKey, + MagicEffect::SpellAbsorption, + MagicEffect::Reflect, + MagicEffect::CureCommonDisease, + MagicEffect::CureBlightDisease, + MagicEffect::CureCorprusDisease, + MagicEffect::CurePoison, + MagicEffect::CureParalyzation, + MagicEffect::RestoreAttribute, + MagicEffect::RestoreHealth, + MagicEffect::RestoreMagicka, + MagicEffect::RestoreFatigue, + MagicEffect::RestoreSkill, + MagicEffect::FortifyAttribute, + MagicEffect::FortifyHealth, + MagicEffect::FortifyMagicka, + MagicEffect::FortifyFatigue, + MagicEffect::FortifySkill, + MagicEffect::FortifyMaximumMagicka, + MagicEffect::AbsorbAttribute, + MagicEffect::AbsorbHealth, + MagicEffect::AbsorbMagicka, + MagicEffect::AbsorbFatigue, + MagicEffect::AbsorbSkill, + MagicEffect::ResistFire, + MagicEffect::ResistFrost, + MagicEffect::ResistShock, + MagicEffect::ResistMagicka, + MagicEffect::ResistCommonDisease, + MagicEffect::ResistBlightDisease, + MagicEffect::ResistCorprusDisease, + MagicEffect::ResistPoison, + MagicEffect::ResistNormalWeapons, + MagicEffect::ResistParalysis, + MagicEffect::RemoveCurse, + MagicEffect::TurnUndead, + MagicEffect::SummonScamp, + MagicEffect::SummonClannfear, + MagicEffect::SummonDaedroth, + MagicEffect::SummonDremora, + MagicEffect::SummonAncestralGhost, + MagicEffect::SummonSkeletalMinion, + MagicEffect::SummonBonewalker, + MagicEffect::SummonGreaterBonewalker, + MagicEffect::SummonBonelord, + MagicEffect::SummonWingedTwilight, + MagicEffect::SummonHunger, + MagicEffect::SummonGoldenSaint, + MagicEffect::SummonFlameAtronach, + MagicEffect::SummonFrostAtronach, + MagicEffect::SummonStormAtronach, + MagicEffect::FortifyAttack, + MagicEffect::CommandCreature, + MagicEffect::CommandHumanoid, + MagicEffect::BoundDagger, + MagicEffect::BoundLongsword, + MagicEffect::BoundMace, + MagicEffect::BoundBattleAxe, + MagicEffect::BoundSpear, + MagicEffect::BoundLongbow, + MagicEffect::ExtraSpell, + MagicEffect::BoundCuirass, + MagicEffect::BoundHelm, + MagicEffect::BoundBoots, + MagicEffect::BoundShield, + MagicEffect::BoundGloves, + MagicEffect::Corprus, + MagicEffect::Vampirism, + MagicEffect::SummonCenturionSphere, + MagicEffect::SunDamage, + MagicEffect::StuntedMagicka, // tribunal - "SummonFabricant", + MagicEffect::SummonFabricant, // bloodmoon - "SummonWolf", - "SummonBear", - "SummonBonewolf", - "SummonCreature04", - "SummonCreature05", + MagicEffect::SummonWolf, + MagicEffect::SummonBear, + MagicEffect::SummonBonewolf, + MagicEffect::SummonCreature04, + MagicEffect::SummonCreature05, }; - template - static std::map initStringToIntMap(const Collection& strings) + template > + static std::map initToIndexMap( + const Collection& data, Comparator comp = Comparator()) { - std::map map; - for (size_t i = 0; i < strings.size(); i++) - map[strings[i]] = static_cast(i); - + std::map map(comp); + for (size_t i = 0; i < data.size(); ++i) + map[data[i]] = static_cast(i); return map; } - const std::map MagicEffect::sGmstEffectIdToIndexMap - = initStringToIntMap(MagicEffect::sGmstEffectIds); + const std::map sGmstEffectIdToIndexMap + = initToIndexMap(sGmstEffectIds, Misc::StringUtils::CiComp()); - const std::map MagicEffect::sIndexNameToIndexMap - = initStringToIntMap(MagicEffect::sIndexNames); - - class FindSecond - { - std::string_view mName; - - public: - FindSecond(std::string_view name) - : mName(name) - { - } - - bool operator()(const std::pair& item) const - { - if (Misc::StringUtils::ciEqual(item.second, mName)) - return true; - return false; - } - }; + const std::map sMagicEffectIdToIndexMap = initToIndexMap(sMagicEffectIds); MagicEffect::MagnitudeDisplayType MagicEffect::getMagnitudeDisplayType() const { + int index = refIdToIndex(mId); if (mData.mFlags & NoMagnitude) return MDT_None; - if (mIndex == 84) + if (index == 84) return MDT_TimesInt; - if (mIndex == 59 || (mIndex >= 64 && mIndex <= 66)) + if (index == 59 || (index >= 64 && index <= 66)) return MDT_Feet; - if (mIndex == 118 || mIndex == 119) + if (index == 118 || index == 119) return MDT_Level; - if ((mIndex >= 28 && mIndex <= 36) || (mIndex >= 90 && mIndex <= 99) || mIndex == 40 || mIndex == 47 - || mIndex == 57 || mIndex == 68) + if ((index >= 28 && index <= 36) || (index >= 90 && index <= 99) || index == 40 || index == 47 || index == 57 + || index == 68) return MDT_Percentage; return MDT_Points; @@ -620,44 +796,41 @@ namespace ESM return color; } - const std::string& MagicEffect::indexToGmstString(int effectID) + std::string_view MagicEffect::refIdToGmstString(RefId effectId) { - if (effectID < 0 || static_cast(effectID) >= sGmstEffectIds.size()) - throw std::runtime_error(std::string("Unimplemented effect ID ") + std::to_string(effectID)); - - return sGmstEffectIds[effectID]; + int index = refIdToIndex(effectId); + if (index < 0 || index >= Length) + return {}; + return sGmstEffectIds[index]; } - std::string_view MagicEffect::indexToName(int effectID) + RefId MagicEffect::effectGmstIdToRefId(std::string_view gmstId) { - if (effectID < 0 || static_cast(effectID) >= sIndexNames.size()) - throw std::runtime_error(std::string("Unimplemented effect ID ") + std::to_string(effectID)); - - return sIndexNames[effectID]; - } - - int MagicEffect::indexNameToIndex(std::string_view effect) - { - auto name = sIndexNameToIndexMap.find(effect); - if (name == sIndexNameToIndexMap.end()) - return -1; - - return name->second; - } - - int MagicEffect::effectGmstIdToIndex(std::string_view gmstId) - { - auto name = sGmstEffectIdToIndexMap.find(gmstId); - if (name == sGmstEffectIdToIndexMap.end()) - throw std::runtime_error("Unimplemented effect " + std::string(gmstId)); - - return name->second; + auto it = sGmstEffectIdToIndexMap.find(gmstId); + if (it == sGmstEffectIdToIndexMap.end()) + return {}; + return sMagicEffectIds[it->second]; } RefId MagicEffect::indexToRefId(int index) { - if (index == -1) - return RefId(); - return RefId::index(sRecordId, static_cast(index)); + if (index < 0 || index >= Length) + return {}; + return sMagicEffectIds[index]; + } + + int MagicEffect::refIdToIndex(RefId effectId) + { + const auto it = sMagicEffectIdToIndexMap.find(effectId); + if (it != sMagicEffectIdToIndexMap.end()) + return it->second; + return -1; + } + + std::string_view MagicEffect::indexToName(int index) + { + if (index < 0 || index >= Length) + return {}; + return sMagicEffectIds[index].getRefIdString(); } } diff --git a/components/esm3/loadmgef.hpp b/components/esm3/loadmgef.hpp index 9f028e4bdc..b4c0063d13 100644 --- a/components/esm3/loadmgef.hpp +++ b/components/esm3/loadmgef.hpp @@ -83,10 +83,10 @@ namespace ESM float mUnknown2; // Called "Size Cap" in CS }; // 36 bytes - /// Returns the effect that provides resistance against \a effect (or -1 if there's none) - static short getResistanceEffect(short effect); - /// Returns the effect that induces weakness against \a effect (or -1 if there's none) - static short getWeaknessEffect(short effect); + /// Returns the effect that provides resistance against \a effect (or empty RefId if there's none) + static RefId getResistanceEffect(RefId effect); + /// Returns the effect that induces weakness against \a effect (or empty RefId if there's none) + static RefId getWeaknessEffect(RefId effect); MagnitudeDisplayType getMagnitudeDisplayType() const; @@ -98,17 +98,6 @@ namespace ESM ESM::RefId mCastSound, mBoltSound, mHitSound, mAreaSound; // Sounds std::string mDescription; - // Index of this magical effect. Corresponds to one of the - // hard-coded effects in the original engine: - // 0-136 in Morrowind - // 137 in Tribunal - // 138-140 in Bloodmoon (also changes 64?) - // 141-142 are summon effects introduced in bloodmoon, but not used - // there. They can be redefined in mods by setting the name in GMST - // sEffectSummonCreature04/05 creature id in - // sMagicCreature04ID/05ID. - int32_t mIndex; - void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; @@ -117,170 +106,163 @@ namespace ESM osg::Vec4f getColor() const; - enum Effects : short - { - WaterBreathing = 0, - SwiftSwim = 1, - WaterWalking = 2, - Shield = 3, - FireShield = 4, - LightningShield = 5, - FrostShield = 6, - Burden = 7, - Feather = 8, - Jump = 9, - Levitate = 10, - SlowFall = 11, - Lock = 12, - Open = 13, - FireDamage = 14, - ShockDamage = 15, - FrostDamage = 16, - DrainAttribute = 17, - DrainHealth = 18, - DrainMagicka = 19, - DrainFatigue = 20, - DrainSkill = 21, - DamageAttribute = 22, - DamageHealth = 23, - DamageMagicka = 24, - DamageFatigue = 25, - DamageSkill = 26, - Poison = 27, - WeaknessToFire = 28, - WeaknessToFrost = 29, - WeaknessToShock = 30, - WeaknessToMagicka = 31, - WeaknessToCommonDisease = 32, - WeaknessToBlightDisease = 33, - WeaknessToCorprusDisease = 34, - WeaknessToPoison = 35, - WeaknessToNormalWeapons = 36, - DisintegrateWeapon = 37, - DisintegrateArmor = 38, - Invisibility = 39, - Chameleon = 40, - Light = 41, - Sanctuary = 42, - NightEye = 43, - Charm = 44, - Paralyze = 45, - Silence = 46, - Blind = 47, - Sound = 48, - CalmHumanoid = 49, - CalmCreature = 50, - FrenzyHumanoid = 51, - FrenzyCreature = 52, - DemoralizeHumanoid = 53, - DemoralizeCreature = 54, - RallyHumanoid = 55, - RallyCreature = 56, - Dispel = 57, - Soultrap = 58, - Telekinesis = 59, - Mark = 60, - Recall = 61, - DivineIntervention = 62, - AlmsiviIntervention = 63, - DetectAnimal = 64, - DetectEnchantment = 65, - DetectKey = 66, - SpellAbsorption = 67, - Reflect = 68, - CureCommonDisease = 69, - CureBlightDisease = 70, - CureCorprusDisease = 71, - CurePoison = 72, - CureParalyzation = 73, - RestoreAttribute = 74, - RestoreHealth = 75, - RestoreMagicka = 76, - RestoreFatigue = 77, - RestoreSkill = 78, - FortifyAttribute = 79, - FortifyHealth = 80, - FortifyMagicka = 81, - FortifyFatigue = 82, - FortifySkill = 83, - FortifyMaximumMagicka = 84, - AbsorbAttribute = 85, - AbsorbHealth = 86, - AbsorbMagicka = 87, - AbsorbFatigue = 88, - AbsorbSkill = 89, - ResistFire = 90, - ResistFrost = 91, - ResistShock = 92, - ResistMagicka = 93, - ResistCommonDisease = 94, - ResistBlightDisease = 95, - ResistCorprusDisease = 96, - ResistPoison = 97, - ResistNormalWeapons = 98, - ResistParalysis = 99, - RemoveCurse = 100, - TurnUndead = 101, - SummonScamp = 102, - SummonClannfear = 103, - SummonDaedroth = 104, - SummonDremora = 105, - SummonAncestralGhost = 106, - SummonSkeletalMinion = 107, - SummonBonewalker = 108, - SummonGreaterBonewalker = 109, - SummonBonelord = 110, - SummonWingedTwilight = 111, - SummonHunger = 112, - SummonGoldenSaint = 113, - SummonFlameAtronach = 114, - SummonFrostAtronach = 115, - SummonStormAtronach = 116, - FortifyAttack = 117, - CommandCreature = 118, - CommandHumanoid = 119, - BoundDagger = 120, - BoundLongsword = 121, - BoundMace = 122, - BoundBattleAxe = 123, - BoundSpear = 124, - BoundLongbow = 125, - ExtraSpell = 126, - BoundCuirass = 127, - BoundHelm = 128, - BoundBoots = 129, - BoundShield = 130, - BoundGloves = 131, - Corprus = 132, - Vampirism = 133, - SummonCenturionSphere = 134, - SunDamage = 135, - StuntedMagicka = 136, + static const StringRefId WaterBreathing; + static const StringRefId SwiftSwim; + static const StringRefId WaterWalking; + static const StringRefId Shield; + static const StringRefId FireShield; + static const StringRefId LightningShield; + static const StringRefId FrostShield; + static const StringRefId Burden; + static const StringRefId Feather; + static const StringRefId Jump; + static const StringRefId Levitate; + static const StringRefId SlowFall; + static const StringRefId Lock; + static const StringRefId Open; + static const StringRefId FireDamage; + static const StringRefId ShockDamage; + static const StringRefId FrostDamage; + static const StringRefId DrainAttribute; + static const StringRefId DrainHealth; + static const StringRefId DrainMagicka; + static const StringRefId DrainFatigue; + static const StringRefId DrainSkill; + static const StringRefId DamageAttribute; + static const StringRefId DamageHealth; + static const StringRefId DamageMagicka; + static const StringRefId DamageFatigue; + static const StringRefId DamageSkill; + static const StringRefId Poison; + static const StringRefId WeaknessToFire; + static const StringRefId WeaknessToFrost; + static const StringRefId WeaknessToShock; + static const StringRefId WeaknessToMagicka; + static const StringRefId WeaknessToCommonDisease; + static const StringRefId WeaknessToBlightDisease; + static const StringRefId WeaknessToCorprusDisease; + static const StringRefId WeaknessToPoison; + static const StringRefId WeaknessToNormalWeapons; + static const StringRefId DisintegrateWeapon; + static const StringRefId DisintegrateArmor; + static const StringRefId Invisibility; + static const StringRefId Chameleon; + static const StringRefId Light; + static const StringRefId Sanctuary; + static const StringRefId NightEye; + static const StringRefId Charm; + static const StringRefId Paralyze; + static const StringRefId Silence; + static const StringRefId Blind; + static const StringRefId Sound; + static const StringRefId CalmHumanoid; + static const StringRefId CalmCreature; + static const StringRefId FrenzyHumanoid; + static const StringRefId FrenzyCreature; + static const StringRefId DemoralizeHumanoid; + static const StringRefId DemoralizeCreature; + static const StringRefId RallyHumanoid; + static const StringRefId RallyCreature; + static const StringRefId Dispel; + static const StringRefId Soultrap; + static const StringRefId Telekinesis; + static const StringRefId Mark; + static const StringRefId Recall; + static const StringRefId DivineIntervention; + static const StringRefId AlmsiviIntervention; + static const StringRefId DetectAnimal; + static const StringRefId DetectEnchantment; + static const StringRefId DetectKey; + static const StringRefId SpellAbsorption; + static const StringRefId Reflect; + static const StringRefId CureCommonDisease; + static const StringRefId CureBlightDisease; + static const StringRefId CureCorprusDisease; + static const StringRefId CurePoison; + static const StringRefId CureParalyzation; + static const StringRefId RestoreAttribute; + static const StringRefId RestoreHealth; + static const StringRefId RestoreMagicka; + static const StringRefId RestoreFatigue; + static const StringRefId RestoreSkill; + static const StringRefId FortifyAttribute; + static const StringRefId FortifyHealth; + static const StringRefId FortifyMagicka; + static const StringRefId FortifyFatigue; + static const StringRefId FortifySkill; + static const StringRefId FortifyMaximumMagicka; + static const StringRefId AbsorbAttribute; + static const StringRefId AbsorbHealth; + static const StringRefId AbsorbMagicka; + static const StringRefId AbsorbFatigue; + static const StringRefId AbsorbSkill; + static const StringRefId ResistFire; + static const StringRefId ResistFrost; + static const StringRefId ResistShock; + static const StringRefId ResistMagicka; + static const StringRefId ResistCommonDisease; + static const StringRefId ResistBlightDisease; + static const StringRefId ResistCorprusDisease; + static const StringRefId ResistPoison; + static const StringRefId ResistNormalWeapons; + static const StringRefId ResistParalysis; + static const StringRefId RemoveCurse; + static const StringRefId TurnUndead; + static const StringRefId SummonScamp; + static const StringRefId SummonClannfear; + static const StringRefId SummonDaedroth; + static const StringRefId SummonDremora; + static const StringRefId SummonAncestralGhost; + static const StringRefId SummonSkeletalMinion; + static const StringRefId SummonBonewalker; + static const StringRefId SummonGreaterBonewalker; + static const StringRefId SummonBonelord; + static const StringRefId SummonWingedTwilight; + static const StringRefId SummonHunger; + static const StringRefId SummonGoldenSaint; + static const StringRefId SummonFlameAtronach; + static const StringRefId SummonFrostAtronach; + static const StringRefId SummonStormAtronach; + static const StringRefId FortifyAttack; + static const StringRefId CommandCreature; + static const StringRefId CommandHumanoid; + static const StringRefId BoundDagger; + static const StringRefId BoundLongsword; + static const StringRefId BoundMace; + static const StringRefId BoundBattleAxe; + static const StringRefId BoundSpear; + static const StringRefId BoundLongbow; + static const StringRefId ExtraSpell; + static const StringRefId BoundCuirass; + static const StringRefId BoundHelm; + static const StringRefId BoundBoots; + static const StringRefId BoundShield; + static const StringRefId BoundGloves; + static const StringRefId Corprus; + static const StringRefId Vampirism; + static const StringRefId SummonCenturionSphere; + static const StringRefId SunDamage; + static const StringRefId StuntedMagicka; - // Tribunal only - SummonFabricant = 137, + // Tribunal only + static const StringRefId SummonFabricant; - // Bloodmoon only - SummonWolf = 138, - SummonBear = 139, - SummonBonewolf = 140, - SummonCreature04 = 141, - SummonCreature05 = 142, + // Bloodmoon only + static const StringRefId SummonWolf; + static const StringRefId SummonBear; + static const StringRefId SummonBonewolf; + static const StringRefId SummonCreature04; + static const StringRefId SummonCreature05; - Length - }; + static constexpr int Length = 143; - static const std::array sGmstEffectIds; - static const std::array sIndexNames; - static const std::map sGmstEffectIdToIndexMap; - static const std::map sIndexNameToIndexMap; - - static const std::string& indexToGmstString(int effectID); - static std::string_view indexToName(int effectID); - static int indexNameToIndex(std::string_view effect); - static int effectGmstIdToIndex(std::string_view gmstId); + static std::string_view refIdToGmstString(RefId effectId); + static RefId effectGmstIdToRefId(std::string_view gmstId); static RefId indexToRefId(int index); + static int refIdToIndex(RefId effectId); + + static std::string_view indexToName(int index); }; } #endif diff --git a/components/esm3/magiceffects.cpp b/components/esm3/magiceffects.cpp index a8a759949b..6b86dfb02b 100644 --- a/components/esm3/magiceffects.cpp +++ b/components/esm3/magiceffects.cpp @@ -3,6 +3,8 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { @@ -10,7 +12,7 @@ namespace ESM { for (const auto& [key, params] : mEffects) { - esm.writeHNT("EFID", key); + esm.writeHNT("EFID", ESM::MagicEffect::refIdToIndex(key)); esm.writeHNT("BASE", params.first); esm.writeHNT("MODI", params.second); } @@ -28,7 +30,7 @@ namespace ESM params.second = 0.f; else esm.getHNT(params.second, "MODI"); - mEffects.emplace(id, params); + mEffects.emplace(ESM::MagicEffect::indexToRefId(id), params); } } diff --git a/components/esm3/magiceffects.hpp b/components/esm3/magiceffects.hpp index 74a6e34743..d33ff96e9a 100644 --- a/components/esm3/magiceffects.hpp +++ b/components/esm3/magiceffects.hpp @@ -16,7 +16,7 @@ namespace ESM struct MagicEffects { // - std::map> mEffects; + std::map> mEffects; void load(ESMReader& esm); void save(ESMWriter& esm) const; @@ -24,14 +24,14 @@ namespace ESM struct SummonKey { - SummonKey(int32_t effectId, const ESM::RefId& sourceId, int32_t index) + SummonKey(ESM::RefId effectId, const ESM::RefId& sourceId, int32_t index) : mEffectId(effectId) , mSourceId(sourceId) , mEffectIndex(index) { } - int32_t mEffectId; + ESM::RefId mEffectId; ESM::RefId mSourceId; int32_t mEffectIndex; };