1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2026-01-25 12:30:55 +00:00

Merge branch 'Assign-RefIds-to-MagicEffects' into 'master'

Assign StringRefIds to magic effects

See merge request OpenMW/openmw!5064
This commit is contained in:
Alexei Kotov 2026-01-18 01:50:55 +03:00
commit dc022d10d4
58 changed files with 2164 additions and 1947 deletions

View file

@ -8,6 +8,7 @@
#include <components/esm3/loaddial.hpp>
#include <components/esm3/loadinfo.hpp>
#include <components/esm3/loadland.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadregn.hpp>
#include <components/esm3/loadscpt.hpp>
#include <components/esm3/loadweap.hpp>
@ -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,

View file

@ -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<ESM::MagicEffect>::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;

View file

@ -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<ESM::RefId, 6> 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);

View file

@ -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);

View file

@ -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<std::uint32_t>(index));
}
inline void setRecordId(const ESM::RefId& id, ESM::Skill& record)
{
if (const auto* skillId = id.getIf<ESM::SkillId>())

View file

@ -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 <class T>
std::string operator()(const T& value) const

View file

@ -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<short>(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;
}

View file

@ -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:

View file

@ -71,7 +71,7 @@ namespace MWClass
std::unique_ptr<MWWorld::Action> Ingredient::use(const MWWorld::Ptr& ptr, bool force) const
{
if (ptr.get<ESM::Ingredient>()->mBase->mData.mEffectID[0] < 0)
if (ptr.get<ESM::Ingredient>()->mBase->mData.mEffectID[0].empty())
return std::make_unique<MWWorld::NullAction>();
std::unique_ptr<MWWorld::Action> action = std::make_unique<MWWorld::ActionEat>(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<short>(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);

View file

@ -459,7 +459,7 @@ namespace MWGui
for (const MWMechanics::EffectKey& effectKey : effectIds)
{
Widgets::SpellEffectParams params;
params.mEffectID = static_cast<short>(effectKey.mId);
params.mEffectID = effectKey.mId;
const ESM::MagicEffect* magicEffect
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effectKey.mId);
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)

View file

@ -1,5 +1,7 @@
#include "spellcreationdialog.hpp"
#include <format>
#include <MyGUI_Button.h>
#include <MyGUI_Gui.h>
#include <MyGUI_ImageBox.h>
@ -33,20 +35,20 @@
namespace
{
bool sortMagicEffects(short id1, short id2)
bool sortMagicEffects(ESM::RefId id1, ESM::RefId id2)
{
const MWWorld::Store<ESM::GameSetting>& gmst
= MWBase::Environment::get().getESMStore()->get<ESM::GameSetting>();
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<int16_t>(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<short> knownEffects;
std::vector<ESM::RefId> 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<ESM::MagicEffect>().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<ESM::GameSetting>()
.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<ESM::GameSetting>()
.find(ESM::MagicEffect::indexToGmstString(effectId))
.find(ESM::MagicEffect::refIdToGmstString(effectId))
->mValue.getString();
MyGUI::Button* w = mAvailableEffectsList->getItemWidget(name);
mAvailableButtons.emplace_back(w);

View file

@ -113,7 +113,7 @@ namespace MWGui
void setConstantEffect(bool constant);
protected:
std::map<int, short> mButtonMapping; // maps button ID to effect ID
std::map<int, ESM::RefId> mButtonMapping; // maps button ID to effect ID
Gui::MWList* mAvailableEffectsList;
MyGUI::ScrollView* mUsedEffectsView;
@ -123,7 +123,7 @@ namespace MWGui
std::unique_ptr<SelectSkillDialog> mSelectSkillDialog;
int mSelectedEffect;
short mSelectedKnownEffectId;
ESM::RefId mSelectedKnownEffectId;
bool mConstantEffect;

View file

@ -1,5 +1,6 @@
#include "spellicons.hpp"
#include <format>
#include <iomanip>
#include <sstream>
@ -28,7 +29,7 @@ namespace MWGui
MWWorld::Ptr player = MWMechanics::getPlayer();
const MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player);
std::map<int, std::vector<MagicEffectInfo>> effects;
std::map<ESM::RefId, std::vector<MagicEffectInfo>> 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;

View file

@ -44,7 +44,7 @@ namespace MWGui
void updateWidgets(MyGUI::Widget* parent, bool adjustSize);
private:
std::map<int, MyGUI::ImageBox*> mWidgetMap;
std::map<ESM::RefId, MyGUI::ImageBox*> mWidgetMap;
};
}

View file

@ -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<ESM::MagicEffect>().find(effectId);
const ESM::Attribute* attribute

View file

@ -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<ESM::MagicEffect>().find(id);
const std::string& name = ESM::MagicEffect::indexToGmstString(id);
const ESM::MagicEffect* effect = store->get<ESM::MagicEffect>().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}: "

View file

@ -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

View file

@ -10,6 +10,7 @@
#include <components/esm/attr.hpp>
#include <components/esm/refid.hpp>
#include <components/esm3/effectlist.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadskil.hpp>
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);
}

View file

@ -1,5 +1,7 @@
#include "magicbindings.hpp"
#include <format>
#include <components/esm3/activespells.hpp>
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadarmo.hpp>
@ -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<ESM::MagicEffect>(magicEffects, context);
magicApi["effects"] = LuaUtil::makeReadOnly(magicEffects);
using MagicEffectStore = MWWorld::Store<ESM::MagicEffect>;
const MagicEffectStore* magicEffectStore
= &MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>();
auto magicEffectStoreT = state.new_usertype<MagicEffectStore>("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<int> id) -> std::tuple<sol::object, sol::object> {
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<ESM::MagicEffect>();
// Spell record
auto spellT = state.new_usertype<ESM::Spell>("ESM3_Spell");
@ -345,16 +314,14 @@ namespace MWLua
auto effectParamsT = state.new_usertype<ESM::IndexedENAMstruct>("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<std::string> {
ESM::RefId id = ESM::Skill::indexToRefId(params.mData.mSkill);
@ -386,12 +353,9 @@ namespace MWLua
auto magicEffectT = state.new_usertype<ESM::MagicEffect>("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<ESM::GameSetting>()
.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<ESM::ActiveEffect>("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>("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<std::string_view> argStr) -> MWMechanics::EffectKey {
auto id = ESM::MagicEffect::indexNameToIndex(idStr);
auto id = ESM::RefId::deserializeText(idStr);
auto* rec = MWBase::Environment::get().getWorld()->getStore().get<ESM::MagicEffect>().find(id);
MWMechanics::EffectKey key = MWMechanics::EffectKey(id);

View file

@ -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<int16_t>(rec.mData.mEffectID[i]);
effect.mData.mEffectID = rec.mData.mEffectID[i];
effect.mData.mSkill = static_cast<signed char>(rec.mData.mSkills[i]);
effect.mData.mAttribute = static_cast<signed char>(rec.mData.mAttributes[i]);
effect.mData.mRange = ESM::RT_Self;
@ -63,4 +63,4 @@ namespace MWLua
return res;
});
}
}
}

View file

@ -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) {

View file

@ -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);

View file

@ -796,7 +796,7 @@ namespace MWMechanics
continue;
for (const auto& effect : spell.getEffects())
{
static const std::array<int, 7> damageEffects{
static const std::array<ESM::RefId, 7> 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)
{

View file

@ -4,6 +4,7 @@
#include <cmath>
#include <algorithm>
#include <format>
#include <map>
#include <stdexcept>
@ -30,7 +31,7 @@ namespace
std::optional<MWMechanics::EffectKey> 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<int16_t>(effectKey.mId);
effect.mEffectID = effectKey.mId;
effect.mAttribute = -1;
effect.mSkill = -1;
@ -621,7 +622,7 @@ std::vector<std::string> MWMechanics::Alchemy::effectsDescription(
if (alchemySkill < fWortChanceValue * static_cast<int>(i + 1))
break;
if (effectID != -1)
if (!effectID.empty())
{
const ESM::Attribute* attribute
= store->get<ESM::Attribute>().search(ESM::Attribute::indexToRefId(data.mAttributes[i]));

View file

@ -2803,19 +2803,19 @@ namespace MWMechanics
// Stop any effects that are no longer active
std::vector<std::string_view> 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<ESM::MagicEffect>().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);
}
}

View file

@ -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<ESM::RefId, 3> 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

View file

@ -649,7 +649,7 @@ namespace MWMechanics
return mTimeOfDeath;
}
std::multimap<int, ESM::RefNum>& CreatureStats::getSummonedCreatureMap()
std::multimap<ESM::RefId, ESM::RefNum>& CreatureStats::getSummonedCreatureMap()
{
return mSummonedCreatures;
}

View file

@ -82,7 +82,7 @@ namespace MWMechanics
MWWorld::TimeStamp mTimeOfDeath;
private:
std::multimap<int, ESM::RefNum> mSummonedCreatures; // <Effect, Actor>
std::multimap<ESM::RefId, ESM::RefNum> mSummonedCreatures; // <Effect, Actor>
float mAwarenessTimer = 0.f;
int mAwarenessRoll = -1;
@ -231,7 +231,7 @@ namespace MWMechanics
void setBlock(bool value);
bool getBlock() const;
std::multimap<int, ESM::RefNum>& getSummonedCreatureMap(); // <Effect, summoned creature>
std::multimap<ESM::RefId, ESM::RefNum>& getSummonedCreatureMap(); // <Effect, summoned creature>
enum Flag
{

View file

@ -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<EffectParam> 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, {});
}

View file

@ -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<EffectParam> get(const EffectKey& key) const;
///< This function can safely be used for keys that are not present.
};

View file

@ -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<ESM::MagicEffect>().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);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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<ESM::Spell>().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<ESM::MagicEffect>().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<ESM::NPC>()->mBase->mRace;
const ESM::Race* race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().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<ESM::Weapon>()->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<float>& 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<ESM::NPC>()->mBase->mRace;
const ESM::Race* race = MWBase::Environment::get().getESMStore()->get<ESM::Race>().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<ESM::Weapon>()->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<float>& 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<ESM::RefId, 6> boundWeapons{
ESM::MagicEffect::BoundDagger,
ESM::MagicEffect::BoundLongsword,
ESM::MagicEffect::BoundMace,
ESM::MagicEffect::BoundBattleAxe,
ESM::MagicEffect::BoundSpear,
ESM::MagicEffect::BoundLongbow,
};
static const std::array<ESM::RefId, 6> 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))

View file

@ -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<ESM::MagicEffect>().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;
}
}

View file

@ -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

View file

@ -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);

View file

@ -166,13 +166,13 @@ namespace MWMechanics
throw std::range_error("Index out of range");
ESM::ENAMstruct effect;
effect.mEffectID = static_cast<int16_t>(ingredient->mData.mEffectID[index]);
effect.mEffectID = ingredient->mData.mEffectID[index];
effect.mSkill = static_cast<signed char>(ingredient->mData.mSkills[index]);
effect.mAttribute = static_cast<signed char>(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();

View file

@ -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<ESM::RefId, 22> 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<int, ESM::RefId>& getSummonMap()
static const std::map<ESM::RefId, ESM::RefId>& getSummonMap()
{
static std::map<int, ESM::RefId> summonMap;
static std::map<ESM::RefId, ESM::RefId> summonMap;
if (summonMap.size() > 0)
return summonMap;
const std::map<int, std::string_view> summonMapToGameSetting{
const std::map<ESM::RefId, std::string_view> 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<int, ESM::RefNum>& summon)
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<ESM::RefId, ESM::RefNum>& summon)
{
auto& creatureStats = summoner.getClass().getCreatureStats(summoner);
creatureStats.getActiveSpells().purge(

View file

@ -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<int, ESM::RefNum>& summon);
void purgeSummonEffect(const MWWorld::Ptr& summoner, const std::pair<ESM::RefId, ESM::RefNum>& 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);
}

View file

@ -613,13 +613,13 @@ namespace MWScript
return;
}
long key;
ESM::RefId key;
if (const auto k = ::Misc::StringUtils::toNumeric<long>(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())

View file

@ -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<int>(effectId)));
}
};
@ -1262,13 +1263,13 @@ namespace MWScript
template <class R>
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 R>
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 R>
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<int>(delta));
effects.modifyBase(MWMechanics::EffectKey(ESM::MagicEffect::NightEye), static_cast<int>(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<int>(delta));
effects.modifyBase(MWMechanics::EffectKey(ESM::MagicEffect::NightEye), static_cast<int>(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<OpGetMagicEffect<ImplicitRef>>(
Compiler::Stats::opcodeGetMagicEffect + i, positive, negative);

View file

@ -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;
}

View file

@ -110,9 +110,6 @@ namespace MWWorld
return ptr;
}
// Need to instantiate these before they're used
template class IndexedStore<ESM::MagicEffect>;
template <class T, class Id>
TypedDynamicStore<T, Id>::TypedDynamicStore()
{
@ -976,10 +973,6 @@ namespace MWWorld
TypedDynamicStore<ESM::GameSetting>::setUp();
}
// Magic effect
//=========================================================================
Store<ESM::MagicEffect>::Store() {}
// Attribute
//=========================================================================
@ -1260,7 +1253,7 @@ template class MWWorld::TypedDynamicStore<ESM::ItemLevList>;
// template class MWWorld::Store<ESM::LandTexture>;
template class MWWorld::TypedDynamicStore<ESM::Light>;
template class MWWorld::TypedDynamicStore<ESM::Lockpick>;
// template class MWWorld::Store<ESM::MagicEffect>;
template class MWWorld::TypedDynamicStore<ESM::MagicEffect>;
template class MWWorld::TypedDynamicStore<ESM::Miscellaneous>;
template class MWWorld::TypedDynamicStore<ESM::NPC>;
// template class MWWorld::Store<ESM::Pathgrid>;

View file

@ -17,6 +17,7 @@
#include <components/esm3/loadglob.hpp>
#include <components/esm3/loadgmst.hpp>
#include <components/esm3/loadland.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadpgrd.hpp>
#include <components/esm3/loadskil.hpp>
#include <components/esm4/loadachr.hpp>
@ -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<ESM::GameSetting>& settings);
};
template <>
class Store<ESM::MagicEffect> : public IndexedStore<ESM::MagicEffect>
{
public:
Store();
};
template <>
class Store<ESM::Attribute> : public TypedDynamicStore<ESM::Attribute>
{

View file

@ -3108,7 +3108,7 @@ namespace MWWorld
const MWWorld::Class& cls = ptr.getClass();
if (cls.isActor())
{
std::set<int> playing;
std::set<ESM::RefId> playing;
for (const auto& params : cls.getCreatureStats(ptr).getActiveSpells())
{
for (const auto& effect : params.getEffects())

View file

@ -447,6 +447,8 @@ namespace
refId = ESM::Attribute::Strength;
else if constexpr (std::is_same_v<RecordType, ESM::Skill>)
refId = ESM::Skill::Block;
else if constexpr (std::is_same_v<RecordType, ESM::MagicEffect>)
refId = ESM::MagicEffect::WaterBreathing;
else
refId = ESM::StringRefId(stringId);
@ -501,7 +503,6 @@ namespace
}
}
static_assert(ESM::hasIndex<ESM::MagicEffect>);
static_assert(ESM::hasStringId<ESM::Dialogue>);
template <class T, class = std::void_t<>>

View file

@ -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<ActiveSpells::ActiveSpellParams>& 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<ESM::RefId>(&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;

View file

@ -31,7 +31,7 @@ namespace ESM
Flag_Invalid = 1 << 5
};
int32_t mEffectId;
RefId mEffectId;
float mMagnitude;
float mMinMagnitude;
float mMaxMagnitude;

View file

@ -2,6 +2,8 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include <components/esm3/loadmgef.hpp>
#include <limits>
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<uint32_t>(actorId), .mContentFile = -1 });
mSummonedCreatureMap[SummonKey(ESM::MagicEffect::indexToRefId(magicEffect), source, effectIndex)]
= actorId;
mSummonedCreatures.emplace(ESM::MagicEffect::indexToRefId(magicEffect),
RefNum{ .mIndex = static_cast<uint32_t>(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");
}

View file

@ -43,7 +43,7 @@ namespace ESM
std::array<StatState<int>, 4> mAiSettings;
std::map<SummonKey, int> mSummonedCreatureMap;
std::multimap<int, RefNum> mSummonedCreatures;
std::multimap<ESM::RefId, RefNum> mSummonedCreatures;
std::vector<int> mSummonGraveyard;
TimeStamp mTradeTime;

View file

@ -3,11 +3,55 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include <format>
#include <components/esm3/loadmgef.hpp>
#include <components/misc/concepts.hpp>
namespace ESM
{
template <Misc::SameAsWithoutCvref<ENAMstruct> 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<int16_t>(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 <Misc::SameAsWithoutCvref<EsmENAMstruct> 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<uint32_t>(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);
}
}

View file

@ -4,6 +4,8 @@
#include <cstdint>
#include <vector>
#include <components/esm/refid.hpp>
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.)

View file

@ -3,11 +3,46 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include <components/esm3/loadmgef.hpp>
#include <components/misc/concepts.hpp>
namespace ESM
{
template <Misc::SameAsWithoutCvref<Ingredient::IRDTstruct> 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 <Misc::SameAsWithoutCvref<EsmIRDTstruct> 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;
}

View file

@ -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
};

View file

@ -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<short, short> makeEffectsMap()
std::unordered_map<RefId, RefId> makeResistancesMap()
{
std::map<short, short> effects;
std::unordered_map<RefId, RefId> 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
// <Effect, Effect providing resistance against first effect>
static const std::map<short, short> effects = makeEffectsMap();
static const std::unordered_map<RefId, RefId> 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<short, short> effects;
if (effects.empty())
std::unordered_map<RefId, RefId> 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<RefId, RefId> 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<std::string, MagicEffect::Length> MagicEffect::sGmstEffectIds = {
RefId MagicEffect::getWeaknessEffect(RefId effectId)
{
static const std::unordered_map<RefId, RefId> 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<std::string_view, MagicEffect::Length> sGmstEffectIds = {
"sEffectWaterBreathing",
"sEffectSwiftSwim",
"sEffectWaterWalking",
@ -386,203 +581,184 @@ namespace ESM
"sEffectSummonCreature05",
};
// Map effect ID to identifying name
const std::array<std::string_view, MagicEffect::Length> 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<RefId, MagicEffect::Length> 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 <typename Collection>
static std::map<std::string_view, int, Misc::StringUtils::CiComp> initStringToIntMap(const Collection& strings)
template <typename Collection, typename Comparator = std::less<typename Collection::value_type>>
static std::map<typename Collection::value_type, int, Comparator> initToIndexMap(
const Collection& data, Comparator comp = Comparator())
{
std::map<std::string_view, int, Misc::StringUtils::CiComp> map;
for (size_t i = 0; i < strings.size(); i++)
map[strings[i]] = static_cast<int>(i);
std::map<typename Collection::value_type, int, Comparator> map(comp);
for (size_t i = 0; i < data.size(); ++i)
map[data[i]] = static_cast<int>(i);
return map;
}
const std::map<std::string_view, int, Misc::StringUtils::CiComp> MagicEffect::sGmstEffectIdToIndexMap
= initStringToIntMap(MagicEffect::sGmstEffectIds);
const std::map<std::string_view, int, Misc::StringUtils::CiComp> sGmstEffectIdToIndexMap
= initToIndexMap(sGmstEffectIds, Misc::StringUtils::CiComp());
const std::map<std::string_view, int, Misc::StringUtils::CiComp> MagicEffect::sIndexNameToIndexMap
= initStringToIntMap(MagicEffect::sIndexNames);
class FindSecond
{
std::string_view mName;
public:
FindSecond(std::string_view name)
: mName(name)
{
}
bool operator()(const std::pair<short, std::string>& item) const
{
if (Misc::StringUtils::ciEqual(item.second, mName))
return true;
return false;
}
};
const std::map<RefId, int> 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<std::size_t>(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<std::size_t>(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<std::uint32_t>(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();
}
}

View file

@ -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<std::string, Length> sGmstEffectIds;
static const std::array<std::string_view, Length> sIndexNames;
static const std::map<std::string_view, int, Misc::StringUtils::CiComp> sGmstEffectIdToIndexMap;
static const std::map<std::string_view, int, Misc::StringUtils::CiComp> 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

View file

@ -3,6 +3,8 @@
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include <components/esm3/loadmgef.hpp>
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);
}
}

View file

@ -16,7 +16,7 @@ namespace ESM
struct MagicEffects
{
// <Effect Id, Base value, Modifier>
std::map<int32_t, std::pair<int32_t, float>> mEffects;
std::map<ESM::RefId, std::pair<int32_t, float>> 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;
};