Merge branch 'createenchantrecordsinlua' into 'master'
Some checks are pending
Build and test / Windows (2019) (push) Blocked by required conditions
Build and test / Windows (2022) (push) Blocked by required conditions
Build and test / Ubuntu (push) Waiting to run
Build and test / MacOS (push) Waiting to run
Build and test / Read .env file and expose it as output (push) Waiting to run

Draft: Add Lua bindings to effects, enchant, spells (#8342)

See merge request OpenMW/openmw!4533
This commit is contained in:
alex2603@live.ca 2025-04-28 01:25:56 +00:00
commit 3b31fbcd9c
6 changed files with 197 additions and 2 deletions

View file

@ -137,6 +137,94 @@ namespace MWLua
using ActorActiveSpells = ActorStore<MWMechanics::ActiveSpells>;
}
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<ESM::Enchantment>(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<int>();
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<ESM::Spell>(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<int>();
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<std::string_view>());
if (effectId == -1)
throw std::runtime_error("Invalid effect id provided: " + effectTable["id"].get<std::string>());
effect.mData.mEffectID = effectId;
const ESM::MagicEffect* const magicEffect = MWBase::Environment::get()
.getWorld() -> getStore()
.get<ESM::MagicEffect>()
.find(effectId);
if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill)
{
if (effectTable["affectedSkill"] == sol::nil)
throw std::runtime_error("Expected affectedSkill for " + effectTable["id"].get<std::string>());
int skillId = ESM::Skill::refIdToIndex(
ESM::RefId::deserializeText(effectTable["affectedSkill"].get<std::string_view>()));
if (skillId == -1)
throw std::runtime_error(
"Invalid affectedSkill provided: " + effectTable["affectedSkill"].get<std::string>());
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<std::string>());
int attributeId = ESM::Attribute::refIdToIndex(
ESM::RefId::deserializeText(effectTable["affectedAttribute"].get<std::string_view>()));
if (attributeId == -1)
throw std::runtime_error(
"Invalid affectedAttribute provided: " + effectTable["affectedAttribute"].get<std::string>());
effect.mData.mAttribute = attributeId;
}
if (effectTable["range"] != sol::nil)
{
effect.mData.mRange = effectTable["range"].get<int32_t>();
if ((effect.mData.mRange == ESM::RT_Self) and !(magicEffect->mData.mFlags & ESM::MagicEffect::CastSelf))
throw std::runtime_error(effectTable["id"].get<std::string>() + " 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<std::string>() + " 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<std::string>() + " 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<int32_t>();
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration))
if (effectTable["duration"] != sol::nil)
effect.mData.mDuration = effectTable["duration"].get<int32_t>();
if (!(magicEffect->mData.mFlags & ESM::MagicEffect::NoMagnitude))
{
if (effectTable["magnitudeMin"] != sol::nil)
effect.mData.mMagnMin = effectTable["magnitudeMin"].get<int32_t>();
if (effectTable["magnitudeMax"] != sol::nil)
effect.mData.mMagnMax = effectTable["magnitudeMax"].get<int32_t>();
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<ESM::Spell>(spells, context);
spells["createRecordDraft"] = tableToSpell;
magicApi["spells"] = LuaUtil::makeReadOnly(spells);
// Enchantment store
sol::table enchantments(lua, sol::create);
addRecordFunctionBinding<ESM::Enchantment>(enchantments, context);
enchantments["createRecordDraft"] = tableToEnchantment;
magicApi["enchantments"] = LuaUtil::makeReadOnly(enchantments);
// MagicEffect store

View file

@ -3,12 +3,15 @@
#include <sol/forward.hpp>
#include <components/esm3/effectlist.hpp>
#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

View file

@ -2,7 +2,10 @@
#include "modelproperty.hpp"
#include "../magicbindings.hpp"
#include <components/esm3/loadalch.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/lua/luastate.hpp>
#include <components/lua/util.hpp>
#include <components/misc/resourcehelpers.hpp>
@ -50,8 +53,17 @@ namespace
potion.mEffects.mList.resize(numEffects);
for (size_t i = 0; i < numEffects; ++i)
{
potion.mEffects.mList[i] = LuaUtil::cast<ESM::IndexedENAMstruct>(effectsTable[LuaUtil::toLuaIndex(i)]);
sol::object element = effectsTable[LuaUtil::toLuaIndex(i)];
if (element.is<ESM::IndexedENAMstruct>()) // It can be casted (extracted from another magic thing)
{
potion.mEffects.mList[i]
= LuaUtil::cast<ESM::IndexedENAMstruct>(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;

View file

@ -9,6 +9,8 @@
#include <components/esm3/loadmisc.hpp>
#include <components/esm3/loadskil.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/esm3/loadspel.hpp>
#include <components/esm3/loadench.hpp>
#include <components/lua/luastate.hpp>
#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) {

View file

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

View file

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