diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 19dada74a7..c03304ff39 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -137,6 +137,94 @@ namespace MWLua using ActorActiveSpells = ActorStore; } +namespace +{ + // Populates an enchantment struct from a Lua table. + ESM::Enchantment tableToEnchantment(const sol::table& rec) + { + ESM::Enchantment enchantment; + if (rec["template"] != sol::nil) + enchantment = LuaUtil::cast(rec["template"]); + else + enchantment.blank(); + if (rec["autocalcFlag"] != sol::nil) + rec["autocalcFlag"] + ? (enchantment.mData.mFlags | ESM::Enchantment::Autocalc) + : (enchantment.mData.mFlags & ~ESM::Enchantment::Autocalc); + if (rec["charge"] != sol::nil) + enchantment.mData.mCharge = rec["charge"]; + if (rec["cost"] != sol::nil) + enchantment.mData.mCost = rec["cost"]; + if (rec["effects"] != sol::nil) + { + sol::table effectsTable = rec["effects"]; + size_t numEffects = effectsTable.size(); + enchantment.mEffects.mList.resize(numEffects); + for (size_t i = 0; i < numEffects; ++i) + { + enchantment.mEffects.mList[i] = MWLua::tableToEffectParam(effectsTable[LuaUtil::toLuaIndex(i)]); + } + enchantment.mEffects.updateIndexes(); + } + if (rec["type"] != sol::nil) + { + int enchType = rec["type"].get(); + if (enchType >= 0 && enchType <= ESM::Enchantment::Type::ConstantEffect) + enchantment.mData.mType = enchType; + else + throw std::runtime_error("Invalid Enchantment Type provided: " + std::to_string(enchType)); + } + + return enchantment; + } + + // Populates a spell struct from a Lua table. + ESM::Spell tableToSpell(const sol::table& rec) + { + ESM::Spell spell; + if (rec["template"] != sol::nil) + spell = LuaUtil::cast(rec["template"]); + else + spell.blank(); + if (rec["alwaysSucceedFlag"] != sol::nil) + rec["alwaysSucceedFlag"] + ? (spell.mData.mFlags | ESM::Spell::F_Always) + : (spell.mData.mFlags & ~ESM::Spell::F_Always); + if (rec["autocalcFlag"] != sol::nil) + rec["autocalcFlag"] + ? (spell.mData.mFlags | ESM::Spell::F_Autocalc) + : (spell.mData.mFlags & ~ESM::Spell::F_Autocalc); + if (rec["starterSpellFlag"] != sol::nil) + rec["starterSpellFlag"] + ? (spell.mData.mFlags | ESM::Spell::F_PCStart) + : (spell.mData.mFlags & ~ESM::Spell::F_PCStart); + if (rec["cost"] != sol::nil) + spell.mData.mCost = rec["cost"]; + if (rec["name"] != sol::nil) + spell.mName = rec["name"]; + if (rec["effects"] != sol::nil) + { + sol::table effectsTable = rec["effects"]; + size_t numEffects = effectsTable.size(); + spell.mEffects.mList.resize(numEffects); + for (size_t i = 0; i < numEffects; ++i) + { + spell.mEffects.mList[i] = MWLua::tableToEffectParam(effectsTable[LuaUtil::toLuaIndex(i)]); + } + spell.mEffects.updateIndexes(); + } + if (rec["type"] != sol::nil) + { + int spellType = rec["type"].get(); + if (spellType >= 0 && spellType <= ESM::Spell::ST_Power) + spell.mData.mType = spellType; + else + throw std::runtime_error("Invalid Spell Type provided: " + std::to_string(spellType)); + } + return spell; + } +} + namespace sol { template <> @@ -212,6 +300,71 @@ namespace MWLua return res; } + // Creates the IndexedENAMstruct based on the table, validating provided data and ignoring unrequired fields + ESM::IndexedENAMstruct tableToEffectParam(const sol::table& effectTable) + { + ESM::IndexedENAMstruct effect; + effect.blank(); + if (effectTable["id"] == sol::nil) + throw std::runtime_error("Spell effect id expected"); + + int effectId = ESM::MagicEffect::indexNameToIndex(effectTable["id"].get()); + if (effectId == -1) + throw std::runtime_error("Invalid effect id provided: " + effectTable["id"].get()); + effect.mData.mEffectID = effectId; + const ESM::MagicEffect* const magicEffect = MWBase::Environment::get() + .getWorld() -> getStore() + .get() + .find(effectId); + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) + { + if (effectTable["affectedSkill"] == sol::nil) + throw std::runtime_error("Expected affectedSkill for " + effectTable["id"].get()); + int skillId = ESM::Skill::refIdToIndex( + ESM::RefId::deserializeText(effectTable["affectedSkill"].get())); + if (skillId == -1) + throw std::runtime_error( + "Invalid affectedSkill provided: " + effectTable["affectedSkill"].get()); + effect.mData.mSkill = skillId; + } + if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) + { + if (effectTable["affectedAttribute"] == sol::nil) + throw std::runtime_error("Expected affectedAttribute for " + effectTable["id"].get()); + int attributeId = ESM::Attribute::refIdToIndex( + ESM::RefId::deserializeText(effectTable["affectedAttribute"].get())); + if (attributeId == -1) + throw std::runtime_error( + "Invalid affectedAttribute provided: " + effectTable["affectedAttribute"].get()); + effect.mData.mAttribute = attributeId; + } + if (effectTable["range"] != sol::nil) + { + effect.mData.mRange = effectTable["range"].get(); + if ((effect.mData.mRange == ESM::RT_Self) and !(magicEffect->mData.mFlags & ESM::MagicEffect::CastSelf)) + throw std::runtime_error(effectTable["id"].get() + " cannot be casted on self"); + if ((effect.mData.mRange == ESM::RT_Touch) and !(magicEffect->mData.mFlags & ESM::MagicEffect::CastTouch)) + throw std::runtime_error(effectTable["id"].get() + " cannot be casted on touch"); + if ((effect.mData.mRange == ESM::RT_Target) and !(magicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)) + throw std::runtime_error(effectTable["id"].get() + " cannot be casted on target"); + } + if (effectTable["area"] != sol::nil) // Should area be only available to Touch and or Target ? + effect.mData.mArea = effectTable["area"].get(); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + if (effectTable["duration"] != sol::nil) + effect.mData.mDuration = effectTable["duration"].get(); + if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude)) + { + if (effectTable["magnitudeMin"] != sol::nil) + effect.mData.mMagnMin = effectTable["magnitudeMin"].get(); + if (effectTable["magnitudeMax"] != sol::nil) + effect.mData.mMagnMax = effectTable["magnitudeMax"].get(); + if (effect.mData.mMagnMax < effect.mData.mMagnMin) + throw std::runtime_error("magnitudeMax cannot be lower than magnitudeMin"); + } + return effect; + } + sol::table initCoreMagicBindings(const Context& context) { sol::state_view lua = context.sol(); @@ -253,11 +406,13 @@ namespace MWLua // Spell store sol::table spells(lua, sol::create); addRecordFunctionBinding(spells, context); + spells["createRecordDraft"] = tableToSpell; magicApi["spells"] = LuaUtil::makeReadOnly(spells); // Enchantment store sol::table enchantments(lua, sol::create); addRecordFunctionBinding(enchantments, context); + enchantments["createRecordDraft"] = tableToEnchantment; magicApi["enchantments"] = LuaUtil::makeReadOnly(enchantments); // MagicEffect store diff --git a/apps/openmw/mwlua/magicbindings.hpp b/apps/openmw/mwlua/magicbindings.hpp index 047bd2e3d9..7deb705c1a 100644 --- a/apps/openmw/mwlua/magicbindings.hpp +++ b/apps/openmw/mwlua/magicbindings.hpp @@ -3,12 +3,15 @@ #include +#include + #include "context.hpp" namespace MWLua { sol::table initCoreMagicBindings(const Context& context); void addActorMagicBindings(sol::table& actor, const Context& context); + ESM::IndexedENAMstruct tableToEffectParam(const sol::table& effectTable); } #endif // MWLUA_MAGICBINDINGS_H diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index 4d04c8bb13..f7dba86fec 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -2,7 +2,10 @@ #include "modelproperty.hpp" +#include "../magicbindings.hpp" + #include +#include #include #include #include @@ -50,8 +53,17 @@ namespace potion.mEffects.mList.resize(numEffects); for (size_t i = 0; i < numEffects; ++i) { - potion.mEffects.mList[i] = LuaUtil::cast(effectsTable[LuaUtil::toLuaIndex(i)]); - } + sol::object element = effectsTable[LuaUtil::toLuaIndex(i)]; + if (element.is()) // It can be casted (extracted from another magic thing) + { + potion.mEffects.mList[i] + = LuaUtil::cast(effectsTable[LuaUtil::toLuaIndex(i)]); + } + else // Recreates from a table + { + potion.mEffects.mList[i] = MWLua::tableToEffectParam(effectsTable[LuaUtil::toLuaIndex(i)]); + } + } // Updates the index in the IndexedENAMstruct potion.mEffects.updateIndexes(); } return potion; diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index ac7bd307cf..af50f79758 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include "../mwbase/environment.hpp" @@ -192,6 +194,14 @@ namespace MWLua [lua = context.mLua](const ESM::Light& light) -> const ESM::Light* { checkGameInitialized(lua); return MWBase::Environment::get().getESMStore()->insert(light); + }, + [lua = context.mLua](const ESM::Enchantment& ench) -> const ESM::Enchantment* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(ench); + }, + [lua = context.mLua](const ESM::Spell& spell) -> const ESM::Spell* { + checkGameInitialized(lua); + return MWBase::Environment::get().getESMStore()->insert(spell); }); api["_runStandardActivationAction"] = [context](const GObject& object, const GObject& actor) { diff --git a/components/esm3/effectlist.cpp b/components/esm3/effectlist.cpp index 5a1fa91f28..6ca6a66d4c 100644 --- a/components/esm3/effectlist.cpp +++ b/components/esm3/effectlist.cpp @@ -58,4 +58,18 @@ namespace ESM || mData.mMagnMax != rhs.mData.mMagnMax || mData.mDuration != rhs.mData.mDuration; } + // Initialize a default, blank effect + void IndexedENAMstruct::blank() + { + mData.mEffectID = 0; + mData.mArea = 0; + mData.mRange = 0; + mData.mSkill = -1; + mData.mAttribute = -1; + mData.mMagnMax = 0; + mData.mMagnMin = 0; + mData.mDuration = 0; + mIndex = 0; + } + } // end namespace diff --git a/components/esm3/effectlist.hpp b/components/esm3/effectlist.hpp index 8eb347d6c8..3db3bebeb6 100644 --- a/components/esm3/effectlist.hpp +++ b/components/esm3/effectlist.hpp @@ -30,6 +30,7 @@ namespace ESM { bool operator!=(const IndexedENAMstruct& rhs) const; bool operator==(const IndexedENAMstruct& rhs) const { return !(this->operator!=(rhs)); } + void blank(); ENAMstruct mData; uint32_t mIndex; };