mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-16 15:29:55 +00:00
Merge branch 'spellcast-refactor' into 'master'
Spellcast related Lua API + spellcasting/activespell refactor See merge request OpenMW/openmw!3922
This commit is contained in:
commit
012d10703f
55 changed files with 974 additions and 527 deletions
|
@ -180,22 +180,23 @@ namespace
|
|||
void printEffectList(const ESM::EffectList& effects)
|
||||
{
|
||||
int i = 0;
|
||||
for (const ESM::ENAMstruct& effect : effects.mList)
|
||||
for (const ESM::IndexedENAMstruct& effect : effects.mList)
|
||||
{
|
||||
std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mEffectID) << " (" << effect.mEffectID
|
||||
<< ")" << std::endl;
|
||||
if (effect.mSkill != -1)
|
||||
std::cout << " Skill: " << skillLabel(effect.mSkill) << " (" << (int)effect.mSkill << ")"
|
||||
std::cout << " Effect[" << i << "]: " << magicEffectLabel(effect.mData.mEffectID) << " ("
|
||||
<< effect.mData.mEffectID << ")" << std::endl;
|
||||
if (effect.mData.mSkill != -1)
|
||||
std::cout << " Skill: " << skillLabel(effect.mData.mSkill) << " (" << (int)effect.mData.mSkill << ")"
|
||||
<< std::endl;
|
||||
if (effect.mData.mAttribute != -1)
|
||||
std::cout << " Attribute: " << attributeLabel(effect.mData.mAttribute) << " ("
|
||||
<< (int)effect.mData.mAttribute << ")" << std::endl;
|
||||
std::cout << " Range: " << rangeTypeLabel(effect.mData.mRange) << " (" << effect.mData.mRange << ")"
|
||||
<< std::endl;
|
||||
if (effect.mAttribute != -1)
|
||||
std::cout << " Attribute: " << attributeLabel(effect.mAttribute) << " (" << (int)effect.mAttribute
|
||||
<< ")" << std::endl;
|
||||
std::cout << " Range: " << rangeTypeLabel(effect.mRange) << " (" << effect.mRange << ")" << std::endl;
|
||||
// Area is always zero if range type is "Self"
|
||||
if (effect.mRange != ESM::RT_Self)
|
||||
std::cout << " Area: " << effect.mArea << std::endl;
|
||||
std::cout << " Duration: " << effect.mDuration << std::endl;
|
||||
std::cout << " Magnitude: " << effect.mMagnMin << "-" << effect.mMagnMax << std::endl;
|
||||
if (effect.mData.mRange != ESM::RT_Self)
|
||||
std::cout << " Area: " << effect.mData.mArea << std::endl;
|
||||
std::cout << " Duration: " << effect.mData.mDuration << std::endl;
|
||||
std::cout << " Magnitude: " << effect.mData.mMagnMin << "-" << effect.mData.mMagnMax << std::endl;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,38 +60,38 @@ void CSMTools::EnchantmentCheckStage::perform(int stage, CSMDoc::Messages& messa
|
|||
}
|
||||
else
|
||||
{
|
||||
std::vector<ESM::ENAMstruct>::const_iterator effect = enchantment.mEffects.mList.begin();
|
||||
std::vector<ESM::IndexedENAMstruct>::const_iterator effect = enchantment.mEffects.mList.begin();
|
||||
|
||||
for (size_t i = 1; i <= enchantment.mEffects.mList.size(); i++)
|
||||
{
|
||||
const std::string number = std::to_string(i);
|
||||
// At the time of writing this effects, attributes and skills are hardcoded
|
||||
if (effect->mEffectID < 0 || effect->mEffectID > 142)
|
||||
if (effect->mData.mEffectID < 0 || effect->mData.mEffectID > 142)
|
||||
{
|
||||
messages.add(id, "Effect #" + number + " is invalid", "", CSMDoc::Message::Severity_Error);
|
||||
++effect;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (effect->mSkill < -1 || effect->mSkill > 26)
|
||||
if (effect->mData.mSkill < -1 || effect->mData.mSkill > 26)
|
||||
messages.add(
|
||||
id, "Effect #" + number + " affected skill is invalid", "", CSMDoc::Message::Severity_Error);
|
||||
if (effect->mAttribute < -1 || effect->mAttribute > 7)
|
||||
if (effect->mData.mAttribute < -1 || effect->mData.mAttribute > 7)
|
||||
messages.add(
|
||||
id, "Effect #" + number + " affected attribute is invalid", "", CSMDoc::Message::Severity_Error);
|
||||
if (effect->mRange < 0 || effect->mRange > 2)
|
||||
if (effect->mData.mRange < 0 || effect->mData.mRange > 2)
|
||||
messages.add(id, "Effect #" + number + " range is invalid", "", CSMDoc::Message::Severity_Error);
|
||||
if (effect->mArea < 0)
|
||||
if (effect->mData.mArea < 0)
|
||||
messages.add(id, "Effect #" + number + " area is negative", "", CSMDoc::Message::Severity_Error);
|
||||
if (effect->mDuration < 0)
|
||||
if (effect->mData.mDuration < 0)
|
||||
messages.add(id, "Effect #" + number + " duration is negative", "", CSMDoc::Message::Severity_Error);
|
||||
if (effect->mMagnMin < 0)
|
||||
if (effect->mData.mMagnMin < 0)
|
||||
messages.add(
|
||||
id, "Effect #" + number + " minimum magnitude is negative", "", CSMDoc::Message::Severity_Error);
|
||||
if (effect->mMagnMax < 0)
|
||||
if (effect->mData.mMagnMax < 0)
|
||||
messages.add(
|
||||
id, "Effect #" + number + " maximum magnitude is negative", "", CSMDoc::Message::Severity_Error);
|
||||
if (effect->mMagnMin > effect->mMagnMax)
|
||||
if (effect->mData.mMagnMin > effect->mData.mMagnMax)
|
||||
messages.add(id, "Effect #" + number + " minimum magnitude is higher than maximum magnitude", "",
|
||||
CSMDoc::Message::Severity_Error);
|
||||
++effect;
|
||||
|
|
|
@ -255,20 +255,22 @@ namespace CSMWorld
|
|||
{
|
||||
ESXRecordT magic = record.get();
|
||||
|
||||
std::vector<ESM::ENAMstruct>& effectsList = magic.mEffects.mList;
|
||||
std::vector<ESM::IndexedENAMstruct>& effectsList = magic.mEffects.mList;
|
||||
|
||||
// blank row
|
||||
ESM::ENAMstruct effect;
|
||||
effect.mEffectID = 0;
|
||||
effect.mSkill = -1;
|
||||
effect.mAttribute = -1;
|
||||
effect.mRange = 0;
|
||||
effect.mArea = 0;
|
||||
effect.mDuration = 0;
|
||||
effect.mMagnMin = 0;
|
||||
effect.mMagnMax = 0;
|
||||
ESM::IndexedENAMstruct effect;
|
||||
effect.mIndex = position;
|
||||
effect.mData.mEffectID = 0;
|
||||
effect.mData.mSkill = -1;
|
||||
effect.mData.mAttribute = -1;
|
||||
effect.mData.mRange = 0;
|
||||
effect.mData.mArea = 0;
|
||||
effect.mData.mDuration = 0;
|
||||
effect.mData.mMagnMin = 0;
|
||||
effect.mData.mMagnMax = 0;
|
||||
|
||||
effectsList.insert(effectsList.begin() + position, effect);
|
||||
magic.mEffects.updateIndexes();
|
||||
|
||||
record.setModified(magic);
|
||||
}
|
||||
|
@ -277,12 +279,13 @@ namespace CSMWorld
|
|||
{
|
||||
ESXRecordT magic = record.get();
|
||||
|
||||
std::vector<ESM::ENAMstruct>& effectsList = magic.mEffects.mList;
|
||||
std::vector<ESM::IndexedENAMstruct>& effectsList = magic.mEffects.mList;
|
||||
|
||||
if (rowToRemove < 0 || rowToRemove >= static_cast<int>(effectsList.size()))
|
||||
throw std::runtime_error("index out of range");
|
||||
|
||||
effectsList.erase(effectsList.begin() + rowToRemove);
|
||||
magic.mEffects.updateIndexes();
|
||||
|
||||
record.setModified(magic);
|
||||
}
|
||||
|
@ -292,7 +295,7 @@ namespace CSMWorld
|
|||
ESXRecordT magic = record.get();
|
||||
|
||||
magic.mEffects.mList
|
||||
= static_cast<const NestedTableWrapper<std::vector<ESM::ENAMstruct>>&>(nestedTable).mNestedTable;
|
||||
= static_cast<const NestedTableWrapper<std::vector<ESM::IndexedENAMstruct>>&>(nestedTable).mNestedTable;
|
||||
|
||||
record.setModified(magic);
|
||||
}
|
||||
|
@ -300,19 +303,19 @@ namespace CSMWorld
|
|||
NestedTableWrapperBase* table(const Record<ESXRecordT>& record) const override
|
||||
{
|
||||
// deleted by dtor of NestedTableStoring
|
||||
return new NestedTableWrapper<std::vector<ESM::ENAMstruct>>(record.get().mEffects.mList);
|
||||
return new NestedTableWrapper<std::vector<ESM::IndexedENAMstruct>>(record.get().mEffects.mList);
|
||||
}
|
||||
|
||||
QVariant getData(const Record<ESXRecordT>& record, int subRowIndex, int subColIndex) const override
|
||||
{
|
||||
ESXRecordT magic = record.get();
|
||||
|
||||
std::vector<ESM::ENAMstruct>& effectsList = magic.mEffects.mList;
|
||||
std::vector<ESM::IndexedENAMstruct>& effectsList = magic.mEffects.mList;
|
||||
|
||||
if (subRowIndex < 0 || subRowIndex >= static_cast<int>(effectsList.size()))
|
||||
throw std::runtime_error("index out of range");
|
||||
|
||||
ESM::ENAMstruct effect = effectsList[subRowIndex];
|
||||
ESM::ENAMstruct effect = effectsList[subRowIndex].mData;
|
||||
switch (subColIndex)
|
||||
{
|
||||
case 0:
|
||||
|
@ -374,12 +377,12 @@ namespace CSMWorld
|
|||
{
|
||||
ESXRecordT magic = record.get();
|
||||
|
||||
std::vector<ESM::ENAMstruct>& effectsList = magic.mEffects.mList;
|
||||
std::vector<ESM::IndexedENAMstruct>& effectsList = magic.mEffects.mList;
|
||||
|
||||
if (subRowIndex < 0 || subRowIndex >= static_cast<int>(effectsList.size()))
|
||||
throw std::runtime_error("index out of range");
|
||||
|
||||
ESM::ENAMstruct effect = effectsList[subRowIndex];
|
||||
ESM::ENAMstruct effect = effectsList[subRowIndex].mData;
|
||||
switch (subColIndex)
|
||||
{
|
||||
case 0:
|
||||
|
@ -438,7 +441,7 @@ namespace CSMWorld
|
|||
throw std::runtime_error("Magic Effects subcolumn index out of range");
|
||||
}
|
||||
|
||||
magic.mEffects.mList[subRowIndex] = effect;
|
||||
magic.mEffects.mList[subRowIndex].mData = effect;
|
||||
|
||||
record.setModified(magic);
|
||||
}
|
||||
|
|
|
@ -265,7 +265,7 @@ namespace MWBase
|
|||
virtual bool isReadyToBlock(const MWWorld::Ptr& ptr) const = 0;
|
||||
virtual bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const = 0;
|
||||
|
||||
virtual void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) = 0;
|
||||
virtual void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) = 0;
|
||||
|
||||
virtual void processChangedSettings(const std::set<std::pair<std::string, std::string>>& settings) = 0;
|
||||
|
||||
|
|
|
@ -461,7 +461,7 @@ namespace MWBase
|
|||
*/
|
||||
virtual MWWorld::SpellCastState startSpellCast(const MWWorld::Ptr& actor) = 0;
|
||||
|
||||
virtual void castSpell(const MWWorld::Ptr& actor, bool manualSpell = false) = 0;
|
||||
virtual void castSpell(const MWWorld::Ptr& actor, bool scriptedSpell = false) = 0;
|
||||
|
||||
virtual void launchMagicBolt(const ESM::RefId& spellId, const MWWorld::Ptr& caster,
|
||||
const osg::Vec3f& fallbackDirection, ESM::RefNum item)
|
||||
|
|
|
@ -136,7 +136,7 @@ namespace MWClass
|
|||
const ESM::MagicEffect* effect = store.get<ESM::MagicEffect>().find(ESM::MagicEffect::Telekinesis);
|
||||
|
||||
animation->addSpellCastGlow(
|
||||
effect, 1); // 1 second glow to match the time taken for a door opening or closing
|
||||
effect->getColor(), 1); // 1 second glow to match the time taken for a door opening or closing
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@ namespace MWGui
|
|||
|
||||
void EnchantingDialog::notifyEffectsChanged()
|
||||
{
|
||||
mEffectList.mList = mEffects;
|
||||
mEffectList.populate(mEffects);
|
||||
mEnchanting.setEffect(mEffectList);
|
||||
updateLabels();
|
||||
}
|
||||
|
|
|
@ -427,7 +427,7 @@ namespace MWGui
|
|||
{
|
||||
// use the icon of the first effect
|
||||
const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(
|
||||
spell->mEffects.mList.front().mEffectID);
|
||||
spell->mEffects.mList.front().mData.mEffectID);
|
||||
std::string icon = effect->mIcon;
|
||||
std::replace(icon.begin(), icon.end(), '/', '\\');
|
||||
size_t slashPos = icon.rfind('\\');
|
||||
|
|
|
@ -299,7 +299,8 @@ namespace MWGui
|
|||
mSelected->button->setUserString("Spell", spellId.serialize());
|
||||
|
||||
// use the icon of the first effect
|
||||
const ESM::MagicEffect* effect = esmStore.get<ESM::MagicEffect>().find(spell->mEffects.mList.front().mEffectID);
|
||||
const ESM::MagicEffect* effect
|
||||
= esmStore.get<ESM::MagicEffect>().find(spell->mEffects.mList.front().mData.mEffectID);
|
||||
|
||||
std::string path = effect->mIcon;
|
||||
std::replace(path.begin(), path.end(), '/', '\\');
|
||||
|
|
|
@ -470,9 +470,7 @@ namespace MWGui
|
|||
y *= 1.5;
|
||||
}
|
||||
|
||||
ESM::EffectList effectList;
|
||||
effectList.mList = mEffects;
|
||||
mSpell.mEffects = std::move(effectList);
|
||||
mSpell.mEffects.populate(mEffects);
|
||||
mSpell.mData.mCost = int(y);
|
||||
mSpell.mData.mType = ESM::Spell::ST_Spell;
|
||||
mSpell.mData.mFlags = 0;
|
||||
|
@ -528,10 +526,11 @@ namespace MWGui
|
|||
if (spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
continue;
|
||||
|
||||
for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList)
|
||||
for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList)
|
||||
{
|
||||
int16_t effectId = effectInfo.mData.mEffectID;
|
||||
const ESM::MagicEffect* effect
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effectInfo.mEffectID);
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effectId);
|
||||
|
||||
// skip effects that do not allow spellmaking/enchanting
|
||||
int requiredFlags
|
||||
|
@ -539,8 +538,8 @@ namespace MWGui
|
|||
if (!(effect->mData.mFlags & requiredFlags))
|
||||
continue;
|
||||
|
||||
if (std::find(knownEffects.begin(), knownEffects.end(), effectInfo.mEffectID) == knownEffects.end())
|
||||
knownEffects.push_back(effectInfo.mEffectID);
|
||||
if (std::find(knownEffects.begin(), knownEffects.end(), effectId) == knownEffects.end())
|
||||
knownEffects.push_back(effectId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -48,14 +48,14 @@ namespace MWGui
|
|||
|
||||
for (const auto& effect : effects.mList)
|
||||
{
|
||||
short effectId = effect.mEffectID;
|
||||
short effectId = effect.mData.mEffectID;
|
||||
|
||||
if (effectId != -1)
|
||||
{
|
||||
const ESM::MagicEffect* magicEffect = store.get<ESM::MagicEffect>().find(effectId);
|
||||
const ESM::Attribute* attribute
|
||||
= store.get<ESM::Attribute>().search(ESM::Attribute::indexToRefId(effect.mAttribute));
|
||||
const ESM::Skill* skill = store.get<ESM::Skill>().search(ESM::Skill::indexToRefId(effect.mSkill));
|
||||
= store.get<ESM::Attribute>().search(ESM::Attribute::indexToRefId(effect.mData.mAttribute));
|
||||
const ESM::Skill* skill = store.get<ESM::Skill>().search(ESM::Skill::indexToRefId(effect.mData.mSkill));
|
||||
|
||||
std::string fullEffectName = MWMechanics::getMagicEffectString(*magicEffect, attribute, skill);
|
||||
std::string convert = Utf8Stream::lowerCaseUtf8(fullEffectName);
|
||||
|
|
|
@ -222,17 +222,17 @@ namespace MWGui
|
|||
= store->get<ESM::Spell>().find(ESM::RefId::deserialize(focus->getUserString("Spell")));
|
||||
info.caption = spell->mName;
|
||||
Widgets::SpellEffectList effects;
|
||||
for (const ESM::ENAMstruct& spellEffect : spell->mEffects.mList)
|
||||
for (const ESM::IndexedENAMstruct& spellEffect : spell->mEffects.mList)
|
||||
{
|
||||
Widgets::SpellEffectParams params;
|
||||
params.mEffectID = spellEffect.mEffectID;
|
||||
params.mSkill = ESM::Skill::indexToRefId(spellEffect.mSkill);
|
||||
params.mAttribute = ESM::Attribute::indexToRefId(spellEffect.mAttribute);
|
||||
params.mDuration = spellEffect.mDuration;
|
||||
params.mMagnMin = spellEffect.mMagnMin;
|
||||
params.mMagnMax = spellEffect.mMagnMax;
|
||||
params.mRange = spellEffect.mRange;
|
||||
params.mArea = spellEffect.mArea;
|
||||
params.mEffectID = spellEffect.mData.mEffectID;
|
||||
params.mSkill = ESM::Skill::indexToRefId(spellEffect.mData.mSkill);
|
||||
params.mAttribute = ESM::Attribute::indexToRefId(spellEffect.mData.mAttribute);
|
||||
params.mDuration = spellEffect.mData.mDuration;
|
||||
params.mMagnMin = spellEffect.mData.mMagnMin;
|
||||
params.mMagnMax = spellEffect.mData.mMagnMax;
|
||||
params.mRange = spellEffect.mData.mRange;
|
||||
params.mArea = spellEffect.mData.mArea;
|
||||
params.mIsConstant = (spell->mData.mType == ESM::Spell::ST_Ability);
|
||||
params.mNoTarget = false;
|
||||
effects.push_back(params);
|
||||
|
|
|
@ -195,18 +195,18 @@ namespace MWGui::Widgets
|
|||
const ESM::Spell* spell = store.get<ESM::Spell>().search(mId);
|
||||
MYGUI_ASSERT(spell, "spell with id '" << mId << "' not found");
|
||||
|
||||
for (const ESM::ENAMstruct& effectInfo : spell->mEffects.mList)
|
||||
for (const ESM::IndexedENAMstruct& effectInfo : spell->mEffects.mList)
|
||||
{
|
||||
MWSpellEffectPtr effect
|
||||
= creator->createWidget<MWSpellEffect>("MW_EffectImage", coord, MyGUI::Align::Default);
|
||||
SpellEffectParams params;
|
||||
params.mEffectID = effectInfo.mEffectID;
|
||||
params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill);
|
||||
params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute);
|
||||
params.mDuration = effectInfo.mDuration;
|
||||
params.mMagnMin = effectInfo.mMagnMin;
|
||||
params.mMagnMax = effectInfo.mMagnMax;
|
||||
params.mRange = effectInfo.mRange;
|
||||
params.mEffectID = effectInfo.mData.mEffectID;
|
||||
params.mSkill = ESM::Skill::indexToRefId(effectInfo.mData.mSkill);
|
||||
params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mData.mAttribute);
|
||||
params.mDuration = effectInfo.mData.mDuration;
|
||||
params.mMagnMin = effectInfo.mData.mMagnMin;
|
||||
params.mMagnMax = effectInfo.mData.mMagnMax;
|
||||
params.mRange = effectInfo.mData.mRange;
|
||||
params.mIsConstant = (flags & MWEffectList::EF_Constant) != 0;
|
||||
params.mNoTarget = (flags & MWEffectList::EF_NoTarget);
|
||||
params.mNoMagnitude = (flags & MWEffectList::EF_NoMagnitude);
|
||||
|
@ -308,17 +308,17 @@ namespace MWGui::Widgets
|
|||
SpellEffectList MWEffectList::effectListFromESM(const ESM::EffectList* effects)
|
||||
{
|
||||
SpellEffectList result;
|
||||
for (const ESM::ENAMstruct& effectInfo : effects->mList)
|
||||
for (const ESM::IndexedENAMstruct& effectInfo : effects->mList)
|
||||
{
|
||||
SpellEffectParams params;
|
||||
params.mEffectID = effectInfo.mEffectID;
|
||||
params.mSkill = ESM::Skill::indexToRefId(effectInfo.mSkill);
|
||||
params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mAttribute);
|
||||
params.mDuration = effectInfo.mDuration;
|
||||
params.mMagnMin = effectInfo.mMagnMin;
|
||||
params.mMagnMax = effectInfo.mMagnMax;
|
||||
params.mRange = effectInfo.mRange;
|
||||
params.mArea = effectInfo.mArea;
|
||||
params.mEffectID = effectInfo.mData.mEffectID;
|
||||
params.mSkill = ESM::Skill::indexToRefId(effectInfo.mData.mSkill);
|
||||
params.mAttribute = ESM::Attribute::indexToRefId(effectInfo.mData.mAttribute);
|
||||
params.mDuration = effectInfo.mData.mDuration;
|
||||
params.mMagnMin = effectInfo.mData.mMagnMin;
|
||||
params.mMagnMax = effectInfo.mData.mMagnMax;
|
||||
params.mRange = effectInfo.mData.mRange;
|
||||
params.mArea = effectInfo.mData.mArea;
|
||||
result.push_back(params);
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -13,12 +13,14 @@
|
|||
#include <components/lua/luastate.hpp>
|
||||
#include <components/misc/color.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/misc/strings/format.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwmechanics/activespells.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
#include "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/magiceffects.hpp"
|
||||
#include "../mwmechanics/spellutil.hpp"
|
||||
|
@ -141,7 +143,7 @@ namespace sol
|
|||
{
|
||||
};
|
||||
template <>
|
||||
struct is_automagical<ESM::ENAMstruct> : std::false_type
|
||||
struct is_automagical<ESM::IndexedENAMstruct> : std::false_type
|
||||
{
|
||||
};
|
||||
template <>
|
||||
|
@ -189,6 +191,26 @@ namespace MWLua
|
|||
return ESM::RefId::deserializeText(LuaUtil::cast<std::string_view>(recordOrId));
|
||||
}
|
||||
|
||||
static const ESM::Spell* toSpell(const sol::object& spellOrId)
|
||||
{
|
||||
if (spellOrId.is<ESM::Spell>())
|
||||
return spellOrId.as<const ESM::Spell*>();
|
||||
else
|
||||
{
|
||||
auto& store = MWBase::Environment::get().getWorld()->getStore();
|
||||
auto refId = ESM::RefId::deserializeText(LuaUtil::cast<std::string_view>(spellOrId));
|
||||
return store.get<ESM::Spell>().find(refId);
|
||||
}
|
||||
}
|
||||
|
||||
static sol::table effectParamsListToTable(sol::state_view& lua, const std::vector<ESM::IndexedENAMstruct>& effects)
|
||||
{
|
||||
sol::table res(lua, sol::create);
|
||||
for (size_t i = 0; i < effects.size(); ++i)
|
||||
res[i + 1] = effects[i]; // ESM::IndexedENAMstruct (effect params)
|
||||
return res;
|
||||
}
|
||||
|
||||
sol::table initCoreMagicBindings(const Context& context)
|
||||
{
|
||||
sol::state_view& lua = context.mLua->sol();
|
||||
|
@ -281,12 +303,12 @@ namespace MWLua
|
|||
spellT["name"] = sol::readonly_property([](const ESM::Spell& rec) -> std::string_view { return rec.mName; });
|
||||
spellT["type"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mType; });
|
||||
spellT["cost"] = sol::readonly_property([](const ESM::Spell& rec) -> int { return rec.mData.mCost; });
|
||||
spellT["effects"] = sol::readonly_property([&lua](const ESM::Spell& rec) -> sol::table {
|
||||
sol::table res(lua, sol::create);
|
||||
for (size_t i = 0; i < rec.mEffects.mList.size(); ++i)
|
||||
res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params)
|
||||
return res;
|
||||
});
|
||||
spellT["alwaysSucceedFlag"] = sol::readonly_property(
|
||||
[](const ESM::Spell& rec) -> bool { return !!(rec.mData.mFlags & ESM::Spell::F_Always); });
|
||||
spellT["autocalcFlag"] = sol::readonly_property(
|
||||
[](const ESM::Spell& rec) -> bool { return !!(rec.mData.mFlags & ESM::Spell::F_Autocalc); });
|
||||
spellT["effects"] = sol::readonly_property(
|
||||
[&lua](const ESM::Spell& rec) -> sol::table { return effectParamsListToTable(lua, rec.mEffects.mList); });
|
||||
|
||||
// Enchantment record
|
||||
auto enchantT = lua.new_usertype<ESM::Enchantment>("ESM3_Enchantment");
|
||||
|
@ -301,46 +323,49 @@ namespace MWLua
|
|||
enchantT["charge"]
|
||||
= sol::readonly_property([](const ESM::Enchantment& rec) -> int { return rec.mData.mCharge; });
|
||||
enchantT["effects"] = sol::readonly_property([&lua](const ESM::Enchantment& rec) -> sol::table {
|
||||
sol::table res(lua, sol::create);
|
||||
for (size_t i = 0; i < rec.mEffects.mList.size(); ++i)
|
||||
res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params)
|
||||
return res;
|
||||
return effectParamsListToTable(lua, rec.mEffects.mList);
|
||||
});
|
||||
|
||||
// Effect params
|
||||
auto effectParamsT = lua.new_usertype<ESM::ENAMstruct>("ESM3_EffectParams");
|
||||
effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::ENAMstruct& params) {
|
||||
const ESM::MagicEffect* const rec = magicEffectStore->find(params.mEffectID);
|
||||
auto effectParamsT = lua.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) + "]";
|
||||
};
|
||||
effectParamsT["effect"]
|
||||
= sol::readonly_property([magicEffectStore](const ESM::ENAMstruct& params) -> const ESM::MagicEffect* {
|
||||
return magicEffectStore->find(params.mEffectID);
|
||||
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["affectedSkill"]
|
||||
= sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional<std::string> {
|
||||
ESM::RefId id = ESM::Skill::indexToRefId(params.mSkill);
|
||||
= sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> sol::optional<std::string> {
|
||||
ESM::RefId id = ESM::Skill::indexToRefId(params.mData.mSkill);
|
||||
if (!id.empty())
|
||||
return id.serializeText();
|
||||
return sol::nullopt;
|
||||
});
|
||||
effectParamsT["affectedAttribute"]
|
||||
= sol::readonly_property([](const ESM::ENAMstruct& params) -> sol::optional<std::string> {
|
||||
ESM::RefId id = ESM::Attribute::indexToRefId(params.mAttribute);
|
||||
= sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> sol::optional<std::string> {
|
||||
ESM::RefId id = ESM::Attribute::indexToRefId(params.mData.mAttribute);
|
||||
if (!id.empty())
|
||||
return id.serializeText();
|
||||
return sol::nullopt;
|
||||
});
|
||||
effectParamsT["range"]
|
||||
= sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mRange; });
|
||||
= sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mRange; });
|
||||
effectParamsT["area"]
|
||||
= sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mArea; });
|
||||
= sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mArea; });
|
||||
effectParamsT["magnitudeMin"]
|
||||
= sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMin; });
|
||||
= sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mMagnMin; });
|
||||
effectParamsT["magnitudeMax"]
|
||||
= sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mMagnMax; });
|
||||
effectParamsT["duration"]
|
||||
= sol::readonly_property([](const ESM::ENAMstruct& params) -> int { return params.mDuration; });
|
||||
= sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mMagnMax; });
|
||||
effectParamsT["duration"] = sol::readonly_property(
|
||||
[](const ESM::IndexedENAMstruct& params) -> int { return params.mData.mDuration; });
|
||||
effectParamsT["index"]
|
||||
= sol::readonly_property([](const ESM::IndexedENAMstruct& params) -> int { return params.mIndex; });
|
||||
|
||||
// MagicEffect record
|
||||
auto magicEffectT = context.mLua->sol().new_usertype<ESM::MagicEffect>("ESM3_MagicEffect");
|
||||
|
@ -361,12 +386,22 @@ namespace MWLua
|
|||
magicEffectT["continuousVfx"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> bool {
|
||||
return (rec.mData.mFlags & ESM::MagicEffect::ContinuousVfx) != 0;
|
||||
});
|
||||
magicEffectT["castingStatic"] = sol::readonly_property(
|
||||
magicEffectT["areaSound"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> std::string { return rec.mAreaSound.serializeText(); });
|
||||
magicEffectT["boltSound"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> std::string { return rec.mBoltSound.serializeText(); });
|
||||
magicEffectT["castSound"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> std::string { return rec.mCastSound.serializeText(); });
|
||||
magicEffectT["hitSound"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> std::string { return rec.mHitSound.serializeText(); });
|
||||
magicEffectT["areaStatic"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); });
|
||||
magicEffectT["boltStatic"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> std::string { return rec.mBolt.serializeText(); });
|
||||
magicEffectT["castStatic"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> std::string { return rec.mCasting.serializeText(); });
|
||||
magicEffectT["hitStatic"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> std::string { return rec.mHit.serializeText(); });
|
||||
magicEffectT["areaStatic"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> std::string { return rec.mArea.serializeText(); });
|
||||
magicEffectT["name"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> std::string_view {
|
||||
return MWBase::Environment::get()
|
||||
.getWorld()
|
||||
|
@ -382,8 +417,20 @@ namespace MWLua
|
|||
magicEffectT["color"] = sol::readonly_property([](const ESM::MagicEffect& rec) -> Misc::Color {
|
||||
return Misc::Color(rec.mData.mRed / 255.f, rec.mData.mGreen / 255.f, rec.mData.mBlue / 255.f, 1.f);
|
||||
});
|
||||
magicEffectT["hasDuration"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> bool { return !(rec.mData.mFlags & ESM::MagicEffect::NoDuration); });
|
||||
magicEffectT["hasMagnitude"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> bool { return !(rec.mData.mFlags & ESM::MagicEffect::NoMagnitude); });
|
||||
// TODO: Not self-explanatory. Needs either a better name or documentation. The description in
|
||||
// loadmgef.hpp is uninformative.
|
||||
magicEffectT["isAppliedOnce"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::AppliedOnce; });
|
||||
magicEffectT["harmful"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::Harmful; });
|
||||
magicEffectT["casterLinked"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::CasterLinked; });
|
||||
magicEffectT["nonRecastable"] = sol::readonly_property(
|
||||
[](const ESM::MagicEffect& rec) -> bool { return rec.mData.mFlags & ESM::MagicEffect::NonRecastable; });
|
||||
|
||||
// TODO: Should we expose it? What happens if a spell has several effects with different projectileSpeed?
|
||||
// magicEffectT["projectileSpeed"]
|
||||
|
@ -397,6 +444,8 @@ namespace MWLua
|
|||
auto name = ESM::MagicEffect::indexToName(effect.mEffectId);
|
||||
return Misc::StringUtils::lowerCase(name);
|
||||
});
|
||||
activeSpellEffectT["index"]
|
||||
= sol::readonly_property([](const ESM::ActiveEffect& effect) -> int { return effect.mEffectIndex; });
|
||||
activeSpellEffectT["name"] = sol::readonly_property([](const ESM::ActiveEffect& effect) -> std::string {
|
||||
return MWMechanics::EffectKey(effect.mEffectId, effect.getSkillOrAttribute()).toString();
|
||||
});
|
||||
|
@ -460,12 +509,13 @@ namespace MWLua
|
|||
|
||||
auto activeSpellT = context.mLua->sol().new_usertype<ActiveSpell>("ActiveSpellParams");
|
||||
activeSpellT[sol::meta_function::to_string] = [](const ActiveSpell& activeSpell) {
|
||||
return "ActiveSpellParams[" + activeSpell.mParams.getId().serializeText() + "]";
|
||||
return "ActiveSpellParams[" + activeSpell.mParams.getSourceSpellId().serializeText() + "]";
|
||||
};
|
||||
activeSpellT["name"] = sol::readonly_property(
|
||||
[](const ActiveSpell& activeSpell) -> std::string_view { return activeSpell.mParams.getDisplayName(); });
|
||||
activeSpellT["id"] = sol::readonly_property(
|
||||
[](const ActiveSpell& activeSpell) -> std::string { return activeSpell.mParams.getId().serializeText(); });
|
||||
activeSpellT["id"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> std::string {
|
||||
return activeSpell.mParams.getSourceSpellId().serializeText();
|
||||
});
|
||||
activeSpellT["item"] = sol::readonly_property([&lua](const ActiveSpell& activeSpell) -> sol::object {
|
||||
auto item = activeSpell.mParams.getItem();
|
||||
if (!item.isSet())
|
||||
|
@ -502,6 +552,21 @@ namespace MWLua
|
|||
}
|
||||
return res;
|
||||
});
|
||||
activeSpellT["fromEquipment"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool {
|
||||
return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Equipment);
|
||||
});
|
||||
activeSpellT["temporary"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool {
|
||||
return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Temporary);
|
||||
});
|
||||
activeSpellT["affectsBaseValues"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool {
|
||||
return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues);
|
||||
});
|
||||
activeSpellT["stackable"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> bool {
|
||||
return activeSpell.mParams.hasFlag(ESM::ActiveSpells::Flag_Stackable);
|
||||
});
|
||||
activeSpellT["activeSpellId"] = sol::readonly_property([](const ActiveSpell& activeSpell) -> std::string {
|
||||
return activeSpell.mParams.getActiveSpellId().serializeText();
|
||||
});
|
||||
|
||||
auto activeEffectT = context.mLua->sol().new_usertype<ActiveEffect>("ActiveEffect");
|
||||
|
||||
|
@ -540,6 +605,78 @@ namespace MWLua
|
|||
return LuaUtil::makeReadOnly(magicApi);
|
||||
}
|
||||
|
||||
static std::pair<std::string_view, std::vector<ESM::IndexedENAMstruct>> getNameAndMagicEffects(
|
||||
const MWWorld::Ptr& actor, ESM::RefId id, const sol::table& effects, bool quiet)
|
||||
{
|
||||
std::vector<int32_t> effectIndexes;
|
||||
|
||||
for (const auto& entry : effects)
|
||||
{
|
||||
if (entry.second.is<int32_t>())
|
||||
effectIndexes.push_back(entry.second.as<int32_t>());
|
||||
else if (entry.second.is<ESM::IndexedENAMstruct>())
|
||||
throw std::runtime_error("Error: Adding effects as enam structs is not implemented, use indexes.");
|
||||
else
|
||||
throw std::runtime_error("Unexpected entry in 'effects' table while trying to add to active effects");
|
||||
}
|
||||
|
||||
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
|
||||
|
||||
auto getEffectsFromIndexes = [&](const ESM::EffectList& effects) {
|
||||
std::vector<ESM::IndexedENAMstruct> enams;
|
||||
for (auto index : effectIndexes)
|
||||
enams.push_back(effects.mList.at(index));
|
||||
return enams;
|
||||
};
|
||||
|
||||
auto getNameAndEffects = [&](auto* record) {
|
||||
return std::pair<std::string_view, std::vector<ESM::IndexedENAMstruct>>(
|
||||
record->mName, getEffectsFromIndexes(record->mEffects));
|
||||
};
|
||||
auto getNameAndEffectsEnch = [&](auto* record) {
|
||||
auto* enchantment = esmStore.get<ESM::Enchantment>().find(record->mEnchant);
|
||||
return std::pair<std::string_view, std::vector<ESM::IndexedENAMstruct>>(
|
||||
record->mName, getEffectsFromIndexes(enchantment->mEffects));
|
||||
};
|
||||
switch (esmStore.find(id))
|
||||
{
|
||||
case ESM::REC_ALCH:
|
||||
return getNameAndEffects(esmStore.get<ESM::Potion>().find(id));
|
||||
case ESM::REC_INGR:
|
||||
{
|
||||
// Ingredients are a special case as their effect list is calculated on consumption.
|
||||
const ESM::Ingredient* ingredient = esmStore.get<ESM::Ingredient>().find(id);
|
||||
std::vector<ESM::IndexedENAMstruct> enams;
|
||||
quiet = quiet || actor != MWMechanics::getPlayer();
|
||||
for (uint32_t i = 0; i < effectIndexes.size(); i++)
|
||||
{
|
||||
if (auto effect = MWMechanics::rollIngredientEffect(actor, ingredient, effectIndexes[i]))
|
||||
enams.push_back(effect->mList[0]);
|
||||
}
|
||||
if (enams.empty() && !quiet)
|
||||
{
|
||||
// "X has no effect on you"
|
||||
std::string message = esmStore.get<ESM::GameSetting>().find("sNotifyMessage50")->mValue.getString();
|
||||
message = Misc::StringUtils::format(message, ingredient->mName);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||
}
|
||||
return { ingredient->mName, std::move(enams) };
|
||||
}
|
||||
case ESM::REC_ARMO:
|
||||
return getNameAndEffectsEnch(esmStore.get<ESM::Armor>().find(id));
|
||||
case ESM::REC_BOOK:
|
||||
return getNameAndEffectsEnch(esmStore.get<ESM::Book>().find(id));
|
||||
case ESM::REC_CLOT:
|
||||
return getNameAndEffectsEnch(esmStore.get<ESM::Clothing>().find(id));
|
||||
case ESM::REC_WEAP:
|
||||
return getNameAndEffectsEnch(esmStore.get<ESM::Weapon>().find(id));
|
||||
default:
|
||||
// esmStore.find doesn't find REC_SPELs
|
||||
case ESM::REC_SPEL:
|
||||
return getNameAndEffects(esmStore.get<ESM::Spell>().find(id));
|
||||
}
|
||||
}
|
||||
|
||||
void addActorMagicBindings(sol::table& actor, const Context& context)
|
||||
{
|
||||
const MWWorld::Store<ESM::Spell>* spellStore
|
||||
|
@ -698,6 +835,16 @@ namespace MWLua
|
|||
});
|
||||
};
|
||||
|
||||
// types.Actor.spells(o):canUsePower()
|
||||
spellsT["canUsePower"] = [](const ActorSpells& spells, const sol::object& spellOrId) -> bool {
|
||||
if (spells.mActor.isLObject())
|
||||
throw std::runtime_error("Local scripts can modify only spells of the actor they are attached to.");
|
||||
auto* spell = toSpell(spellOrId);
|
||||
if (auto* store = spells.getStore())
|
||||
return store->canUsePower(spell);
|
||||
return false;
|
||||
};
|
||||
|
||||
// pairs(types.Actor.activeSpells(o))
|
||||
activeSpellsT["__pairs"] = [](sol::this_state ts, ActorActiveSpells& self) {
|
||||
sol::state_view lua(ts);
|
||||
|
@ -705,7 +852,7 @@ namespace MWLua
|
|||
return sol::as_function([lua, self]() mutable -> std::pair<sol::object, sol::object> {
|
||||
if (!self.isEnd())
|
||||
{
|
||||
auto id = sol::make_object(lua, self.mIterator->getId().serializeText());
|
||||
auto id = sol::make_object(lua, self.mIterator->getSourceSpellId().serializeText());
|
||||
auto params = sol::make_object(lua, ActiveSpell{ self.mActor, *self.mIterator });
|
||||
self.advance();
|
||||
return { id, params };
|
||||
|
@ -729,14 +876,97 @@ namespace MWLua
|
|||
};
|
||||
|
||||
// types.Actor.activeSpells(o):remove(id)
|
||||
activeSpellsT["remove"] = [](const ActorActiveSpells& spells, const sol::object& spellOrId) {
|
||||
activeSpellsT["remove"] = [context](const ActorActiveSpells& spells, std::string_view idStr) {
|
||||
if (spells.isLObject())
|
||||
throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to.");
|
||||
|
||||
auto id = toSpellId(spellOrId);
|
||||
context.mLuaManager->addAction([spells = spells, id = ESM::RefId::deserializeText(idStr)]() {
|
||||
if (auto* store = spells.getStore())
|
||||
{
|
||||
store->removeEffects(spells.mActor.ptr(), id);
|
||||
auto it = store->getActiveSpellById(id);
|
||||
if (it != store->end())
|
||||
{
|
||||
if (it->hasFlag(ESM::ActiveSpells::Flag_Temporary))
|
||||
store->removeEffectsByActiveSpellId(spells.mActor.ptr(), id);
|
||||
else
|
||||
throw std::runtime_error("Can only remove temporary effects.");
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// types.Actor.activeSpells(o):add(id, spellid, effects, options)
|
||||
activeSpellsT["add"] = [](const ActorActiveSpells& spells, const sol::table& options) {
|
||||
if (spells.isLObject())
|
||||
throw std::runtime_error("Local scripts can modify effect only on the actor they are attached to.");
|
||||
|
||||
if (auto* store = spells.getStore())
|
||||
{
|
||||
ESM::RefId id = ESM::RefId::deserializeText(options.get<std::string_view>("id"));
|
||||
sol::optional<Object> item = options.get<sol::optional<Object>>("item");
|
||||
ESM::RefNum itemId;
|
||||
if (item)
|
||||
itemId = item->id();
|
||||
sol::optional<Object> caster = options.get<sol::optional<Object>>("caster");
|
||||
bool stackable = options.get_or("stackable", false);
|
||||
bool ignoreReflect = options.get_or("ignoreReflect", false);
|
||||
bool ignoreSpellAbsorption = options.get_or("ignoreSpellAbsorption", false);
|
||||
bool ignoreResistances = options.get_or("ignoreResistances", false);
|
||||
sol::table effects = options.get<sol::table>("effects");
|
||||
bool quiet = options.get_or("quiet", false);
|
||||
if (effects.empty())
|
||||
throw std::runtime_error("Error: Parameter 'effects': cannot be an empty list/table");
|
||||
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
|
||||
auto [name, enams] = getNameAndMagicEffects(spells.mActor.ptr(), id, effects, quiet);
|
||||
name = options.get_or<std::string_view>("name", name);
|
||||
|
||||
MWWorld::Ptr casterPtr;
|
||||
if (caster)
|
||||
casterPtr = caster->ptrOrEmpty();
|
||||
|
||||
bool affectsHealth = false;
|
||||
MWMechanics::ActiveSpells::ActiveSpellParams params(casterPtr, id, name, itemId);
|
||||
params.setFlag(ESM::ActiveSpells::Flag_Lua);
|
||||
params.setFlag(ESM::ActiveSpells::Flag_Temporary);
|
||||
if (stackable)
|
||||
params.setFlag(ESM::ActiveSpells::Flag_Stackable);
|
||||
|
||||
for (auto enam : enams)
|
||||
{
|
||||
const ESM::MagicEffect* mgef = esmStore.get<ESM::MagicEffect>().find(enam.mData.mEffectID);
|
||||
MWMechanics::ActiveSpells::ActiveEffect effect;
|
||||
effect.mEffectId = enam.mData.mEffectID;
|
||||
effect.mArg = MWMechanics::EffectKey(enam.mData).mArg;
|
||||
effect.mMagnitude = 0.f;
|
||||
effect.mMinMagnitude = enam.mData.mMagnMin;
|
||||
effect.mMaxMagnitude = enam.mData.mMagnMax;
|
||||
effect.mEffectIndex = enam.mIndex;
|
||||
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
||||
if (ignoreReflect)
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect;
|
||||
if (ignoreSpellAbsorption)
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
|
||||
if (ignoreResistances)
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances;
|
||||
|
||||
bool hasDuration = !(mgef->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||
effect.mDuration = hasDuration ? static_cast<float>(enam.mData.mDuration) : 1.f;
|
||||
|
||||
bool appliedOnce = mgef->mData.mFlags & ESM::MagicEffect::AppliedOnce;
|
||||
if (!appliedOnce)
|
||||
effect.mDuration = std::max(1.f, effect.mDuration);
|
||||
effect.mTimeLeft = effect.mDuration;
|
||||
params.getEffects().emplace_back(effect);
|
||||
|
||||
affectsHealth = affectsHealth || mgef->mData.mFlags & ESM::MagicEffect::Harmful
|
||||
|| effect.mEffectId == ESM::MagicEffect::RestoreHealth;
|
||||
}
|
||||
store->addSpell(params);
|
||||
if (affectsHealth && casterPtr == MWMechanics::getPlayer())
|
||||
// If player is attempting to cast a harmful spell on or is healing a living target, show the
|
||||
// target's HP bar.
|
||||
// TODO: This should be moved to Lua once the HUD has been dehardcoded
|
||||
MWBase::Environment::get().getWindowManager()->setEnemy(spells.mActor.ptr());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -47,15 +47,16 @@ namespace MWLua
|
|||
{
|
||||
if (rec.mData.mEffectID[i] < 0)
|
||||
continue;
|
||||
ESM::ENAMstruct effect;
|
||||
effect.mEffectID = rec.mData.mEffectID[i];
|
||||
effect.mSkill = rec.mData.mSkills[i];
|
||||
effect.mAttribute = rec.mData.mAttributes[i];
|
||||
effect.mRange = ESM::RT_Self;
|
||||
effect.mArea = 0;
|
||||
effect.mDuration = 0;
|
||||
effect.mMagnMin = 0;
|
||||
effect.mMagnMax = 0;
|
||||
ESM::IndexedENAMstruct effect;
|
||||
effect.mData.mEffectID = rec.mData.mEffectID[i];
|
||||
effect.mData.mSkill = rec.mData.mSkills[i];
|
||||
effect.mData.mAttribute = rec.mData.mAttributes[i];
|
||||
effect.mData.mRange = ESM::RT_Self;
|
||||
effect.mData.mArea = 0;
|
||||
effect.mData.mDuration = 0;
|
||||
effect.mData.mMagnMin = 0;
|
||||
effect.mData.mMagnMax = 0;
|
||||
effect.mIndex = i;
|
||||
res[i + 1] = effect;
|
||||
}
|
||||
return res;
|
||||
|
|
|
@ -46,7 +46,10 @@ namespace
|
|||
size_t numEffects = effectsTable.size();
|
||||
potion.mEffects.mList.resize(numEffects);
|
||||
for (size_t i = 0; i < numEffects; ++i)
|
||||
potion.mEffects.mList[i] = LuaUtil::cast<ESM::ENAMstruct>(effectsTable[i + 1]);
|
||||
{
|
||||
potion.mEffects.mList[i] = LuaUtil::cast<ESM::IndexedENAMstruct>(effectsTable[i + 1]);
|
||||
}
|
||||
potion.mEffects.updateIndexes();
|
||||
}
|
||||
return potion;
|
||||
}
|
||||
|
@ -83,7 +86,7 @@ namespace MWLua
|
|||
record["effects"] = sol::readonly_property([context](const ESM::Potion& rec) -> sol::table {
|
||||
sol::table res(context.mLua->sol(), sol::create);
|
||||
for (size_t i = 0; i < rec.mEffects.mList.size(); ++i)
|
||||
res[i + 1] = rec.mEffects.mList[i]; // ESM::ENAMstruct (effect params)
|
||||
res[i + 1] = rec.mEffects.mList[i]; // ESM::IndexedENAMstruct (effect params)
|
||||
return res;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <components/misc/strings/algorithm.hpp>
|
||||
|
||||
#include <components/esm/generatedrefid.hpp>
|
||||
#include <components/esm3/loadench.hpp>
|
||||
#include <components/esm3/loadmgef.hpp>
|
||||
#include <components/esm3/loadstat.hpp>
|
||||
|
@ -49,16 +50,15 @@ namespace
|
|||
void addEffects(
|
||||
std::vector<ESM::ActiveEffect>& effects, const ESM::EffectList& list, bool ignoreResistances = false)
|
||||
{
|
||||
int currentEffectIndex = 0;
|
||||
for (const auto& enam : list.mList)
|
||||
{
|
||||
ESM::ActiveEffect effect;
|
||||
effect.mEffectId = enam.mEffectID;
|
||||
effect.mArg = MWMechanics::EffectKey(enam).mArg;
|
||||
effect.mEffectId = enam.mData.mEffectID;
|
||||
effect.mArg = MWMechanics::EffectKey(enam.mData).mArg;
|
||||
effect.mMagnitude = 0.f;
|
||||
effect.mMinMagnitude = enam.mMagnMin;
|
||||
effect.mMaxMagnitude = enam.mMagnMax;
|
||||
effect.mEffectIndex = currentEffectIndex++;
|
||||
effect.mMinMagnitude = enam.mData.mMagnMin;
|
||||
effect.mMaxMagnitude = enam.mData.mMagnMax;
|
||||
effect.mEffectIndex = enam.mIndex;
|
||||
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
||||
if (ignoreResistances)
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Resistances;
|
||||
|
@ -82,12 +82,13 @@ namespace MWMechanics
|
|||
mActiveSpells.mIterating = false;
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster)
|
||||
: mId(cast.mId)
|
||||
, mDisplayName(cast.mSourceName)
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(
|
||||
const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item)
|
||||
: mSourceSpellId(id)
|
||||
, mDisplayName(sourceName)
|
||||
, mCasterActorId(-1)
|
||||
, mItem(cast.mItem)
|
||||
, mType(cast.mType)
|
||||
, mItem(item)
|
||||
, mFlags()
|
||||
, mWorsenings(-1)
|
||||
{
|
||||
if (!caster.isEmpty() && caster.getClass().isActor())
|
||||
|
@ -96,48 +97,52 @@ namespace MWMechanics
|
|||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(
|
||||
const ESM::Spell* spell, const MWWorld::Ptr& actor, bool ignoreResistances)
|
||||
: mId(spell->mId)
|
||||
: mSourceSpellId(spell->mId)
|
||||
, mDisplayName(spell->mName)
|
||||
, mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||
, mType(spell->mData.mType == ESM::Spell::ST_Ability ? ESM::ActiveSpells::Type_Ability
|
||||
: ESM::ActiveSpells::Type_Permanent)
|
||||
, mFlags()
|
||||
, mWorsenings(-1)
|
||||
{
|
||||
assert(spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power);
|
||||
setFlag(ESM::ActiveSpells::Flag_SpellStore);
|
||||
if (spell->mData.mType == ESM::Spell::ST_Ability)
|
||||
setFlag(ESM::ActiveSpells::Flag_AffectsBaseValues);
|
||||
addEffects(mEffects, spell->mEffects, ignoreResistances);
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(
|
||||
const MWWorld::ConstPtr& item, const ESM::Enchantment* enchantment, const MWWorld::Ptr& actor)
|
||||
: mId(item.getCellRef().getRefId())
|
||||
: mSourceSpellId(item.getCellRef().getRefId())
|
||||
, mDisplayName(item.getClass().getName(item))
|
||||
, mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||
, mItem(item.getCellRef().getRefNum())
|
||||
, mType(ESM::ActiveSpells::Type_Enchantment)
|
||||
, mFlags()
|
||||
, mWorsenings(-1)
|
||||
{
|
||||
assert(enchantment->mData.mType == ESM::Enchantment::ConstantEffect);
|
||||
addEffects(mEffects, enchantment->mEffects);
|
||||
setFlag(ESM::ActiveSpells::Flag_Equipment);
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ESM::ActiveSpells::ActiveSpellParams& params)
|
||||
: mId(params.mId)
|
||||
: mActiveSpellId(params.mActiveSpellId)
|
||||
, mSourceSpellId(params.mSourceSpellId)
|
||||
, mEffects(params.mEffects)
|
||||
, mDisplayName(params.mDisplayName)
|
||||
, mCasterActorId(params.mCasterActorId)
|
||||
, mItem(params.mItem)
|
||||
, mType(params.mType)
|
||||
, mFlags(params.mFlags)
|
||||
, mWorsenings(params.mWorsenings)
|
||||
, mNextWorsening({ params.mNextWorsening })
|
||||
{
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpellParams::ActiveSpellParams(const ActiveSpellParams& params, const MWWorld::Ptr& actor)
|
||||
: mId(params.mId)
|
||||
: mSourceSpellId(params.mSourceSpellId)
|
||||
, mDisplayName(params.mDisplayName)
|
||||
, mCasterActorId(actor.getClass().getCreatureStats(actor).getActorId())
|
||||
, mItem(params.mItem)
|
||||
, mType(params.mType)
|
||||
, mFlags(params.mFlags)
|
||||
, mWorsenings(-1)
|
||||
{
|
||||
}
|
||||
|
@ -145,17 +150,23 @@ namespace MWMechanics
|
|||
ESM::ActiveSpells::ActiveSpellParams ActiveSpells::ActiveSpellParams::toEsm() const
|
||||
{
|
||||
ESM::ActiveSpells::ActiveSpellParams params;
|
||||
params.mId = mId;
|
||||
params.mActiveSpellId = mActiveSpellId;
|
||||
params.mSourceSpellId = mSourceSpellId;
|
||||
params.mEffects = mEffects;
|
||||
params.mDisplayName = mDisplayName;
|
||||
params.mCasterActorId = mCasterActorId;
|
||||
params.mItem = mItem;
|
||||
params.mType = mType;
|
||||
params.mFlags = mFlags;
|
||||
params.mWorsenings = mWorsenings;
|
||||
params.mNextWorsening = mNextWorsening.toEsm();
|
||||
return params;
|
||||
}
|
||||
|
||||
void ActiveSpells::ActiveSpellParams::setFlag(ESM::ActiveSpells::Flags flag)
|
||||
{
|
||||
mFlags = static_cast<ESM::ActiveSpells::Flags>(mFlags | flag);
|
||||
}
|
||||
|
||||
void ActiveSpells::ActiveSpellParams::worsen()
|
||||
{
|
||||
++mWorsenings;
|
||||
|
@ -178,21 +189,31 @@ namespace MWMechanics
|
|||
{
|
||||
// Enchantment id is not stored directly. Instead the enchanted item is stored.
|
||||
const auto& store = MWBase::Environment::get().getESMStore();
|
||||
switch (store->find(mId))
|
||||
switch (store->find(mSourceSpellId))
|
||||
{
|
||||
case ESM::REC_ARMO:
|
||||
return store->get<ESM::Armor>().find(mId)->mEnchant;
|
||||
return store->get<ESM::Armor>().find(mSourceSpellId)->mEnchant;
|
||||
case ESM::REC_BOOK:
|
||||
return store->get<ESM::Book>().find(mId)->mEnchant;
|
||||
return store->get<ESM::Book>().find(mSourceSpellId)->mEnchant;
|
||||
case ESM::REC_CLOT:
|
||||
return store->get<ESM::Clothing>().find(mId)->mEnchant;
|
||||
return store->get<ESM::Clothing>().find(mSourceSpellId)->mEnchant;
|
||||
case ESM::REC_WEAP:
|
||||
return store->get<ESM::Weapon>().find(mId)->mEnchant;
|
||||
return store->get<ESM::Weapon>().find(mSourceSpellId)->mEnchant;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const ESM::Spell* ActiveSpells::ActiveSpellParams::getSpell() const
|
||||
{
|
||||
return MWBase::Environment::get().getESMStore()->get<ESM::Spell>().search(getSourceSpellId());
|
||||
}
|
||||
|
||||
bool ActiveSpells::ActiveSpellParams::hasFlag(ESM::ActiveSpells::Flags flags) const
|
||||
{
|
||||
return static_cast<ESM::ActiveSpells::Flags>(mFlags & flags) == flags;
|
||||
}
|
||||
|
||||
void ActiveSpells::update(const MWWorld::Ptr& ptr, float duration)
|
||||
{
|
||||
if (mIterating)
|
||||
|
@ -203,8 +224,7 @@ namespace MWMechanics
|
|||
// Erase no longer active spells and effects
|
||||
for (auto spellIt = mSpells.begin(); spellIt != mSpells.end();)
|
||||
{
|
||||
if (spellIt->mType != ESM::ActiveSpells::Type_Temporary
|
||||
&& spellIt->mType != ESM::ActiveSpells::Type_Consumable)
|
||||
if (!spellIt->hasFlag(ESM::ActiveSpells::Flag_Temporary))
|
||||
{
|
||||
++spellIt;
|
||||
continue;
|
||||
|
@ -244,7 +264,10 @@ namespace MWMechanics
|
|||
{
|
||||
if (spell->mData.mType != ESM::Spell::ST_Spell && spell->mData.mType != ESM::Spell::ST_Power
|
||||
&& !isSpellActive(spell->mId))
|
||||
{
|
||||
mSpells.emplace_back(ActiveSpellParams{ spell, ptr });
|
||||
mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
||||
}
|
||||
}
|
||||
|
||||
bool updateSpellWindow = false;
|
||||
|
@ -270,8 +293,8 @@ namespace MWMechanics
|
|||
if (std::find_if(mSpells.begin(), mSpells.end(),
|
||||
[&](const ActiveSpellParams& params) {
|
||||
return params.mItem == slot->getCellRef().getRefNum()
|
||||
&& params.mType == ESM::ActiveSpells::Type_Enchantment
|
||||
&& params.mId == slot->getCellRef().getRefId();
|
||||
&& params.hasFlag(ESM::ActiveSpells::Flag_Equipment)
|
||||
&& params.mSourceSpellId == slot->getCellRef().getRefId();
|
||||
})
|
||||
!= mSpells.end())
|
||||
continue;
|
||||
|
@ -279,8 +302,8 @@ namespace MWMechanics
|
|||
// invisibility manually
|
||||
purgeEffect(ptr, ESM::MagicEffect::Invisibility);
|
||||
applyPurges(ptr);
|
||||
const ActiveSpellParams& params
|
||||
= mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr });
|
||||
ActiveSpellParams& params = mSpells.emplace_back(ActiveSpellParams{ *slot, enchantment, ptr });
|
||||
params.setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
||||
for (const auto& effect : params.mEffects)
|
||||
MWMechanics::playEffects(
|
||||
ptr, *world->getStore().get<ESM::MagicEffect>().find(effect.mEffectId), playNonLooping);
|
||||
|
@ -350,12 +373,11 @@ namespace MWMechanics
|
|||
continue;
|
||||
|
||||
bool remove = false;
|
||||
if (spellIt->mType == ESM::ActiveSpells::Type_Ability
|
||||
|| spellIt->mType == ESM::ActiveSpells::Type_Permanent)
|
||||
if (spellIt->hasFlag(ESM::ActiveSpells::Flag_SpellStore))
|
||||
{
|
||||
try
|
||||
{
|
||||
remove = !spells.hasSpell(spellIt->mId);
|
||||
remove = !spells.hasSpell(spellIt->mSourceSpellId);
|
||||
}
|
||||
catch (const std::runtime_error& e)
|
||||
{
|
||||
|
@ -363,9 +385,9 @@ namespace MWMechanics
|
|||
Log(Debug::Error) << "Removing active effect: " << e.what();
|
||||
}
|
||||
}
|
||||
else if (spellIt->mType == ESM::ActiveSpells::Type_Enchantment)
|
||||
else if (spellIt->hasFlag(ESM::ActiveSpells::Flag_Equipment))
|
||||
{
|
||||
// Remove constant effect enchantments that have been unequipped
|
||||
// Remove effects tied to equipment that has been unequipped
|
||||
const auto& store = ptr.getClass().getInventoryStore(ptr);
|
||||
remove = true;
|
||||
for (int slotIndex = 0; slotIndex < MWWorld::InventoryStore::Slots; slotIndex++)
|
||||
|
@ -411,11 +433,11 @@ namespace MWMechanics
|
|||
|
||||
void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell)
|
||||
{
|
||||
if (spell.mType != ESM::ActiveSpells::Type_Consumable)
|
||||
if (!spell.hasFlag(ESM::ActiveSpells::Flag_Stackable))
|
||||
{
|
||||
auto found = std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& existing) {
|
||||
return spell.mId == existing.mId && spell.mCasterActorId == existing.mCasterActorId
|
||||
&& spell.mItem == existing.mItem;
|
||||
return spell.mSourceSpellId == existing.mSourceSpellId
|
||||
&& spell.mCasterActorId == existing.mCasterActorId && spell.mItem == existing.mItem;
|
||||
});
|
||||
if (found != mSpells.end())
|
||||
{
|
||||
|
@ -428,6 +450,7 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
mSpells.emplace_back(spell);
|
||||
mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
||||
}
|
||||
|
||||
ActiveSpells::ActiveSpells()
|
||||
|
@ -445,10 +468,19 @@ namespace MWMechanics
|
|||
return mSpells.end();
|
||||
}
|
||||
|
||||
ActiveSpells::TIterator ActiveSpells::getActiveSpellById(const ESM::RefId& id)
|
||||
{
|
||||
for (TIterator it = begin(); it != end(); it++)
|
||||
if (it->getActiveSpellId() == id)
|
||||
return it;
|
||||
return end();
|
||||
}
|
||||
|
||||
bool ActiveSpells::isSpellActive(const ESM::RefId& id) const
|
||||
{
|
||||
return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) { return spell.mId == id; })
|
||||
!= mSpells.end();
|
||||
return std::find_if(mSpells.begin(), mSpells.end(), [&](const auto& spell) {
|
||||
return spell.mSourceSpellId == id;
|
||||
}) != mSpells.end();
|
||||
}
|
||||
|
||||
bool ActiveSpells::isEnchantmentActive(const ESM::RefId& id) const
|
||||
|
@ -557,9 +589,14 @@ namespace MWMechanics
|
|||
return removedCurrentSpell;
|
||||
}
|
||||
|
||||
void ActiveSpells::removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id)
|
||||
void ActiveSpells::removeEffectsBySourceSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id)
|
||||
{
|
||||
purge([=](const ActiveSpellParams& params) { return params.mId == id; }, ptr);
|
||||
purge([=](const ActiveSpellParams& params) { return params.mSourceSpellId == id; }, ptr);
|
||||
}
|
||||
|
||||
void ActiveSpells::removeEffectsByActiveSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id)
|
||||
{
|
||||
purge([=](const ActiveSpellParams& params) { return params.mActiveSpellId == id; }, ptr);
|
||||
}
|
||||
|
||||
void ActiveSpells::purgeEffect(const MWWorld::Ptr& ptr, int effectId, ESM::RefId effectArg)
|
||||
|
@ -604,19 +641,19 @@ namespace MWMechanics
|
|||
void ActiveSpells::readState(const ESM::ActiveSpells& state)
|
||||
{
|
||||
for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mSpells)
|
||||
{
|
||||
mSpells.emplace_back(ActiveSpellParams{ spell });
|
||||
// Generate ID for older saves that didn't have any.
|
||||
if (mSpells.back().getActiveSpellId().empty())
|
||||
mSpells.back().setActiveSpellId(MWBase::Environment::get().getESMStore()->generateId());
|
||||
}
|
||||
for (const ESM::ActiveSpells::ActiveSpellParams& spell : state.mQueue)
|
||||
mQueue.emplace_back(ActiveSpellParams{ spell });
|
||||
}
|
||||
|
||||
void ActiveSpells::unloadActor(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
purge(
|
||||
[](const auto& spell) {
|
||||
return spell.getType() == ESM::ActiveSpells::Type_Consumable
|
||||
|| spell.getType() == ESM::ActiveSpells::Type_Temporary;
|
||||
},
|
||||
ptr);
|
||||
purge([](const auto& spell) { return spell.hasFlag(ESM::ActiveSpells::Flag_Temporary); }, ptr);
|
||||
mQueue.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,12 +33,13 @@ namespace MWMechanics
|
|||
using ActiveEffect = ESM::ActiveEffect;
|
||||
class ActiveSpellParams
|
||||
{
|
||||
ESM::RefId mId;
|
||||
ESM::RefId mActiveSpellId;
|
||||
ESM::RefId mSourceSpellId;
|
||||
std::vector<ActiveEffect> mEffects;
|
||||
std::string mDisplayName;
|
||||
int mCasterActorId;
|
||||
ESM::RefNum mItem;
|
||||
ESM::ActiveSpells::EffectType mType;
|
||||
ESM::ActiveSpells::Flags mFlags;
|
||||
int mWorsenings;
|
||||
MWWorld::TimeStamp mNextWorsening;
|
||||
MWWorld::Ptr mSource;
|
||||
|
@ -57,15 +58,17 @@ namespace MWMechanics
|
|||
friend class ActiveSpells;
|
||||
|
||||
public:
|
||||
ActiveSpellParams(const CastSpell& cast, const MWWorld::Ptr& caster);
|
||||
ActiveSpellParams(
|
||||
const MWWorld::Ptr& caster, const ESM::RefId& id, std::string_view sourceName, ESM::RefNum item);
|
||||
|
||||
const ESM::RefId& getId() const { return mId; }
|
||||
ESM::RefId getActiveSpellId() const { return mActiveSpellId; }
|
||||
void setActiveSpellId(ESM::RefId id) { mActiveSpellId = id; }
|
||||
|
||||
const ESM::RefId& getSourceSpellId() const { return mSourceSpellId; }
|
||||
|
||||
const std::vector<ActiveEffect>& getEffects() const { return mEffects; }
|
||||
std::vector<ActiveEffect>& getEffects() { return mEffects; }
|
||||
|
||||
ESM::ActiveSpells::EffectType getType() const { return mType; }
|
||||
|
||||
int getCasterActorId() const { return mCasterActorId; }
|
||||
|
||||
int getWorsenings() const { return mWorsenings; }
|
||||
|
@ -75,6 +78,10 @@ namespace MWMechanics
|
|||
ESM::RefNum getItem() const { return mItem; }
|
||||
ESM::RefId getEnchantment() const;
|
||||
|
||||
const ESM::Spell* getSpell() const;
|
||||
bool hasFlag(ESM::ActiveSpells::Flags flags) const;
|
||||
void setFlag(ESM::ActiveSpells::Flags flags);
|
||||
|
||||
// Increments worsenings count and sets the next timestamp
|
||||
void worsen();
|
||||
|
||||
|
@ -93,6 +100,8 @@ namespace MWMechanics
|
|||
|
||||
TIterator end() const;
|
||||
|
||||
TIterator getActiveSpellById(const ESM::RefId& id);
|
||||
|
||||
void update(const MWWorld::Ptr& ptr, float duration);
|
||||
|
||||
private:
|
||||
|
@ -132,7 +141,9 @@ namespace MWMechanics
|
|||
void addSpell(const ESM::Spell* spell, const MWWorld::Ptr& actor);
|
||||
|
||||
/// Removes the active effects from this spell/potion/.. with \a id
|
||||
void removeEffects(const MWWorld::Ptr& ptr, const ESM::RefId& id);
|
||||
void removeEffectsBySourceSpellId(const MWWorld::Ptr& ptr, const ESM::RefId& id);
|
||||
/// Removes the active effects of a specific active spell
|
||||
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 = {});
|
||||
|
|
|
@ -1228,11 +1228,11 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void Actors::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell) const
|
||||
void Actors::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell) const
|
||||
{
|
||||
const auto iter = mIndex.find(ptr.mRef);
|
||||
if (iter != mIndex.end())
|
||||
iter->second->getCharacterController().castSpell(spellId, manualSpell);
|
||||
iter->second->getCharacterController().castSpell(spellId, scriptedSpell);
|
||||
}
|
||||
|
||||
bool Actors::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) const
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace MWMechanics
|
|||
|
||||
void resurrect(const MWWorld::Ptr& ptr) const;
|
||||
|
||||
void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) const;
|
||||
void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell = false) const;
|
||||
|
||||
void updateActor(const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) const;
|
||||
///< Updates an actor with a new Ptr
|
||||
|
|
|
@ -25,11 +25,11 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
MWMechanics::AiCast::AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell)
|
||||
MWMechanics::AiCast::AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool scriptedSpell)
|
||||
: mTargetId(targetId)
|
||||
, mSpellId(spellId)
|
||||
, mCasting(false)
|
||||
, mManual(manualSpell)
|
||||
, mScripted(scriptedSpell)
|
||||
, mDistance(getInitialDistance(spellId))
|
||||
{
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
|
|||
if (target.isEmpty())
|
||||
return true;
|
||||
|
||||
if (!mManual
|
||||
if (!mScripted
|
||||
&& !pathTo(actor, target.getRefData().getPosition().asVec3(), duration,
|
||||
characterController.getSupportedMovementDirections(), mDistance))
|
||||
{
|
||||
|
@ -85,7 +85,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
|
|||
|
||||
if (!mCasting)
|
||||
{
|
||||
MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mManual);
|
||||
MWBase::Environment::get().getMechanicsManager()->castSpell(actor, mSpellId, mScripted);
|
||||
mCasting = true;
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace MWMechanics
|
|||
class AiCast final : public TypedAiPackage<AiCast>
|
||||
{
|
||||
public:
|
||||
AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool manualSpell = false);
|
||||
AiCast(const ESM::RefId& targetId, const ESM::RefId& spellId, bool scriptedSpell = false);
|
||||
|
||||
bool execute(const MWWorld::Ptr& actor, CharacterController& characterController, AiState& state,
|
||||
float duration) override;
|
||||
|
@ -37,7 +37,7 @@ namespace MWMechanics
|
|||
const ESM::RefId mTargetId;
|
||||
const ESM::RefId mSpellId;
|
||||
bool mCasting;
|
||||
const bool mManual;
|
||||
const bool mScripted;
|
||||
const float mDistance;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -275,7 +275,7 @@ namespace MWMechanics
|
|||
if (!spellId.empty())
|
||||
{
|
||||
const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get<ESM::Spell>().find(spellId);
|
||||
if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mRange != ESM::RT_Target)
|
||||
if (spell->mEffects.mList.empty() || spell->mEffects.mList[0].mData.mRange != ESM::RT_Target)
|
||||
canShout = false;
|
||||
}
|
||||
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat, canShout);
|
||||
|
|
|
@ -355,14 +355,14 @@ namespace MWMechanics
|
|||
{
|
||||
const ESM::Spell* spell
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Spell>().find(selectedSpellId);
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin();
|
||||
for (std::vector<ESM::IndexedENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin();
|
||||
effectIt != spell->mEffects.mList.end(); ++effectIt)
|
||||
{
|
||||
if (effectIt->mRange == ESM::RT_Target)
|
||||
if (effectIt->mData.mRange == ESM::RT_Target)
|
||||
{
|
||||
const ESM::MagicEffect* effect
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(
|
||||
effectIt->mEffectID);
|
||||
effectIt->mData.mEffectID);
|
||||
dist = effect->mData.mSpeed;
|
||||
break;
|
||||
}
|
||||
|
@ -375,14 +375,14 @@ namespace MWMechanics
|
|||
{
|
||||
const ESM::Enchantment* ench
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Enchantment>().find(enchId);
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = ench->mEffects.mList.begin();
|
||||
for (std::vector<ESM::IndexedENAMstruct>::const_iterator effectIt = ench->mEffects.mList.begin();
|
||||
effectIt != ench->mEffects.mList.end(); ++effectIt)
|
||||
{
|
||||
if (effectIt->mRange == ESM::RT_Target)
|
||||
if (effectIt->mData.mRange == ESM::RT_Target)
|
||||
{
|
||||
const ESM::MagicEffect* effect
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(
|
||||
effectIt->mEffectID);
|
||||
effectIt->mData.mEffectID);
|
||||
dist = effect->mData.mSpeed;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -262,13 +262,13 @@ const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co
|
|||
|
||||
for (size_t i = 0; i < iter->mEffects.mList.size(); ++i)
|
||||
{
|
||||
const ESM::ENAMstruct& first = iter->mEffects.mList[i];
|
||||
const ESM::IndexedENAMstruct& first = iter->mEffects.mList[i];
|
||||
const ESM::ENAMstruct& second = mEffects[i];
|
||||
|
||||
if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange
|
||||
|| first.mSkill != second.mSkill || first.mAttribute != second.mAttribute
|
||||
|| first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax
|
||||
|| first.mDuration != second.mDuration)
|
||||
if (first.mData.mEffectID != second.mEffectID || first.mData.mArea != second.mArea
|
||||
|| first.mData.mRange != second.mRange || first.mData.mSkill != second.mSkill
|
||||
|| first.mData.mAttribute != second.mAttribute || first.mData.mMagnMin != second.mMagnMin
|
||||
|| first.mData.mMagnMax != second.mMagnMax || first.mData.mDuration != second.mDuration)
|
||||
{
|
||||
mismatch = true;
|
||||
break;
|
||||
|
@ -324,7 +324,7 @@ void MWMechanics::Alchemy::addPotion(const std::string& name)
|
|||
newRecord.mModel = "m\\misc_potion_" + std::string(meshes[index]) + "_01.nif";
|
||||
newRecord.mIcon = "m\\tx_potion_" + std::string(meshes[index]) + "_01.dds";
|
||||
|
||||
newRecord.mEffects.mList = mEffects;
|
||||
newRecord.mEffects.populate(mEffects);
|
||||
|
||||
const ESM::Potion* record = getRecord(newRecord);
|
||||
if (!record)
|
||||
|
|
|
@ -221,7 +221,7 @@ namespace MWMechanics
|
|||
for (const auto& spellEffect : spell->mEffects.mList)
|
||||
{
|
||||
const ESM::MagicEffect* magicEffect
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(spellEffect.mEffectID);
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(spellEffect.mData.mEffectID);
|
||||
static const int iAutoSpellAttSkillMin = MWBase::Environment::get()
|
||||
.getESMStore()
|
||||
->get<ESM::GameSetting>()
|
||||
|
@ -230,7 +230,7 @@ namespace MWMechanics
|
|||
|
||||
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill))
|
||||
{
|
||||
ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mSkill);
|
||||
ESM::RefId skill = ESM::Skill::indexToRefId(spellEffect.mData.mSkill);
|
||||
auto found = actorSkills.find(skill);
|
||||
if (found == actorSkills.end() || found->second.getBase() < iAutoSpellAttSkillMin)
|
||||
return false;
|
||||
|
@ -238,7 +238,7 @@ namespace MWMechanics
|
|||
|
||||
if ((magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute))
|
||||
{
|
||||
ESM::RefId attribute = ESM::Attribute::indexToRefId(spellEffect.mAttribute);
|
||||
ESM::RefId attribute = ESM::Attribute::indexToRefId(spellEffect.mData.mAttribute);
|
||||
auto found = actorAttributes.find(attribute);
|
||||
if (found == actorAttributes.end() || found->second.getBase() < iAutoSpellAttSkillMin)
|
||||
return false;
|
||||
|
@ -253,22 +253,22 @@ namespace MWMechanics
|
|||
{
|
||||
// Morrowind for some reason uses a formula slightly different from magicka cost calculation
|
||||
float minChance = std::numeric_limits<float>::max();
|
||||
for (const ESM::ENAMstruct& effect : spell->mEffects.mList)
|
||||
for (const ESM::IndexedENAMstruct& effect : spell->mEffects.mList)
|
||||
{
|
||||
const ESM::MagicEffect* magicEffect
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effect.mData.mEffectID);
|
||||
|
||||
int minMagn = 1;
|
||||
int maxMagn = 1;
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||
{
|
||||
minMagn = effect.mMagnMin;
|
||||
maxMagn = effect.mMagnMax;
|
||||
minMagn = effect.mData.mMagnMin;
|
||||
maxMagn = effect.mData.mMagnMax;
|
||||
}
|
||||
|
||||
int duration = 0;
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
||||
duration = effect.mDuration;
|
||||
duration = effect.mData.mDuration;
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce))
|
||||
duration = std::max(1, duration);
|
||||
|
||||
|
@ -281,10 +281,10 @@ namespace MWMechanics
|
|||
float x = 0.5 * (std::max(1, minMagn) + std::max(1, maxMagn));
|
||||
x *= 0.1 * magicEffect->mData.mBaseCost;
|
||||
x *= 1 + duration;
|
||||
x += 0.05 * std::max(1, effect.mArea) * magicEffect->mData.mBaseCost;
|
||||
x += 0.05 * std::max(1, effect.mData.mArea) * magicEffect->mData.mBaseCost;
|
||||
x *= fEffectCostMult;
|
||||
|
||||
if (effect.mRange == ESM::RT_Target)
|
||||
if (effect.mData.mRange == ESM::RT_Target)
|
||||
x *= 1.5f;
|
||||
|
||||
float s = 0.f;
|
||||
|
|
|
@ -1155,8 +1155,8 @@ namespace MWMechanics
|
|||
else if (groupname == "spellcast" && action == mAttackType + " release")
|
||||
{
|
||||
if (mCanCast)
|
||||
MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingManualSpell);
|
||||
mCastingManualSpell = false;
|
||||
MWBase::Environment::get().getWorld()->castSpell(mPtr, mCastingScriptedSpell);
|
||||
mCastingScriptedSpell = false;
|
||||
mCanCast = false;
|
||||
}
|
||||
else if (groupname == "containeropen" && action == "loot")
|
||||
|
@ -1526,9 +1526,9 @@ namespace MWMechanics
|
|||
bool isMagicItem = false;
|
||||
|
||||
// Play hand VFX and allow castSpell use (assuming an animation is going to be played) if
|
||||
// spellcasting is successful. Manual spellcasting bypasses restrictions.
|
||||
// spellcasting is successful. Scripted spellcasting bypasses restrictions.
|
||||
MWWorld::SpellCastState spellCastResult = MWWorld::SpellCastState::Success;
|
||||
if (!mCastingManualSpell)
|
||||
if (!mCastingScriptedSpell)
|
||||
spellCastResult = world->startSpellCast(mPtr);
|
||||
mCanCast = spellCastResult == MWWorld::SpellCastState::Success;
|
||||
|
||||
|
@ -1558,9 +1558,9 @@ namespace MWMechanics
|
|||
else if (!spellid.empty() && spellCastResult != MWWorld::SpellCastState::PowerAlreadyUsed)
|
||||
{
|
||||
world->breakInvisibility(mPtr);
|
||||
MWMechanics::CastSpell cast(mPtr, {}, false, mCastingManualSpell);
|
||||
MWMechanics::CastSpell cast(mPtr, {}, false, mCastingScriptedSpell);
|
||||
|
||||
const std::vector<ESM::ENAMstruct>* effects{ nullptr };
|
||||
const std::vector<ESM::IndexedENAMstruct>* effects{ nullptr };
|
||||
const MWWorld::ESMStore& store = world->getStore();
|
||||
if (isMagicItem)
|
||||
{
|
||||
|
@ -1579,7 +1579,7 @@ namespace MWMechanics
|
|||
if (mCanCast)
|
||||
{
|
||||
const ESM::MagicEffect* effect = store.get<ESM::MagicEffect>().find(
|
||||
effects->back().mEffectID); // use last effect of list for color of VFX_Hands
|
||||
effects->back().mData.mEffectID); // use last effect of list for color of VFX_Hands
|
||||
|
||||
const ESM::Static* castStatic
|
||||
= world->getStore().get<ESM::Static>().find(ESM::RefId::stringRefId("VFX_Hands"));
|
||||
|
@ -1593,7 +1593,7 @@ namespace MWMechanics
|
|||
"", false, "Bip01 R Hand", effect->mParticle);
|
||||
}
|
||||
// first effect used for casting animation
|
||||
const ESM::ENAMstruct& firstEffect = effects->front();
|
||||
const ESM::ENAMstruct& firstEffect = effects->front().mData;
|
||||
|
||||
std::string startKey;
|
||||
std::string stopKey;
|
||||
|
@ -1602,9 +1602,9 @@ namespace MWMechanics
|
|||
startKey = "start";
|
||||
stopKey = "stop";
|
||||
if (mCanCast)
|
||||
world->castSpell(
|
||||
mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately
|
||||
mCastingManualSpell = false;
|
||||
world->castSpell(mPtr,
|
||||
mCastingScriptedSpell); // No "release" text key to use, so cast immediately
|
||||
mCastingScriptedSpell = false;
|
||||
mCanCast = false;
|
||||
}
|
||||
else
|
||||
|
@ -2735,7 +2735,7 @@ namespace MWMechanics
|
|||
// Make sure we canceled the current attack or spellcasting,
|
||||
// because we disabled attack animations anyway.
|
||||
mCanCast = false;
|
||||
mCastingManualSpell = false;
|
||||
mCastingScriptedSpell = false;
|
||||
setAttackingOrSpell(false);
|
||||
if (mUpperBodyState != UpperBodyState::None)
|
||||
mUpperBodyState = UpperBodyState::WeaponEquipped;
|
||||
|
@ -2887,7 +2887,7 @@ namespace MWMechanics
|
|||
|
||||
bool CharacterController::isCastingSpell() const
|
||||
{
|
||||
return mCastingManualSpell || mUpperBodyState == UpperBodyState::Casting;
|
||||
return mCastingScriptedSpell || mUpperBodyState == UpperBodyState::Casting;
|
||||
}
|
||||
|
||||
bool CharacterController::isReadyToBlock() const
|
||||
|
@ -2941,10 +2941,10 @@ namespace MWMechanics
|
|||
mPtr.getClass().getCreatureStats(mPtr).setAttackingOrSpell(attackingOrSpell);
|
||||
}
|
||||
|
||||
void CharacterController::castSpell(const ESM::RefId& spellId, bool manualSpell)
|
||||
void CharacterController::castSpell(const ESM::RefId& spellId, bool scriptedSpell)
|
||||
{
|
||||
setAttackingOrSpell(true);
|
||||
mCastingManualSpell = manualSpell;
|
||||
mCastingScriptedSpell = scriptedSpell;
|
||||
ActionSpell action = ActionSpell(spellId);
|
||||
action.prepare(mPtr);
|
||||
}
|
||||
|
|
|
@ -192,7 +192,7 @@ namespace MWMechanics
|
|||
|
||||
bool mCanCast{ false };
|
||||
|
||||
bool mCastingManualSpell{ false };
|
||||
bool mCastingScriptedSpell{ false };
|
||||
|
||||
bool mIsMovingBackward{ false };
|
||||
osg::Vec2f mSmoothedSpeed;
|
||||
|
@ -312,7 +312,7 @@ namespace MWMechanics
|
|||
bool isAttackingOrSpell() const;
|
||||
|
||||
void setVisibility(float visibility) const;
|
||||
void castSpell(const ESM::RefId& spellId, bool manualSpell = false);
|
||||
void castSpell(const ESM::RefId& spellId, bool scriptedSpell = false);
|
||||
void setAIAttackType(std::string_view attackType);
|
||||
static std::string_view getRandomAttackType();
|
||||
|
||||
|
|
|
@ -198,13 +198,13 @@ namespace MWMechanics
|
|||
|
||||
float enchantmentCost = 0.f;
|
||||
float cost = 0.f;
|
||||
for (const ESM::ENAMstruct& effect : mEffectList.mList)
|
||||
for (const ESM::IndexedENAMstruct& effect : mEffectList.mList)
|
||||
{
|
||||
float baseCost = (store.get<ESM::MagicEffect>().find(effect.mEffectID))->mData.mBaseCost;
|
||||
int magMin = std::max(1, effect.mMagnMin);
|
||||
int magMax = std::max(1, effect.mMagnMax);
|
||||
int area = std::max(1, effect.mArea);
|
||||
float duration = static_cast<float>(effect.mDuration);
|
||||
float baseCost = (store.get<ESM::MagicEffect>().find(effect.mData.mEffectID))->mData.mBaseCost;
|
||||
int magMin = std::max(1, effect.mData.mMagnMin);
|
||||
int magMax = std::max(1, effect.mData.mMagnMax);
|
||||
int area = std::max(1, effect.mData.mArea);
|
||||
float duration = static_cast<float>(effect.mData.mDuration);
|
||||
if (mCastStyle == ESM::Enchantment::ConstantEffect)
|
||||
duration = fEnchantmentConstantDurationMult;
|
||||
|
||||
|
@ -212,7 +212,7 @@ namespace MWMechanics
|
|||
|
||||
cost = std::max(1.f, cost);
|
||||
|
||||
if (effect.mRange == ESM::RT_Target)
|
||||
if (effect.mData.mRange == ESM::RT_Target)
|
||||
cost *= 1.5f;
|
||||
|
||||
enchantmentCost += precise ? cost : std::floor(cost);
|
||||
|
@ -244,13 +244,7 @@ namespace MWMechanics
|
|||
|
||||
for (int i = 0; i < static_cast<int>(iter->mEffects.mList.size()); ++i)
|
||||
{
|
||||
const ESM::ENAMstruct& first = iter->mEffects.mList[i];
|
||||
const ESM::ENAMstruct& second = toFind.mEffects.mList[i];
|
||||
|
||||
if (first.mEffectID != second.mEffectID || first.mArea != second.mArea || first.mRange != second.mRange
|
||||
|| first.mSkill != second.mSkill || first.mAttribute != second.mAttribute
|
||||
|| first.mMagnMin != second.mMagnMin || first.mMagnMax != second.mMagnMax
|
||||
|| first.mDuration != second.mDuration)
|
||||
if (iter->mEffects.mList[i] != toFind.mEffects.mList[i])
|
||||
{
|
||||
mismatch = true;
|
||||
break;
|
||||
|
|
|
@ -261,10 +261,10 @@ namespace MWMechanics
|
|||
mObjects.addObject(ptr);
|
||||
}
|
||||
|
||||
void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell)
|
||||
void MechanicsManager::castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell)
|
||||
{
|
||||
if (ptr.getClass().isActor())
|
||||
mActors.castSpell(ptr, spellId, manualSpell);
|
||||
mActors.castSpell(ptr, spellId, scriptedSpell);
|
||||
}
|
||||
|
||||
void MechanicsManager::remove(const MWWorld::Ptr& ptr, bool keepActive)
|
||||
|
@ -1978,11 +1978,7 @@ namespace MWMechanics
|
|||
|
||||
// Transforming removes all temporary effects
|
||||
actor.getClass().getCreatureStats(actor).getActiveSpells().purge(
|
||||
[](const auto& params) {
|
||||
return params.getType() == ESM::ActiveSpells::Type_Consumable
|
||||
|| params.getType() == ESM::ActiveSpells::Type_Temporary;
|
||||
},
|
||||
actor);
|
||||
[](const auto& params) { return params.hasFlag(ESM::ActiveSpells::Flag_Temporary); }, actor);
|
||||
mActors.updateActor(actor, 0.f);
|
||||
|
||||
if (werewolf)
|
||||
|
|
|
@ -202,7 +202,7 @@ namespace MWMechanics
|
|||
/// Is \a ptr casting spell or using weapon now?
|
||||
bool isAttackingOrSpell(const MWWorld::Ptr& ptr) const override;
|
||||
|
||||
void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool manualSpell = false) override;
|
||||
void castSpell(const MWWorld::Ptr& ptr, const ESM::RefId& spellId, bool scriptedSpell = false) override;
|
||||
|
||||
void processChangedSettings(const Settings::CategorySettingVector& settings) override;
|
||||
|
||||
|
|
|
@ -29,11 +29,11 @@
|
|||
namespace MWMechanics
|
||||
{
|
||||
CastSpell::CastSpell(
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool manualSpell)
|
||||
const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile, const bool scriptedSpell)
|
||||
: mCaster(caster)
|
||||
, mTarget(target)
|
||||
, mFromProjectile(fromProjectile)
|
||||
, mManualSpell(manualSpell)
|
||||
, mScriptedSpell(scriptedSpell)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -41,21 +41,21 @@ namespace MWMechanics
|
|||
const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct>> toApply;
|
||||
std::map<MWWorld::Ptr, std::vector<ESM::IndexedENAMstruct>> toApply;
|
||||
int index = -1;
|
||||
for (const ESM::ENAMstruct& effectInfo : effects.mList)
|
||||
for (const ESM::IndexedENAMstruct& effectInfo : effects.mList)
|
||||
{
|
||||
++index;
|
||||
const ESM::MagicEffect* effect = world->getStore().get<ESM::MagicEffect>().find(effectInfo.mEffectID);
|
||||
const ESM::MagicEffect* effect = world->getStore().get<ESM::MagicEffect>().find(effectInfo.mData.mEffectID);
|
||||
|
||||
if (effectInfo.mRange != rangeType
|
||||
|| (effectInfo.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
|
||||
if (effectInfo.mData.mRange != rangeType
|
||||
|| (effectInfo.mData.mArea <= 0 && !ignore.isEmpty() && ignore.getClass().isActor()))
|
||||
continue; // Not right range type, or not area effect and hit an actor
|
||||
|
||||
if (mFromProjectile && effectInfo.mArea <= 0)
|
||||
if (mFromProjectile && effectInfo.mData.mArea <= 0)
|
||||
continue; // Don't play explosion for projectiles with 0-area effects
|
||||
|
||||
if (!mFromProjectile && effectInfo.mRange == ESM::RT_Touch && !ignore.isEmpty()
|
||||
if (!mFromProjectile && effectInfo.mData.mRange == ESM::RT_Touch && !ignore.isEmpty()
|
||||
&& !ignore.getClass().isActor() && !ignore.getClass().hasToolTip(ignore)
|
||||
&& (mCaster.isEmpty() || mCaster.getClass().isActor()))
|
||||
continue; // Don't play explosion for touch spells on non-activatable objects except when spell is from
|
||||
|
@ -70,16 +70,16 @@ namespace MWMechanics
|
|||
|
||||
const std::string& texture = effect->mParticle;
|
||||
|
||||
if (effectInfo.mArea <= 0)
|
||||
if (effectInfo.mData.mArea <= 0)
|
||||
{
|
||||
if (effectInfo.mRange == ESM::RT_Target)
|
||||
if (effectInfo.mData.mRange == ESM::RT_Target)
|
||||
world->spawnEffect(
|
||||
Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, 1.0f);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition,
|
||||
static_cast<float>(effectInfo.mArea * 2));
|
||||
static_cast<float>(effectInfo.mData.mArea * 2));
|
||||
|
||||
// Play explosion sound (make sure to use NoTrack, since we will delete the projectile now)
|
||||
{
|
||||
|
@ -95,7 +95,7 @@ namespace MWMechanics
|
|||
std::vector<MWWorld::Ptr> objects;
|
||||
static const int unitsPerFoot = ceil(Constants::UnitsPerFoot);
|
||||
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
|
||||
mHitPosition, static_cast<float>(effectInfo.mArea * unitsPerFoot), objects);
|
||||
mHitPosition, static_cast<float>(effectInfo.mData.mArea * unitsPerFoot), objects);
|
||||
for (const MWWorld::Ptr& affected : objects)
|
||||
{
|
||||
// Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing
|
||||
|
@ -104,13 +104,6 @@ namespace MWMechanics
|
|||
continue;
|
||||
|
||||
auto& list = toApply[affected];
|
||||
while (list.size() < static_cast<std::size_t>(index))
|
||||
{
|
||||
// Insert dummy effects to preserve indices
|
||||
auto& dummy = list.emplace_back(effectInfo);
|
||||
dummy.mRange = ESM::RT_Self;
|
||||
assert(dummy.mRange != rangeType);
|
||||
}
|
||||
list.push_back(effectInfo);
|
||||
}
|
||||
}
|
||||
|
@ -151,45 +144,34 @@ namespace MWMechanics
|
|||
void CastSpell::inflict(
|
||||
const MWWorld::Ptr& target, const ESM::EffectList& effects, ESM::RangeType range, bool exploded) const
|
||||
{
|
||||
bool targetIsDeadActor = false;
|
||||
const bool targetIsActor = !target.isEmpty() && target.getClass().isActor();
|
||||
if (targetIsActor)
|
||||
{
|
||||
// Early-out for characters that have departed.
|
||||
const auto& stats = target.getClass().getCreatureStats(target);
|
||||
if (stats.isDead() && stats.isDeathAnimationFinished())
|
||||
return;
|
||||
targetIsDeadActor = true;
|
||||
}
|
||||
|
||||
// If none of the effects need to apply, we can early-out
|
||||
bool found = false;
|
||||
bool containsRecastable = false;
|
||||
std::vector<const ESM::MagicEffect*> magicEffects;
|
||||
magicEffects.reserve(effects.mList.size());
|
||||
const auto& store = MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>();
|
||||
for (const ESM::ENAMstruct& effect : effects.mList)
|
||||
for (const ESM::IndexedENAMstruct& effect : effects.mList)
|
||||
{
|
||||
if (effect.mRange == range)
|
||||
if (effect.mData.mRange == range)
|
||||
{
|
||||
found = true;
|
||||
const ESM::MagicEffect* magicEffect = store.find(effect.mEffectID);
|
||||
// caster needs to be an actor for linked effects (e.g. Absorb)
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
|
||||
&& (mCaster.isEmpty() || !mCaster.getClass().isActor()))
|
||||
{
|
||||
magicEffects.push_back(nullptr);
|
||||
continue;
|
||||
}
|
||||
const ESM::MagicEffect* magicEffect = store.find(effect.mData.mEffectID);
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NonRecastable))
|
||||
containsRecastable = true;
|
||||
magicEffects.push_back(magicEffect);
|
||||
}
|
||||
else
|
||||
magicEffects.push_back(nullptr);
|
||||
}
|
||||
if (!found)
|
||||
return;
|
||||
|
||||
ActiveSpells::ActiveSpellParams params(*this, mCaster);
|
||||
ActiveSpells::ActiveSpellParams params(mCaster, mId, mSourceName, mItem);
|
||||
params.setFlag(mFlags);
|
||||
bool castByPlayer = (!mCaster.isEmpty() && mCaster == getPlayer());
|
||||
|
||||
const ActiveSpells* targetSpells = nullptr;
|
||||
|
@ -204,31 +186,32 @@ namespace MWMechanics
|
|||
return;
|
||||
}
|
||||
|
||||
for (size_t currentEffectIndex = 0; !target.isEmpty() && currentEffectIndex < effects.mList.size();
|
||||
++currentEffectIndex)
|
||||
for (auto& enam : effects.mList)
|
||||
{
|
||||
const ESM::ENAMstruct& enam = effects.mList[currentEffectIndex];
|
||||
if (enam.mRange != range)
|
||||
if (enam.mData.mRange != range)
|
||||
continue;
|
||||
|
||||
const ESM::MagicEffect* magicEffect = magicEffects[currentEffectIndex];
|
||||
const ESM::MagicEffect* magicEffect = store.find(enam.mData.mEffectID);
|
||||
if (!magicEffect)
|
||||
continue;
|
||||
// caster needs to be an actor for linked effects (e.g. Absorb)
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::CasterLinked
|
||||
&& (mCaster.isEmpty() || !mCaster.getClass().isActor()))
|
||||
continue;
|
||||
|
||||
ActiveSpells::ActiveEffect effect;
|
||||
effect.mEffectId = enam.mEffectID;
|
||||
effect.mArg = MWMechanics::EffectKey(enam).mArg;
|
||||
effect.mEffectId = enam.mData.mEffectID;
|
||||
effect.mArg = MWMechanics::EffectKey(enam.mData).mArg;
|
||||
effect.mMagnitude = 0.f;
|
||||
effect.mMinMagnitude = enam.mMagnMin;
|
||||
effect.mMaxMagnitude = enam.mMagnMax;
|
||||
effect.mMinMagnitude = enam.mData.mMagnMin;
|
||||
effect.mMaxMagnitude = enam.mData.mMagnMax;
|
||||
effect.mTimeLeft = 0.f;
|
||||
effect.mEffectIndex = static_cast<int>(currentEffectIndex);
|
||||
effect.mEffectIndex = enam.mIndex;
|
||||
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
||||
if (mManualSpell)
|
||||
if (mScriptedSpell)
|
||||
effect.mFlags |= ESM::ActiveEffect::Flag_Ignore_Reflect;
|
||||
|
||||
bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration);
|
||||
effect.mDuration = hasDuration ? static_cast<float>(enam.mDuration) : 1.f;
|
||||
effect.mDuration = hasDuration ? static_cast<float>(enam.mData.mDuration) : 1.f;
|
||||
|
||||
bool appliedOnce = magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce;
|
||||
if (!appliedOnce)
|
||||
|
@ -240,8 +223,8 @@ namespace MWMechanics
|
|||
params.getEffects().emplace_back(effect);
|
||||
|
||||
bool effectAffectsHealth = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful
|
||||
|| enam.mEffectID == ESM::MagicEffect::RestoreHealth;
|
||||
if (castByPlayer && target != mCaster && targetIsActor && effectAffectsHealth)
|
||||
|| enam.mData.mEffectID == ESM::MagicEffect::RestoreHealth;
|
||||
if (castByPlayer && target != mCaster && targetIsActor && !targetIsDeadActor && effectAffectsHealth)
|
||||
{
|
||||
// If player is attempting to cast a harmful spell on or is healing a living target, show the target's
|
||||
// HP bar.
|
||||
|
@ -262,7 +245,10 @@ namespace MWMechanics
|
|||
if (!params.getEffects().empty())
|
||||
{
|
||||
if (targetIsActor)
|
||||
{
|
||||
if (!targetIsDeadActor)
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().addSpell(params);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Apply effects instantly. We can ignore effect deletion since the entire params object gets
|
||||
|
@ -336,7 +322,7 @@ namespace MWMechanics
|
|||
ESM::RefId school = ESM::Skill::Alteration;
|
||||
if (!enchantment->mEffects.mList.empty())
|
||||
{
|
||||
short effectId = enchantment->mEffects.mList.front().mEffectID;
|
||||
short effectId = enchantment->mEffects.mList.front().mData.mEffectID;
|
||||
const ESM::MagicEffect* magicEffect = store->get<ESM::MagicEffect>().find(effectId);
|
||||
school = magicEffect->mData.mSchool;
|
||||
}
|
||||
|
@ -387,7 +373,8 @@ namespace MWMechanics
|
|||
{
|
||||
mSourceName = potion->mName;
|
||||
mId = potion->mId;
|
||||
mType = ESM::ActiveSpells::Type_Consumable;
|
||||
mFlags = static_cast<ESM::ActiveSpells::Flags>(
|
||||
ESM::ActiveSpells::Flag_Temporary | ESM::ActiveSpells::Flag_Stackable);
|
||||
|
||||
inflict(mCaster, potion->mEffects, ESM::RT_Self);
|
||||
|
||||
|
@ -403,7 +390,7 @@ namespace MWMechanics
|
|||
|
||||
bool godmode = mCaster == MWMechanics::getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
|
||||
|
||||
if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mManualSpell)
|
||||
if (mCaster.getClass().isActor() && !mAlwaysSucceed && !mScriptedSpell)
|
||||
{
|
||||
school = getSpellSchool(spell, mCaster);
|
||||
|
||||
|
@ -438,7 +425,7 @@ namespace MWMechanics
|
|||
stats.getSpells().usePower(spell);
|
||||
}
|
||||
|
||||
if (!mManualSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
|
||||
if (!mScriptedSpell && mCaster == getPlayer() && spellIncreasesSkill(spell))
|
||||
mCaster.getClass().skillUsageSucceeded(mCaster, school, ESM::Skill::Spellcast_Success);
|
||||
|
||||
// A non-actor doesn't play its spell cast effects from a character controller, so play them here
|
||||
|
@ -458,62 +445,27 @@ namespace MWMechanics
|
|||
bool CastSpell::cast(const ESM::Ingredient* ingredient)
|
||||
{
|
||||
mId = ingredient->mId;
|
||||
mType = ESM::ActiveSpells::Type_Consumable;
|
||||
mFlags = static_cast<ESM::ActiveSpells::Flags>(
|
||||
ESM::ActiveSpells::Flag_Temporary | ESM::ActiveSpells::Flag_Stackable);
|
||||
mSourceName = ingredient->mName;
|
||||
|
||||
ESM::ENAMstruct effect;
|
||||
effect.mEffectID = ingredient->mData.mEffectID[0];
|
||||
effect.mSkill = ingredient->mData.mSkills[0];
|
||||
effect.mAttribute = ingredient->mData.mAttributes[0];
|
||||
effect.mRange = ESM::RT_Self;
|
||||
effect.mArea = 0;
|
||||
auto effect = rollIngredientEffect(mCaster, ingredient, mCaster != getPlayer());
|
||||
|
||||
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
|
||||
const auto magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||
const MWMechanics::CreatureStats& creatureStats = mCaster.getClass().getCreatureStats(mCaster);
|
||||
|
||||
float x = (mCaster.getClass().getSkill(mCaster, ESM::Skill::Alchemy)
|
||||
+ 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified()
|
||||
+ 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified())
|
||||
* creatureStats.getFatigueTerm();
|
||||
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
int roll = Misc::Rng::roll0to99(prng);
|
||||
if (roll > x)
|
||||
if (effect)
|
||||
inflict(mCaster, *effect, ESM::RT_Self);
|
||||
else
|
||||
{
|
||||
// "X has no effect on you"
|
||||
std::string message = store.get<ESM::GameSetting>().find("sNotifyMessage50")->mValue.getString();
|
||||
std::string message = MWBase::Environment::get()
|
||||
.getESMStore()
|
||||
->get<ESM::GameSetting>()
|
||||
.find("sNotifyMessage50")
|
||||
->mValue.getString();
|
||||
message = Misc::StringUtils::format(message, ingredient->mName);
|
||||
MWBase::Environment::get().getWindowManager()->messageBox(message);
|
||||
return false;
|
||||
}
|
||||
|
||||
float magnitude = 0;
|
||||
float y = roll / std::min(x, 100.f);
|
||||
y *= 0.25f * x;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
effect.mDuration = 1;
|
||||
else
|
||||
effect.mDuration = static_cast<int>(y);
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||
{
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
||||
magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost));
|
||||
else
|
||||
magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost));
|
||||
magnitude = std::max(1.f, magnitude);
|
||||
}
|
||||
else
|
||||
magnitude = 1;
|
||||
|
||||
effect.mMagnMax = static_cast<int>(magnitude);
|
||||
effect.mMagnMin = static_cast<int>(magnitude);
|
||||
|
||||
ESM::EffectList effects;
|
||||
effects.mList.push_back(effect);
|
||||
|
||||
inflict(mCaster, effects, ESM::RT_Self);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -527,14 +479,14 @@ namespace MWMechanics
|
|||
playSpellCastingEffects(spell->mEffects.mList);
|
||||
}
|
||||
|
||||
void CastSpell::playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects) const
|
||||
void CastSpell::playSpellCastingEffects(const std::vector<ESM::IndexedENAMstruct>& effects) const
|
||||
{
|
||||
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
|
||||
std::vector<std::string> addedEffects;
|
||||
|
||||
for (const ESM::ENAMstruct& effectData : effects)
|
||||
for (const ESM::IndexedENAMstruct& effectData : effects)
|
||||
{
|
||||
const auto effect = store.get<ESM::MagicEffect>().find(effectData.mEffectID);
|
||||
const auto effect = store.get<ESM::MagicEffect>().find(effectData.mData.mEffectID);
|
||||
|
||||
const ESM::Static* castStatic;
|
||||
|
||||
|
@ -587,7 +539,7 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
if (animation && !mCaster.getClass().isActor())
|
||||
animation->addSpellCastGlow(effect);
|
||||
animation->addSpellCastGlow(effect->getColor());
|
||||
|
||||
addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel));
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace MWMechanics
|
|||
MWWorld::Ptr mCaster; // May be empty
|
||||
MWWorld::Ptr mTarget; // May be empty
|
||||
|
||||
void playSpellCastingEffects(const std::vector<ESM::ENAMstruct>& effects) const;
|
||||
void playSpellCastingEffects(const std::vector<ESM::IndexedENAMstruct>& effects) const;
|
||||
|
||||
void explodeSpell(const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const;
|
||||
|
||||
|
@ -41,13 +41,13 @@ namespace MWMechanics
|
|||
false
|
||||
}; // Always succeed spells casted by NPCs/creatures regardless of their chance (default: false)
|
||||
bool mFromProjectile; // True if spell is cast by enchantment of some projectile (arrow, bolt or thrown weapon)
|
||||
bool mManualSpell; // True if spell is casted from script and ignores some checks (mana level, success chance,
|
||||
bool mScriptedSpell; // True if spell is casted from script and ignores some checks (mana level, success chance,
|
||||
// etc.)
|
||||
ESM::RefNum mItem;
|
||||
ESM::ActiveSpells::EffectType mType{ ESM::ActiveSpells::Type_Temporary };
|
||||
ESM::ActiveSpells::Flags mFlags{ ESM::ActiveSpells::Flag_Temporary };
|
||||
|
||||
CastSpell(const MWWorld::Ptr& caster, const MWWorld::Ptr& target, const bool fromProjectile = false,
|
||||
const bool manualSpell = false);
|
||||
const bool scriptedSpell = false);
|
||||
|
||||
bool cast(const ESM::Spell* spell);
|
||||
|
||||
|
|
|
@ -289,7 +289,7 @@ namespace
|
|||
animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel),
|
||||
ESM::MagicEffect::indexToName(ESM::MagicEffect::SpellAbsorption), false);
|
||||
int spellCost = 0;
|
||||
if (const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellParams.getId()))
|
||||
if (const ESM::Spell* spell = esmStore.get<ESM::Spell>().search(spellParams.getSourceSpellId()))
|
||||
{
|
||||
spellCost = MWMechanics::calcSpellCost(*spell);
|
||||
}
|
||||
|
@ -314,8 +314,7 @@ namespace
|
|||
auto& stats = target.getClass().getCreatureStats(target);
|
||||
auto& magnitudes = stats.getMagicEffects();
|
||||
// Apply reflect and spell absorption
|
||||
if (target != caster && spellParams.getType() != ESM::ActiveSpells::Type_Enchantment
|
||||
&& spellParams.getType() != ESM::ActiveSpells::Type_Permanent)
|
||||
if (target != caster && spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary))
|
||||
{
|
||||
bool canReflect = !(magicEffect->mData.mFlags & ESM::MagicEffect::Unreflectable)
|
||||
&& !(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Reflect)
|
||||
|
@ -358,9 +357,8 @@ namespace
|
|||
// Apply resistances
|
||||
if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances))
|
||||
{
|
||||
const ESM::Spell* spell = nullptr;
|
||||
if (spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
||||
spell = MWBase::Environment::get().getESMStore()->get<ESM::Spell>().search(spellParams.getId());
|
||||
const ESM::Spell* spell
|
||||
= spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary) ? spellParams.getSpell() : nullptr;
|
||||
float magnitudeMult
|
||||
= MWMechanics::getEffectMultiplier(effect.mEffectId, target, caster, spell, &magnitudes);
|
||||
if (magnitudeMult == 0)
|
||||
|
@ -429,10 +427,9 @@ namespace MWMechanics
|
|||
// Dispel removes entire spells at once
|
||||
target.getClass().getCreatureStats(target).getActiveSpells().purge(
|
||||
[magnitude = effect.mMagnitude](const ActiveSpells::ActiveSpellParams& params) {
|
||||
if (params.getType() == ESM::ActiveSpells::Type_Temporary)
|
||||
if (params.hasFlag(ESM::ActiveSpells::Flag_Temporary))
|
||||
{
|
||||
const ESM::Spell* spell
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Spell>().search(params.getId());
|
||||
const ESM::Spell* spell = params.getSpell();
|
||||
if (spell && spell->mData.mType == ESM::Spell::ST_Spell)
|
||||
{
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
|
@ -645,7 +642,7 @@ namespace MWMechanics
|
|||
else if (effect.mEffectId == ESM::MagicEffect::DamageFatigue)
|
||||
index = 2;
|
||||
// Damage "Dynamic" abilities reduce the base value
|
||||
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
|
||||
if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues))
|
||||
modDynamicStat(target, index, -effect.mMagnitude);
|
||||
else
|
||||
{
|
||||
|
@ -666,7 +663,7 @@ namespace MWMechanics
|
|||
else if (!godmode)
|
||||
{
|
||||
// Damage Skill abilities reduce base skill :todd:
|
||||
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
|
||||
if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues))
|
||||
{
|
||||
auto& npcStats = target.getClass().getNpcStats(target);
|
||||
SkillValue& skill = npcStats.getSkill(effect.getSkillOrAttribute());
|
||||
|
@ -725,7 +722,7 @@ namespace MWMechanics
|
|||
case ESM::MagicEffect::FortifyHealth:
|
||||
case ESM::MagicEffect::FortifyMagicka:
|
||||
case ESM::MagicEffect::FortifyFatigue:
|
||||
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
|
||||
if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues))
|
||||
modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, effect.mMagnitude);
|
||||
else
|
||||
adjustDynamicStat(
|
||||
|
@ -737,7 +734,7 @@ namespace MWMechanics
|
|||
break;
|
||||
case ESM::MagicEffect::FortifyAttribute:
|
||||
// Abilities affect base stats, but not for drain
|
||||
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
|
||||
if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues))
|
||||
{
|
||||
auto& creatureStats = target.getClass().getCreatureStats(target);
|
||||
auto attribute = effect.getSkillOrAttribute();
|
||||
|
@ -757,7 +754,7 @@ namespace MWMechanics
|
|||
case ESM::MagicEffect::FortifySkill:
|
||||
if (!target.getClass().isNpc())
|
||||
invalid = true;
|
||||
else if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
|
||||
else if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues))
|
||||
{
|
||||
// Abilities affect base stats, but not for drain
|
||||
auto& npcStats = target.getClass().getNpcStats(target);
|
||||
|
@ -922,7 +919,7 @@ namespace MWMechanics
|
|||
{
|
||||
MWRender::Animation* animation = world->getAnimation(target);
|
||||
if (animation)
|
||||
animation->addSpellCastGlow(magicEffect);
|
||||
animation->addSpellCastGlow(magicEffect->getColor());
|
||||
int magnitude = static_cast<int>(roll(effect));
|
||||
if (target.getCellRef().getLockLevel()
|
||||
< magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude
|
||||
|
@ -947,7 +944,7 @@ namespace MWMechanics
|
|||
|
||||
MWRender::Animation* animation = world->getAnimation(target);
|
||||
if (animation)
|
||||
animation->addSpellCastGlow(magicEffect);
|
||||
animation->addSpellCastGlow(magicEffect->getColor());
|
||||
int magnitude = static_cast<int>(roll(effect));
|
||||
if (target.getCellRef().getLockLevel() <= magnitude)
|
||||
{
|
||||
|
@ -985,7 +982,7 @@ namespace MWMechanics
|
|||
return { MagicApplicationResult::Type::APPLIED, receivedMagicDamage, affectedHealth };
|
||||
auto& stats = target.getClass().getCreatureStats(target);
|
||||
auto& magnitudes = stats.getMagicEffects();
|
||||
if (spellParams.getType() != ESM::ActiveSpells::Type_Ability
|
||||
if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues)
|
||||
&& !(effect.mFlags & ESM::ActiveEffect::Flag_Applied))
|
||||
{
|
||||
MagicApplicationResult::Type result
|
||||
|
@ -998,10 +995,9 @@ namespace MWMechanics
|
|||
oldMagnitude = effect.mMagnitude;
|
||||
else
|
||||
{
|
||||
if (spellParams.getType() != ESM::ActiveSpells::Type_Enchantment)
|
||||
playEffects(target, *magicEffect,
|
||||
spellParams.getType() == ESM::ActiveSpells::Type_Consumable
|
||||
|| spellParams.getType() == ESM::ActiveSpells::Type_Temporary);
|
||||
if (!spellParams.hasFlag(ESM::ActiveSpells::Flag_Equipment)
|
||||
&& !spellParams.hasFlag(ESM::ActiveSpells::Flag_Lua))
|
||||
playEffects(target, *magicEffect, spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary));
|
||||
if (effect.mEffectId == ESM::MagicEffect::Soultrap && !target.getClass().isNpc()
|
||||
&& target.getType() == ESM::Creature::sRecordId
|
||||
&& target.get<ESM::Creature>()->mBase->mData.mSoul == 0 && caster == getPlayer())
|
||||
|
@ -1016,8 +1012,7 @@ namespace MWMechanics
|
|||
if (effect.mDuration != 0)
|
||||
{
|
||||
float mult = dt;
|
||||
if (spellParams.getType() == ESM::ActiveSpells::Type_Consumable
|
||||
|| spellParams.getType() == ESM::ActiveSpells::Type_Temporary)
|
||||
if (spellParams.hasFlag(ESM::ActiveSpells::Flag_Temporary))
|
||||
mult = std::min(effect.mTimeLeft, dt);
|
||||
effect.mMagnitude *= mult;
|
||||
}
|
||||
|
@ -1195,7 +1190,7 @@ namespace MWMechanics
|
|||
case ESM::MagicEffect::FortifyHealth:
|
||||
case ESM::MagicEffect::FortifyMagicka:
|
||||
case ESM::MagicEffect::FortifyFatigue:
|
||||
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
|
||||
if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues))
|
||||
modDynamicStat(target, effect.mEffectId - ESM::MagicEffect::FortifyHealth, -effect.mMagnitude);
|
||||
else
|
||||
adjustDynamicStat(
|
||||
|
@ -1206,7 +1201,7 @@ namespace MWMechanics
|
|||
break;
|
||||
case ESM::MagicEffect::FortifyAttribute:
|
||||
// Abilities affect base stats, but not for drain
|
||||
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
|
||||
if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues))
|
||||
{
|
||||
auto& creatureStats = target.getClass().getCreatureStats(target);
|
||||
auto attribute = effect.getSkillOrAttribute();
|
||||
|
@ -1222,7 +1217,7 @@ namespace MWMechanics
|
|||
break;
|
||||
case ESM::MagicEffect::FortifySkill:
|
||||
// Abilities affect base stats, but not for drain
|
||||
if (spellParams.getType() == ESM::ActiveSpells::Type_Ability)
|
||||
if (spellParams.hasFlag(ESM::ActiveSpells::Flag_AffectsBaseValues))
|
||||
{
|
||||
auto& npcStats = target.getClass().getNpcStats(target);
|
||||
auto& skill = npcStats.getSkill(effect.getSkillOrAttribute());
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace
|
|||
if (effectFilter == -1)
|
||||
{
|
||||
const ESM::Spell* spell
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Spell>().search(it->getId());
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::Spell>().search(it->getSourceSpellId());
|
||||
if (!spell || spell->mData.mType != ESM::Spell::ST_Spell)
|
||||
continue;
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ namespace
|
|||
const MWMechanics::ActiveSpells& activeSpells = actor.getClass().getCreatureStats(actor).getActiveSpells();
|
||||
for (MWMechanics::ActiveSpells::TIterator it = activeSpells.begin(); it != activeSpells.end(); ++it)
|
||||
{
|
||||
if (it->getId() != spellId)
|
||||
if (it->getSourceSpellId() != spellId)
|
||||
continue;
|
||||
|
||||
const MWMechanics::ActiveSpells::ActiveSpellParams& params = *it;
|
||||
|
@ -85,7 +85,7 @@ namespace
|
|||
int actorId = caster.getClass().getCreatureStats(caster).getActorId();
|
||||
const auto& active = target.getClass().getCreatureStats(target).getActiveSpells();
|
||||
return std::find_if(active.begin(), active.end(), [&](const auto& spell) {
|
||||
return spell.getCasterActorId() == actorId && spell.getId() == id;
|
||||
return spell.getCasterActorId() == actorId && spell.getSourceSpellId() == id;
|
||||
}) != active.end();
|
||||
}
|
||||
|
||||
|
@ -110,13 +110,13 @@ namespace MWMechanics
|
|||
int getRangeTypes(const ESM::EffectList& effects)
|
||||
{
|
||||
int types = 0;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator it = effects.mList.begin(); it != effects.mList.end(); ++it)
|
||||
for (const ESM::IndexedENAMstruct& effect : effects.mList)
|
||||
{
|
||||
if (it->mRange == ESM::RT_Self)
|
||||
if (effect.mData.mRange == ESM::RT_Self)
|
||||
types |= RangeTypes::Self;
|
||||
else if (it->mRange == ESM::RT_Touch)
|
||||
else if (effect.mData.mRange == ESM::RT_Touch)
|
||||
types |= RangeTypes::Touch;
|
||||
else if (it->mRange == ESM::RT_Target)
|
||||
else if (effect.mData.mRange == ESM::RT_Target)
|
||||
types |= RangeTypes::Target;
|
||||
}
|
||||
return types;
|
||||
|
@ -735,12 +735,12 @@ namespace MWMechanics
|
|||
static const float fAIMagicSpellMult = gmst.find("fAIMagicSpellMult")->mValue.getFloat();
|
||||
static const float fAIRangeMagicSpellMult = gmst.find("fAIRangeMagicSpellMult")->mValue.getFloat();
|
||||
|
||||
for (const ESM::ENAMstruct& effect : list.mList)
|
||||
for (const ESM::IndexedENAMstruct& effect : list.mList)
|
||||
{
|
||||
float effectRating = rateEffect(effect, actor, enemy);
|
||||
float effectRating = rateEffect(effect.mData, actor, enemy);
|
||||
if (useSpellMult)
|
||||
{
|
||||
if (effect.mRange == ESM::RT_Target)
|
||||
if (effect.mData.mRange == ESM::RT_Target)
|
||||
effectRating *= fAIRangeMagicSpellMult;
|
||||
else
|
||||
effectRating *= fAIMagicSpellMult;
|
||||
|
@ -760,10 +760,10 @@ namespace MWMechanics
|
|||
|
||||
float mult = fAIMagicSpellMult;
|
||||
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin();
|
||||
for (std::vector<ESM::IndexedENAMstruct>::const_iterator effectIt = spell->mEffects.mList.begin();
|
||||
effectIt != spell->mEffects.mList.end(); ++effectIt)
|
||||
{
|
||||
if (effectIt->mRange == ESM::RT_Target)
|
||||
if (effectIt->mData.mRange == ESM::RT_Target)
|
||||
{
|
||||
if (!MWBase::Environment::get().getWorld()->isSwimming(enemy))
|
||||
mult = fAIRangeMagicSpellMult;
|
||||
|
|
|
@ -174,7 +174,7 @@ namespace MWMechanics
|
|||
{
|
||||
for (const auto& effectIt : spell->mEffects.mList)
|
||||
{
|
||||
if (effectIt.mEffectID == ESM::MagicEffect::Corprus)
|
||||
if (effectIt.mData.mEffectID == ESM::MagicEffect::Corprus)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include <components/esm3/loadalch.hpp>
|
||||
#include <components/esm3/loadench.hpp>
|
||||
#include <components/esm3/loadingr.hpp>
|
||||
#include <components/esm3/loadmgef.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -23,13 +24,13 @@ namespace MWMechanics
|
|||
{
|
||||
float cost = 0;
|
||||
|
||||
for (const ESM::ENAMstruct& effect : list.mList)
|
||||
for (const ESM::IndexedENAMstruct& effect : list.mList)
|
||||
{
|
||||
float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect, nullptr, method));
|
||||
float effectCost = std::max(0.f, MWMechanics::calcEffectCost(effect.mData, nullptr, method));
|
||||
|
||||
// This is applied to the whole spell cost for each effect when
|
||||
// creating spells, but is only applied on the effect itself in TES:CS.
|
||||
if (effect.mRange == ESM::RT_Target)
|
||||
if (effect.mData.mRange == ESM::RT_Target)
|
||||
effectCost *= 1.5;
|
||||
|
||||
cost += effectCost;
|
||||
|
@ -158,25 +159,83 @@ namespace MWMechanics
|
|||
return potion.mData.mValue;
|
||||
}
|
||||
|
||||
std::optional<ESM::EffectList> rollIngredientEffect(
|
||||
MWWorld::Ptr caster, const ESM::Ingredient* ingredient, uint32_t index)
|
||||
{
|
||||
if (index >= 4)
|
||||
throw std::range_error("Index out of range");
|
||||
|
||||
ESM::ENAMstruct effect;
|
||||
effect.mEffectID = ingredient->mData.mEffectID[index];
|
||||
effect.mSkill = ingredient->mData.mSkills[index];
|
||||
effect.mAttribute = ingredient->mData.mAttributes[index];
|
||||
effect.mRange = ESM::RT_Self;
|
||||
effect.mArea = 0;
|
||||
|
||||
if (effect.mEffectID < 0)
|
||||
return std::nullopt;
|
||||
|
||||
const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore();
|
||||
const auto magicEffect = store.get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||
const MWMechanics::CreatureStats& creatureStats = caster.getClass().getCreatureStats(caster);
|
||||
|
||||
float x = (caster.getClass().getSkill(caster, ESM::Skill::Alchemy)
|
||||
+ 0.2f * creatureStats.getAttribute(ESM::Attribute::Intelligence).getModified()
|
||||
+ 0.1f * creatureStats.getAttribute(ESM::Attribute::Luck).getModified())
|
||||
* creatureStats.getFatigueTerm();
|
||||
|
||||
auto& prng = MWBase::Environment::get().getWorld()->getPrng();
|
||||
int roll = Misc::Rng::roll0to99(prng);
|
||||
if (roll > x)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
float magnitude = 0;
|
||||
float y = roll / std::min(x, 100.f);
|
||||
y *= 0.25f * x;
|
||||
if (magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)
|
||||
effect.mDuration = 1;
|
||||
else
|
||||
effect.mDuration = static_cast<int>(y);
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
|
||||
{
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
|
||||
magnitude = floor((0.05f * y) / (0.1f * magicEffect->mData.mBaseCost));
|
||||
else
|
||||
magnitude = floor(y / (0.1f * magicEffect->mData.mBaseCost));
|
||||
magnitude = std::max(1.f, magnitude);
|
||||
}
|
||||
else
|
||||
magnitude = 1;
|
||||
|
||||
effect.mMagnMax = static_cast<int>(magnitude);
|
||||
effect.mMagnMin = static_cast<int>(magnitude);
|
||||
|
||||
ESM::EffectList effects;
|
||||
effects.mList.push_back({ effect, index });
|
||||
return effects;
|
||||
}
|
||||
|
||||
float calcSpellBaseSuccessChance(const ESM::Spell* spell, const MWWorld::Ptr& actor, ESM::RefId* effectiveSchool)
|
||||
{
|
||||
// Morrowind for some reason uses a formula slightly different from magicka cost calculation
|
||||
float y = std::numeric_limits<float>::max();
|
||||
float lowestSkill = 0;
|
||||
|
||||
for (const ESM::ENAMstruct& effect : spell->mEffects.mList)
|
||||
for (const ESM::IndexedENAMstruct& effect : spell->mEffects.mList)
|
||||
{
|
||||
float x = static_cast<float>(effect.mDuration);
|
||||
float x = static_cast<float>(effect.mData.mDuration);
|
||||
const auto magicEffect
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effect.mEffectID);
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effect.mData.mEffectID);
|
||||
|
||||
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::AppliedOnce))
|
||||
x = std::max(1.f, x);
|
||||
|
||||
x *= 0.1f * magicEffect->mData.mBaseCost;
|
||||
x *= 0.5f * (effect.mMagnMin + effect.mMagnMax);
|
||||
x += effect.mArea * 0.05f * magicEffect->mData.mBaseCost;
|
||||
if (effect.mRange == ESM::RT_Target)
|
||||
x *= 0.5f * (effect.mData.mMagnMin + effect.mData.mMagnMax);
|
||||
x += effect.mData.mArea * 0.05f * magicEffect->mData.mBaseCost;
|
||||
if (effect.mData.mRange == ESM::RT_Target)
|
||||
x *= 1.5f;
|
||||
static const float fEffectCostMult = MWBase::Environment::get()
|
||||
.getESMStore()
|
||||
|
|
|
@ -3,10 +3,14 @@
|
|||
|
||||
#include <components/esm3/loadskil.hpp>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
struct EffectList;
|
||||
struct ENAMstruct;
|
||||
struct Enchantment;
|
||||
struct Ingredient;
|
||||
struct MagicEffect;
|
||||
struct Potion;
|
||||
struct Spell;
|
||||
|
@ -36,6 +40,8 @@ namespace MWMechanics
|
|||
int getEnchantmentCharge(const ESM::Enchantment& enchantment);
|
||||
|
||||
int getPotionValue(const ESM::Potion& potion);
|
||||
std::optional<ESM::EffectList> rollIngredientEffect(
|
||||
MWWorld::Ptr caster, const ESM::Ingredient* ingredient, uint32_t index = 0);
|
||||
|
||||
/**
|
||||
* @param spell spell to cast
|
||||
|
|
|
@ -1511,7 +1511,7 @@ namespace MWRender
|
|||
return mObjectRoot.get();
|
||||
}
|
||||
|
||||
void Animation::addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration)
|
||||
void Animation::addSpellCastGlow(const osg::Vec4f& color, float glowDuration)
|
||||
{
|
||||
if (!mGlowUpdater || (mGlowUpdater->isDone() || (mGlowUpdater->isPermanentGlowUpdater() == true)))
|
||||
{
|
||||
|
@ -1520,12 +1520,11 @@ namespace MWRender
|
|||
|
||||
if (mGlowUpdater && mGlowUpdater->isPermanentGlowUpdater())
|
||||
{
|
||||
mGlowUpdater->setColor(effect->getColor());
|
||||
mGlowUpdater->setColor(color);
|
||||
mGlowUpdater->setDuration(glowDuration);
|
||||
}
|
||||
else
|
||||
mGlowUpdater
|
||||
= SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, effect->getColor(), glowDuration);
|
||||
mGlowUpdater = SceneUtil::addEnchantedGlow(mObjectRoot, mResourceSystem, color, glowDuration);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -345,7 +345,7 @@ namespace MWRender
|
|||
|
||||
// Add a spell casting glow to an object. From measuring video taken from the original engine,
|
||||
// the glow seems to be about 1.5 seconds except for telekinesis, which is 1 second.
|
||||
void addSpellCastGlow(const ESM::MagicEffect* effect, float glowDuration = 1.5);
|
||||
void addSpellCastGlow(const osg::Vec4f& color, float glowDuration = 1.5);
|
||||
|
||||
virtual void updatePtr(const MWWorld::Ptr& ptr);
|
||||
|
||||
|
|
|
@ -560,7 +560,7 @@ namespace MWScript
|
|||
runtime.pop();
|
||||
|
||||
if (ptr.getClass().isActor())
|
||||
ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid);
|
||||
ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffectsBySourceSpellId(ptr, spellid);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -532,7 +532,7 @@ namespace MWWorld
|
|||
return result;
|
||||
|
||||
const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().search(
|
||||
enchantment->mEffects.mList.front().mEffectID);
|
||||
enchantment->mEffects.mList.front().mData.mEffectID);
|
||||
if (!magicEffect)
|
||||
return result;
|
||||
|
||||
|
|
|
@ -171,31 +171,31 @@ namespace
|
|||
auto iter = spell.mEffects.mList.begin();
|
||||
while (iter != spell.mEffects.mList.end())
|
||||
{
|
||||
const ESM::MagicEffect* mgef = magicEffects.search(iter->mEffectID);
|
||||
const ESM::MagicEffect* mgef = magicEffects.search(iter->mData.mEffectID);
|
||||
if (!mgef)
|
||||
{
|
||||
Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId
|
||||
<< ": dropping invalid effect (index " << iter->mEffectID << ")";
|
||||
<< ": dropping invalid effect (index " << iter->mData.mEffectID << ")";
|
||||
iter = spell.mEffects.mList.erase(iter);
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mAttribute != -1)
|
||||
if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mData.mAttribute != -1)
|
||||
{
|
||||
iter->mAttribute = -1;
|
||||
iter->mData.mAttribute = -1;
|
||||
Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId
|
||||
<< ": dropping unexpected attribute argument of "
|
||||
<< ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect";
|
||||
<< ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect";
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mSkill != -1)
|
||||
if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mData.mSkill != -1)
|
||||
{
|
||||
iter->mSkill = -1;
|
||||
iter->mData.mSkill = -1;
|
||||
Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId
|
||||
<< ": dropping unexpected skill argument of "
|
||||
<< ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect";
|
||||
<< ESM::MagicEffect::indexToGmstString(iter->mData.mEffectID) << " effect";
|
||||
changed = true;
|
||||
}
|
||||
|
||||
|
@ -742,7 +742,16 @@ namespace MWWorld
|
|||
|
||||
case ESM::REC_DYNA:
|
||||
reader.getSubNameIs("COUN");
|
||||
if (reader.getFormatVersion() <= ESM::MaxActiveSpellTypeVersion)
|
||||
{
|
||||
uint32_t dynamicCount32 = 0;
|
||||
reader.getHT(dynamicCount32);
|
||||
mDynamicCount = dynamicCount32;
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.getHT(mDynamicCount);
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
|
|
@ -162,7 +162,7 @@ namespace MWWorld
|
|||
std::vector<StoreBase*> mStores;
|
||||
std::vector<DynamicStore*> mDynamicStores;
|
||||
|
||||
unsigned int mDynamicCount;
|
||||
uint64_t mDynamicCount;
|
||||
|
||||
mutable std::unordered_map<ESM::RefId, std::weak_ptr<MWMechanics::SpellList>> mSpellListCache;
|
||||
|
||||
|
@ -209,6 +209,7 @@ namespace MWWorld
|
|||
|
||||
void clearDynamic();
|
||||
void rebuildIdsIndex();
|
||||
ESM::RefId generateId() { return ESM::RefId::generated(mDynamicCount++); }
|
||||
|
||||
void movePlayerRecord();
|
||||
|
||||
|
@ -229,7 +230,7 @@ namespace MWWorld
|
|||
template <class T>
|
||||
const T* insert(const T& x)
|
||||
{
|
||||
const ESM::RefId id = ESM::RefId::generated(mDynamicCount++);
|
||||
const ESM::RefId id = generateId();
|
||||
|
||||
Store<T>& store = getWritable<T>();
|
||||
if (store.search(id) != nullptr)
|
||||
|
|
|
@ -46,8 +46,8 @@ namespace MWWorld
|
|||
|
||||
for (auto& effect : spell->mEffects.mList)
|
||||
{
|
||||
if (effect.mEffectID == ESM::MagicEffect::DrainAttribute)
|
||||
stats.mWorsenings[effect.mAttribute] = oldStats.mWorsenings;
|
||||
if (effect.mData.mEffectID == ESM::MagicEffect::DrainAttribute)
|
||||
stats.mWorsenings[effect.mData.mAttribute] = oldStats.mWorsenings;
|
||||
}
|
||||
creatureStats.mCorprusSpells[id] = stats;
|
||||
}
|
||||
|
@ -58,30 +58,30 @@ namespace MWWorld
|
|||
if (!spell || spell->mData.mType == ESM::Spell::ST_Spell || spell->mData.mType == ESM::Spell::ST_Power)
|
||||
continue;
|
||||
ESM::ActiveSpells::ActiveSpellParams params;
|
||||
params.mId = id;
|
||||
params.mSourceSpellId = id;
|
||||
params.mDisplayName = spell->mName;
|
||||
params.mCasterActorId = creatureStats.mActorId;
|
||||
if (spell->mData.mType == ESM::Spell::ST_Ability)
|
||||
params.mType = ESM::ActiveSpells::Type_Ability;
|
||||
params.mFlags = ESM::Compatibility::ActiveSpells::Type_Ability_Flags;
|
||||
else
|
||||
params.mType = ESM::ActiveSpells::Type_Permanent;
|
||||
params.mFlags = ESM::Compatibility::ActiveSpells::Type_Permanent_Flags;
|
||||
params.mWorsenings = -1;
|
||||
params.mNextWorsening = ESM::TimeStamp();
|
||||
int effectIndex = 0;
|
||||
for (const auto& enam : spell->mEffects.mList)
|
||||
{
|
||||
if (oldParams.mPurgedEffects.find(effectIndex) == oldParams.mPurgedEffects.end())
|
||||
if (oldParams.mPurgedEffects.find(enam.mIndex) == oldParams.mPurgedEffects.end())
|
||||
{
|
||||
ESM::ActiveEffect effect;
|
||||
effect.mEffectId = enam.mEffectID;
|
||||
effect.mArg = MWMechanics::EffectKey(enam).mArg;
|
||||
effect.mEffectId = enam.mData.mEffectID;
|
||||
effect.mArg = MWMechanics::EffectKey(enam.mData).mArg;
|
||||
effect.mDuration = -1;
|
||||
effect.mTimeLeft = -1;
|
||||
effect.mEffectIndex = effectIndex;
|
||||
auto rand = oldParams.mEffectRands.find(effectIndex);
|
||||
effect.mEffectIndex = enam.mIndex;
|
||||
auto rand = oldParams.mEffectRands.find(enam.mIndex);
|
||||
if (rand != oldParams.mEffectRands.end())
|
||||
{
|
||||
float magnitude = (enam.mMagnMax - enam.mMagnMin) * rand->second + enam.mMagnMin;
|
||||
float magnitude
|
||||
= (enam.mData.mMagnMax - enam.mData.mMagnMin) * rand->second + enam.mData.mMagnMin;
|
||||
effect.mMagnitude = magnitude;
|
||||
effect.mMinMagnitude = magnitude;
|
||||
effect.mMaxMagnitude = magnitude;
|
||||
|
@ -92,13 +92,12 @@ namespace MWWorld
|
|||
else
|
||||
{
|
||||
effect.mMagnitude = 0.f;
|
||||
effect.mMinMagnitude = enam.mMagnMin;
|
||||
effect.mMaxMagnitude = enam.mMagnMax;
|
||||
effect.mMinMagnitude = enam.mData.mMagnMin;
|
||||
effect.mMaxMagnitude = enam.mData.mMagnMax;
|
||||
effect.mFlags = ESM::ActiveEffect::Flag_None;
|
||||
}
|
||||
params.mEffects.emplace_back(effect);
|
||||
}
|
||||
effectIndex++;
|
||||
}
|
||||
creatureStats.mActiveSpells.mSpells.emplace_back(params);
|
||||
}
|
||||
|
@ -132,30 +131,28 @@ namespace MWWorld
|
|||
if (!enchantment)
|
||||
continue;
|
||||
ESM::ActiveSpells::ActiveSpellParams params;
|
||||
params.mId = id;
|
||||
params.mSourceSpellId = id;
|
||||
params.mDisplayName = std::move(name);
|
||||
params.mCasterActorId = creatureStats.mActorId;
|
||||
params.mType = ESM::ActiveSpells::Type_Enchantment;
|
||||
params.mFlags = ESM::Compatibility::ActiveSpells::Type_Enchantment_Flags;
|
||||
params.mWorsenings = -1;
|
||||
params.mNextWorsening = ESM::TimeStamp();
|
||||
for (std::size_t effectIndex = 0;
|
||||
effectIndex < oldMagnitudes.size() && effectIndex < enchantment->mEffects.mList.size(); ++effectIndex)
|
||||
for (const auto& enam : enchantment->mEffects.mList)
|
||||
{
|
||||
const auto& enam = enchantment->mEffects.mList[effectIndex];
|
||||
auto [random, multiplier] = oldMagnitudes[effectIndex];
|
||||
float magnitude = (enam.mMagnMax - enam.mMagnMin) * random + enam.mMagnMin;
|
||||
auto [random, multiplier] = oldMagnitudes[enam.mIndex];
|
||||
float magnitude = (enam.mData.mMagnMax - enam.mData.mMagnMin) * random + enam.mData.mMagnMin;
|
||||
magnitude *= multiplier;
|
||||
if (magnitude <= 0)
|
||||
continue;
|
||||
ESM::ActiveEffect effect;
|
||||
effect.mEffectId = enam.mEffectID;
|
||||
effect.mEffectId = enam.mData.mEffectID;
|
||||
effect.mMagnitude = magnitude;
|
||||
effect.mMinMagnitude = magnitude;
|
||||
effect.mMaxMagnitude = magnitude;
|
||||
effect.mArg = MWMechanics::EffectKey(enam).mArg;
|
||||
effect.mArg = MWMechanics::EffectKey(enam.mData).mArg;
|
||||
effect.mDuration = -1;
|
||||
effect.mTimeLeft = -1;
|
||||
effect.mEffectIndex = static_cast<int>(effectIndex);
|
||||
effect.mEffectIndex = enam.mIndex;
|
||||
// Prevent recalculation of resistances and don't reflect or absorb the effect
|
||||
effect.mFlags = ESM::ActiveEffect::Flag_Ignore_Resistances | ESM::ActiveEffect::Flag_Ignore_Reflect
|
||||
| ESM::ActiveEffect::Flag_Ignore_SpellAbsorption;
|
||||
|
@ -172,7 +169,7 @@ namespace MWWorld
|
|||
{
|
||||
auto it
|
||||
= std::find_if(creatureStats.mActiveSpells.mSpells.begin(), creatureStats.mActiveSpells.mSpells.end(),
|
||||
[&](const auto& params) { return params.mId == spell.first; });
|
||||
[&](const auto& params) { return params.mSourceSpellId == spell.first; });
|
||||
if (it != creatureStats.mActiveSpells.mSpells.end())
|
||||
{
|
||||
it->mNextWorsening = spell.second.mNextWorsening;
|
||||
|
@ -188,7 +185,7 @@ namespace MWWorld
|
|||
continue;
|
||||
for (auto& params : creatureStats.mActiveSpells.mSpells)
|
||||
{
|
||||
if (params.mId == key.mSourceId)
|
||||
if (params.mSourceSpellId == key.mSourceId)
|
||||
{
|
||||
bool found = false;
|
||||
for (auto& effect : params.mEffects)
|
||||
|
|
|
@ -79,18 +79,17 @@ namespace
|
|||
int count = 0;
|
||||
speed = 0.0f;
|
||||
ESM::EffectList projectileEffects;
|
||||
for (std::vector<ESM::ENAMstruct>::const_iterator iter(effects->mList.begin()); iter != effects->mList.end();
|
||||
++iter)
|
||||
for (const ESM::IndexedENAMstruct& effect : effects->mList)
|
||||
{
|
||||
const ESM::MagicEffect* magicEffect
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(iter->mEffectID);
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(effect.mData.mEffectID);
|
||||
|
||||
// Speed of multi-effect projectiles should be the average of the constituent effects,
|
||||
// based on observation of the original engine.
|
||||
speed += magicEffect->mData.mSpeed;
|
||||
count++;
|
||||
|
||||
if (iter->mRange != ESM::RT_Target)
|
||||
if (effect.mData.mRange != ESM::RT_Target)
|
||||
continue;
|
||||
|
||||
if (magicEffect->mBolt.empty())
|
||||
|
@ -106,7 +105,7 @@ namespace
|
|||
->get<ESM::Skill>()
|
||||
.find(magicEffect->mData.mSchool)
|
||||
->mSchool->mBoltSound);
|
||||
projectileEffects.mList.push_back(*iter);
|
||||
projectileEffects.mList.push_back(effect);
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
|
@ -117,7 +116,7 @@ namespace
|
|||
{
|
||||
const ESM::MagicEffect* magicEffect
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(
|
||||
effects->mList.begin()->mEffectID);
|
||||
effects->mList.begin()->mData.mEffectID);
|
||||
texture = magicEffect->mParticle;
|
||||
}
|
||||
|
||||
|
@ -136,10 +135,10 @@ namespace
|
|||
{
|
||||
// Calculate combined light diffuse color from magical effects
|
||||
osg::Vec4 lightDiffuseColor;
|
||||
for (const ESM::ENAMstruct& enam : effects.mList)
|
||||
for (const ESM::IndexedENAMstruct& enam : effects.mList)
|
||||
{
|
||||
const ESM::MagicEffect* magicEffect
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(enam.mEffectID);
|
||||
= MWBase::Environment::get().getESMStore()->get<ESM::MagicEffect>().find(enam.mData.mEffectID);
|
||||
lightDiffuseColor += magicEffect->getColor();
|
||||
}
|
||||
int numberOfEffects = effects.mList.size();
|
||||
|
|
|
@ -2940,14 +2940,14 @@ namespace MWWorld
|
|||
return result;
|
||||
}
|
||||
|
||||
void World::castSpell(const Ptr& actor, bool manualSpell)
|
||||
void World::castSpell(const Ptr& actor, bool scriptedSpell)
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = actor.getClass().getCreatureStats(actor);
|
||||
|
||||
const bool casterIsPlayer = actor == MWMechanics::getPlayer();
|
||||
MWWorld::Ptr target;
|
||||
// For scripted spells we should not use hit contact
|
||||
if (manualSpell)
|
||||
if (scriptedSpell)
|
||||
{
|
||||
if (!casterIsPlayer)
|
||||
{
|
||||
|
@ -3015,7 +3015,7 @@ namespace MWWorld
|
|||
|
||||
const ESM::RefId& selectedSpell = stats.getSpells().getSelectedSpell();
|
||||
|
||||
MWMechanics::CastSpell cast(actor, target, false, manualSpell);
|
||||
MWMechanics::CastSpell cast(actor, target, false, scriptedSpell);
|
||||
cast.mHitPosition = hitPosition;
|
||||
|
||||
if (!selectedSpell.empty())
|
||||
|
@ -3703,22 +3703,22 @@ namespace MWWorld
|
|||
|
||||
void World::preloadEffects(const ESM::EffectList* effectList)
|
||||
{
|
||||
for (const ESM::ENAMstruct& effectInfo : effectList->mList)
|
||||
for (const ESM::IndexedENAMstruct& effectInfo : effectList->mList)
|
||||
{
|
||||
const ESM::MagicEffect* effect = mStore.get<ESM::MagicEffect>().find(effectInfo.mEffectID);
|
||||
const ESM::MagicEffect* effect = mStore.get<ESM::MagicEffect>().find(effectInfo.mData.mEffectID);
|
||||
|
||||
if (MWMechanics::isSummoningEffect(effectInfo.mEffectID))
|
||||
if (MWMechanics::isSummoningEffect(effectInfo.mData.mEffectID))
|
||||
{
|
||||
preload(mWorldScene.get(), mStore, ESM::RefId::stringRefId("VFX_Summon_Start"));
|
||||
preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mEffectID));
|
||||
preload(mWorldScene.get(), mStore, MWMechanics::getSummonedCreature(effectInfo.mData.mEffectID));
|
||||
}
|
||||
|
||||
preload(mWorldScene.get(), mStore, effect->mCasting);
|
||||
preload(mWorldScene.get(), mStore, effect->mHit);
|
||||
|
||||
if (effectInfo.mArea > 0)
|
||||
if (effectInfo.mData.mArea > 0)
|
||||
preload(mWorldScene.get(), mStore, effect->mArea);
|
||||
if (effectInfo.mRange == ESM::RT_Target)
|
||||
if (effectInfo.mData.mRange == ESM::RT_Target)
|
||||
preload(mWorldScene.get(), mStore, effect->mBolt);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -530,7 +530,7 @@ namespace ESM
|
|||
TEST_P(Esm3SaveLoadRecordTest, enamShouldNotChange)
|
||||
{
|
||||
EffectList record;
|
||||
record.mList.emplace_back(ENAMstruct{
|
||||
record.mList.emplace_back(IndexedENAMstruct{ {
|
||||
.mEffectID = 1,
|
||||
.mSkill = 2,
|
||||
.mAttribute = 3,
|
||||
|
@ -539,20 +539,21 @@ namespace ESM
|
|||
.mDuration = 6,
|
||||
.mMagnMin = 7,
|
||||
.mMagnMax = 8,
|
||||
});
|
||||
},
|
||||
0 });
|
||||
|
||||
EffectList result;
|
||||
saveAndLoadRecord(record, GetParam(), result);
|
||||
|
||||
EXPECT_EQ(result.mList.size(), record.mList.size());
|
||||
EXPECT_EQ(result.mList[0].mEffectID, record.mList[0].mEffectID);
|
||||
EXPECT_EQ(result.mList[0].mSkill, record.mList[0].mSkill);
|
||||
EXPECT_EQ(result.mList[0].mAttribute, record.mList[0].mAttribute);
|
||||
EXPECT_EQ(result.mList[0].mRange, record.mList[0].mRange);
|
||||
EXPECT_EQ(result.mList[0].mArea, record.mList[0].mArea);
|
||||
EXPECT_EQ(result.mList[0].mDuration, record.mList[0].mDuration);
|
||||
EXPECT_EQ(result.mList[0].mMagnMin, record.mList[0].mMagnMin);
|
||||
EXPECT_EQ(result.mList[0].mMagnMax, record.mList[0].mMagnMax);
|
||||
EXPECT_EQ(result.mList[0].mData.mEffectID, record.mList[0].mData.mEffectID);
|
||||
EXPECT_EQ(result.mList[0].mData.mSkill, record.mList[0].mData.mSkill);
|
||||
EXPECT_EQ(result.mList[0].mData.mAttribute, record.mList[0].mData.mAttribute);
|
||||
EXPECT_EQ(result.mList[0].mData.mRange, record.mList[0].mData.mRange);
|
||||
EXPECT_EQ(result.mList[0].mData.mArea, record.mList[0].mData.mArea);
|
||||
EXPECT_EQ(result.mList[0].mData.mDuration, record.mList[0].mData.mDuration);
|
||||
EXPECT_EQ(result.mList[0].mData.mMagnMin, record.mList[0].mData.mMagnMin);
|
||||
EXPECT_EQ(result.mList[0].mData.mMagnMax, record.mList[0].mData.mMagnMax);
|
||||
}
|
||||
|
||||
TEST_P(Esm3SaveLoadRecordTest, weaponShouldNotChange)
|
||||
|
|
|
@ -93,11 +93,12 @@ namespace ESM
|
|||
{
|
||||
for (const auto& params : spells)
|
||||
{
|
||||
esm.writeHNRefId(tag, params.mId);
|
||||
esm.writeHNRefId(tag, params.mSourceSpellId);
|
||||
esm.writeHNRefId("SPID", params.mActiveSpellId);
|
||||
|
||||
esm.writeHNT("CAST", params.mCasterActorId);
|
||||
esm.writeHNString("DISP", params.mDisplayName);
|
||||
esm.writeHNT("TYPE", params.mType);
|
||||
esm.writeHNT("FLAG", params.mFlags);
|
||||
if (params.mItem.isSet())
|
||||
esm.writeFormId(params.mItem, true, "ITEM");
|
||||
if (params.mWorsenings >= 0)
|
||||
|
@ -130,14 +131,42 @@ namespace ESM
|
|||
while (esm.isNextSub(tag))
|
||||
{
|
||||
ActiveSpells::ActiveSpellParams params;
|
||||
params.mId = esm.getRefId();
|
||||
params.mSourceSpellId = esm.getRefId();
|
||||
if (format > MaxActiveSpellTypeVersion)
|
||||
params.mActiveSpellId = esm.getHNRefId("SPID");
|
||||
esm.getHNT(params.mCasterActorId, "CAST");
|
||||
params.mDisplayName = esm.getHNString("DISP");
|
||||
if (format <= MaxClearModifiersFormatVersion)
|
||||
params.mType = ActiveSpells::Type_Temporary;
|
||||
params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags;
|
||||
else
|
||||
{
|
||||
esm.getHNT(params.mType, "TYPE");
|
||||
if (format <= MaxActiveSpellTypeVersion)
|
||||
{
|
||||
Compatibility::ActiveSpells::EffectType type;
|
||||
esm.getHNT(type, "TYPE");
|
||||
switch (type)
|
||||
{
|
||||
case Compatibility::ActiveSpells::Type_Ability:
|
||||
params.mFlags = Compatibility::ActiveSpells::Type_Ability_Flags;
|
||||
break;
|
||||
case Compatibility::ActiveSpells::Type_Consumable:
|
||||
params.mFlags = Compatibility::ActiveSpells::Type_Consumable_Flags;
|
||||
break;
|
||||
case Compatibility::ActiveSpells::Type_Enchantment:
|
||||
params.mFlags = Compatibility::ActiveSpells::Type_Enchantment_Flags;
|
||||
break;
|
||||
case Compatibility::ActiveSpells::Type_Permanent:
|
||||
params.mFlags = Compatibility::ActiveSpells::Type_Permanent_Flags;
|
||||
break;
|
||||
case Compatibility::ActiveSpells::Type_Temporary:
|
||||
params.mFlags = Compatibility::ActiveSpells::Type_Temporary_Flags;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
esm.getHNT(params.mFlags, "FLAG");
|
||||
}
|
||||
if (esm.peekNextSub("ITEM"))
|
||||
{
|
||||
if (format <= MaxActiveSpellSlotIndexFormatVersion)
|
||||
|
|
|
@ -46,23 +46,28 @@ namespace ESM
|
|||
// format 0, saved games only
|
||||
struct ActiveSpells
|
||||
{
|
||||
enum EffectType
|
||||
enum Flags : uint32_t
|
||||
{
|
||||
Type_Temporary,
|
||||
Type_Ability,
|
||||
Type_Enchantment,
|
||||
Type_Permanent,
|
||||
Type_Consumable
|
||||
Flag_Temporary = 1 << 0, //!< Effect will end automatically once its duration ends.
|
||||
Flag_Equipment = 1 << 1, //!< Effect will end automatically if item is unequipped.
|
||||
Flag_SpellStore = 1 << 2, //!< Effect will end automatically if removed from the actor's spell store.
|
||||
Flag_AffectsBaseValues = 1 << 3, //!< Effects will affect base values instead of current values.
|
||||
Flag_Stackable
|
||||
= 1 << 4, //!< Effect can stack. If this flag is not set, spells from the same caster and item cannot stack.
|
||||
Flag_Lua
|
||||
= 1 << 5, //!< Effect was added via Lua. Should not do any vfx/sound as this is handled by Lua scripts.
|
||||
};
|
||||
|
||||
struct ActiveSpellParams
|
||||
{
|
||||
RefId mId;
|
||||
RefId mActiveSpellId;
|
||||
RefId mSourceSpellId;
|
||||
std::vector<ActiveEffect> mEffects;
|
||||
std::string mDisplayName;
|
||||
int32_t mCasterActorId;
|
||||
RefNum mItem;
|
||||
EffectType mType;
|
||||
Flags mFlags;
|
||||
bool mStackable;
|
||||
int32_t mWorsenings;
|
||||
TimeStamp mNextWorsening;
|
||||
};
|
||||
|
@ -73,6 +78,29 @@ namespace ESM
|
|||
void load(ESMReader& esm);
|
||||
void save(ESMWriter& esm) const;
|
||||
};
|
||||
|
||||
namespace Compatibility
|
||||
{
|
||||
namespace ActiveSpells
|
||||
{
|
||||
enum EffectType
|
||||
{
|
||||
Type_Temporary,
|
||||
Type_Ability,
|
||||
Type_Enchantment,
|
||||
Type_Permanent,
|
||||
Type_Consumable,
|
||||
};
|
||||
|
||||
using Flags = ESM::ActiveSpells::Flags;
|
||||
constexpr Flags Type_Temporary_Flags = Flags::Flag_Temporary;
|
||||
constexpr Flags Type_Consumable_Flags = static_cast<Flags>(Flags::Flag_Temporary | Flags::Flag_Stackable);
|
||||
constexpr Flags Type_Permanent_Flags = Flags::Flag_SpellStore;
|
||||
constexpr Flags Type_Ability_Flags
|
||||
= static_cast<Flags>(Flags::Flag_SpellStore | Flags::Flag_AffectsBaseValues);
|
||||
constexpr Flags Type_Enchantment_Flags = Flags::Flag_Equipment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -22,19 +22,40 @@ namespace ESM
|
|||
}
|
||||
}
|
||||
|
||||
void EffectList::populate(const std::vector<ENAMstruct>& effects)
|
||||
{
|
||||
mList.clear();
|
||||
for (size_t i = 0; i < effects.size(); i++)
|
||||
mList.push_back({ effects[i], static_cast<uint32_t>(i) });
|
||||
}
|
||||
|
||||
void EffectList::updateIndexes()
|
||||
{
|
||||
for (size_t i = 0; i < mList.size(); i++)
|
||||
mList[i].mIndex = i;
|
||||
}
|
||||
|
||||
void EffectList::add(ESMReader& esm)
|
||||
{
|
||||
ENAMstruct s;
|
||||
esm.getSubComposite(s);
|
||||
mList.push_back(s);
|
||||
mList.push_back({ s, static_cast<uint32_t>(mList.size()) });
|
||||
}
|
||||
|
||||
void EffectList::save(ESMWriter& esm) const
|
||||
{
|
||||
for (const ENAMstruct& enam : mList)
|
||||
for (const IndexedENAMstruct& enam : mList)
|
||||
{
|
||||
esm.writeNamedComposite("ENAM", enam);
|
||||
esm.writeNamedComposite("ENAM", enam.mData);
|
||||
}
|
||||
}
|
||||
|
||||
bool IndexedENAMstruct::operator!=(const IndexedENAMstruct& rhs) const
|
||||
{
|
||||
return mData.mEffectID != rhs.mData.mEffectID || mData.mArea != rhs.mData.mArea
|
||||
|| mData.mRange != rhs.mData.mRange || mData.mSkill != rhs.mData.mSkill
|
||||
|| mData.mAttribute != rhs.mData.mAttribute || mData.mMagnMin != rhs.mData.mMagnMin
|
||||
|| mData.mMagnMax != rhs.mData.mMagnMax || mData.mDuration != rhs.mData.mDuration;
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
|
|
|
@ -26,10 +26,21 @@ namespace ESM
|
|||
int32_t mArea, mDuration, mMagnMin, mMagnMax;
|
||||
};
|
||||
|
||||
struct IndexedENAMstruct
|
||||
{
|
||||
bool operator!=(const IndexedENAMstruct& rhs) const;
|
||||
bool operator==(const IndexedENAMstruct& rhs) const { return !(this->operator!=(rhs)); }
|
||||
ENAMstruct mData;
|
||||
uint32_t mIndex;
|
||||
};
|
||||
|
||||
/// EffectList, ENAM subrecord
|
||||
struct EffectList
|
||||
{
|
||||
std::vector<ENAMstruct> mList;
|
||||
std::vector<IndexedENAMstruct> mList;
|
||||
|
||||
void populate(const std::vector<ENAMstruct>& effects);
|
||||
void updateIndexes();
|
||||
|
||||
/// Load one effect, assumes subrecord name was already read
|
||||
void add(ESMReader& esm);
|
||||
|
|
|
@ -26,7 +26,8 @@ namespace ESM
|
|||
inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26;
|
||||
inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27;
|
||||
inline constexpr FormatVersion MaxOldCountFormatVersion = 30;
|
||||
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 31;
|
||||
inline constexpr FormatVersion MaxActiveSpellTypeVersion = 31;
|
||||
inline constexpr FormatVersion CurrentSaveGameFormatVersion = 32;
|
||||
|
||||
inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5;
|
||||
inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21;
|
||||
|
|
|
@ -339,6 +339,11 @@
|
|||
-- @field #string id Record id of the spell or item used to cast the spell
|
||||
-- @field #GameObject item The enchanted item used to cast the spell, or nil if the spell was not cast from an enchanted item. Note that if the spell was cast for a single-use enchantment such as a scroll, this will be nil.
|
||||
-- @field #GameObject caster The caster object, or nil if the spell has no defined caster
|
||||
-- @field #boolean fromEquipment If set, this spell is tied to an equipped item and can only be ended by unequipping the item.
|
||||
-- @field #boolean temporary If set, this spell effect is temporary and should end on its own. Either after a single application or after its duration has run out.
|
||||
-- @field #boolean affectsBaseValues If set, this spell affects the base values of affected stats, rather than modifying current values.
|
||||
-- @field #boolean stackable If set, this spell can be applied multiple times. If not set, the same spell can only be applied once from the same source (where source is determined by caster + item). In vanilla rules, consumables are stackable while spells and enchantments are not.
|
||||
-- @field #number activeSpellId A number uniquely identifying this active spell within the affected actor's list of active spells.
|
||||
-- @field #list<#ActiveSpellEffect> effects The active effects (@{#ActiveSpellEffect}) of this spell.
|
||||
|
||||
---
|
||||
|
@ -369,7 +374,7 @@
|
|||
-- @type Enchantment
|
||||
-- @field #string id Enchantment id
|
||||
-- @field #number type @{#EnchantmentType}
|
||||
-- @field #number autocalcFlag If set, the casting cost should be computer rather than reading the cost field
|
||||
-- @field #boolean autocalcFlag If set, the casting cost should be computed based on the effect list rather than read from the cost field
|
||||
-- @field #number cost
|
||||
-- @field #number charge Charge capacity. Should not be confused with current charge.
|
||||
-- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the enchantment
|
||||
|
@ -671,6 +676,8 @@
|
|||
-- @field #number type @{#SpellType}
|
||||
-- @field #number cost
|
||||
-- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the spell
|
||||
-- @field #boolean alwaysSucceedFlag If set, the spell should ignore skill checks and always succeed.
|
||||
-- @field #boolean autocalcFlag If set, the casting cost should be computed based on the effect list rather than read from the cost field
|
||||
|
||||
---
|
||||
-- @type MagicEffect
|
||||
|
@ -680,16 +687,28 @@
|
|||
-- @field #string school Skill ID that is this effect's school
|
||||
-- @field #number baseCost
|
||||
-- @field openmw.util#Color color
|
||||
-- @field #boolean harmful
|
||||
-- @field #boolean harmful If set, the effect is considered harmful and should elicit a hostile reaction from affected NPCs.
|
||||
-- @field #boolean continuousVfx Whether the magic effect's vfx should loop or not
|
||||
-- @field #boolean hasDuration If set, the magic effect has a duration. As an example, divine intervention has no duration while fire damage does.
|
||||
-- @field #boolean hasMagnitude If set, the magic effect depends on a magnitude. As an example, cure common disease has no magnitude while chameleon does.
|
||||
-- @field #boolean isAppliedOnce If set, the magic effect is applied fully on cast, rather than being continuously applied over the effect's duration. For example, chameleon is applied once, while fire damage is continuously applied for the duration.
|
||||
-- @field #boolean casterLinked If set, it is implied the magic effect links back to the caster in some way and should end immediately or never be applied if the caster dies or is not an actor.
|
||||
-- @field #boolean nonRecastable If set, this effect cannot be re-applied until it has ended. This is used by bound equipment spells.
|
||||
-- @field #string particle Identifier of the particle texture
|
||||
-- @field #string castingStatic Identifier of the vfx static used for casting
|
||||
-- @field #string castStatic Identifier of the vfx static used for casting
|
||||
-- @field #string hitStatic Identifier of the vfx static used on hit
|
||||
-- @field #string areaStatic Identifier of the vfx static used for AOE spells
|
||||
-- @field #string boltStatic Identifier of the projectile vfx static used for ranged spells
|
||||
-- @field #string castSound Identifier of the sound used for casting
|
||||
-- @field #string hitSound Identifier of the sound used on hit
|
||||
-- @field #string areaSound Identifier of the sound used for AOE spells
|
||||
-- @field #string boltSound Identifier of the projectile sound used for ranged spells
|
||||
|
||||
|
||||
---
|
||||
-- @type MagicEffectWithParams
|
||||
-- @field #MagicEffect effect @{#MagicEffect}
|
||||
-- @field #string id ID of the associated @{#MagicEffect}
|
||||
-- @field #string affectedSkill Optional skill ID
|
||||
-- @field #string affectedAttribute Optional attribute ID
|
||||
-- @field #number range
|
||||
|
@ -697,6 +716,7 @@
|
|||
-- @field #number magnitudeMin
|
||||
-- @field #number magnitudeMax
|
||||
-- @field #number duration
|
||||
-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from.
|
||||
|
||||
---
|
||||
-- @type ActiveEffect
|
||||
|
@ -708,6 +728,7 @@
|
|||
-- @field #number magnitude current magnitude of the effect. Will be set to 0 when effect is removed or expires.
|
||||
-- @field #number magnitudeBase
|
||||
-- @field #number magnitudeModifier
|
||||
-- @field #number index Index of this effect within the original list of @{#MagicEffectWithParams} of the spell/enchantment/potion this effect came from.
|
||||
|
||||
--- @{#Sound}: Sounds and Speech
|
||||
-- @field [parent=#core] #Sound sound
|
||||
|
|
|
@ -302,17 +302,59 @@
|
|||
-- end
|
||||
|
||||
---
|
||||
-- Get whether a specific spell is active on the actor.
|
||||
-- Get whether any instance of the specific spell is active on the actor.
|
||||
-- @function [parent=#ActorActiveSpells] isSpellActive
|
||||
-- @param self
|
||||
-- @param #any recordOrId record or string record ID of the active spell's source. valid records are @{openmw.core#Spell}, @{openmw.core#Enchantment}, #IngredientRecord, or #PotionRecord
|
||||
-- @param #any recordOrId A record or string record ID. Valid records are @{openmw.core#Spell}, enchanted @{#Item}, @{#IngredientRecord}, or @{#PotionRecord}.
|
||||
-- @return true if spell is active, false otherwise
|
||||
|
||||
---
|
||||
-- Remove the given spell and all its effects from the given actor's active spells.
|
||||
-- If true, the actor has not used this power in the last 24h. Will return true for powers the actor does not have.
|
||||
-- @function [parent=#ActorActiveSpells] canUsePower
|
||||
-- @param self
|
||||
-- @param #any spellOrId A @{openmw.core#Spell} or string record id.
|
||||
|
||||
---
|
||||
-- Remove an active spell based on active spell ID (see @{openmw.core#ActiveSpell.activeSpellId}). Can only be used in global scripts or on self. Can only be used to remove spells with the temporary flag set (see @{openmw.core#ActiveSpell.temporary}).
|
||||
-- @function [parent=#ActorActiveSpells] remove
|
||||
-- @param self
|
||||
-- @param #any spellOrId @{openmw.core#Spell} or string spell id
|
||||
-- @param #any id Active spell ID.
|
||||
|
||||
---
|
||||
-- Adds a new spell to the list of active spells (only in global scripts or on self).
|
||||
-- Note that this does not play any related VFX or sounds.
|
||||
-- @function [parent=#ActorActiveSpells] add
|
||||
-- @param self
|
||||
-- @param #table options A table of parameters. Must contain the following required parameters:
|
||||
--
|
||||
-- * `id` - A string record ID. Valid records are @{openmw.core#Spell}, enchanted @{#Item}, @{#IngredientRecord}, or @{#PotionRecord}.
|
||||
-- * `effects` - A list of indexes of the effects to be applied. These indexes must be in range of the record's list of @{openmw.core#MagicEffectWithParams}. Note that for Ingredients, normal ingredient consumption rules will be applied to effects.
|
||||
--
|
||||
-- And may contain the following optional parameters:
|
||||
--
|
||||
-- * `name` - The name to show in the list of active effects in the UI. Default: Name of the record identified by the id.
|
||||
-- * `ignoreResistances` - If true, resistances will be ignored. Default: false
|
||||
-- * `ignoreSpellAbsorption` - If true, spell absorption will not be applied. Default: false.
|
||||
-- * `ignoreReflect` - If true, reflects will not be applied. Default: false.
|
||||
-- * `caster` - A game object that identifies the caster. Default: nil
|
||||
-- * `item` - A game object that identifies the specific enchanted item instance used to cast the spell. Default: nil
|
||||
-- * `stackable` - If true, the spell will be able to stack. If false, existing instances of spells with the same id from the same source (where source is caster + item)
|
||||
-- * `quiet` - If true, no messages will be printed if the spell is an Ingredient and it had no effect. Always true if the target is not the player.
|
||||
-- @usage
|
||||
-- -- Adds the effect of the chameleon spell to the character
|
||||
-- Actor.activeSpells(self):add({id = 'chameleon', effects = { 0 }})
|
||||
-- @usage
|
||||
-- -- Adds the effect of a standard potion of intelligence, without consuming any potions from the character's inventory.
|
||||
-- -- Note that stackable = true to let the effect stack like a potion should.
|
||||
-- Actor.activeSpells(self):add({id = 'p_fortify_intelligence_s', effects = { 0 }, stackable = true})
|
||||
-- @usage
|
||||
-- -- Adds the negative effect of Greef twice over, and renames it to Good Greef.
|
||||
-- Actor.activeSpells(self):add({id = 'potion_comberry_brandy_01', effects = { 1, 1 }, stackable = true, name = 'Good Greef'})
|
||||
-- @usage
|
||||
-- -- Has the same effect as if the actor ate a chokeweed. With the same variable effect based on skill / random chance.
|
||||
-- Actor.activeSpells(self):add({id = 'ingred_chokeweed_01', effects = { 0 }, stackable = true, name = 'Chokeweed'})
|
||||
-- -- Same as above, but uses a different index. Note that if multiple indexes are used, the randomicity is applied separately for each effect.
|
||||
-- Actor.activeSpells(self):add({id = 'ingred_chokeweed_01', effects = { 1 }, stackable = true, name = 'Chokeweed'})
|
||||
|
||||
---
|
||||
-- Return the spells (@{#ActorSpells}) of the given actor.
|
||||
|
|
Loading…
Reference in a new issue