Merge branch 'Enchanted' into 'master'

Lua: ESM::Enchantment bindings

See merge request OpenMW/openmw!3149
revert-6246b479
psi29a 2 years ago
commit 66ca0dba38

@ -62,7 +62,7 @@ add_openmw_dir (mwlua
luamanagerimp object worldview userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings
camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings
types/types types/door types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist
types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist
worker magicbindings
)

@ -1,6 +1,7 @@
#include "magicbindings.hpp"
#include <components/esm3/activespells.hpp>
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadspel.hpp>
#include <components/lua/luastate.hpp>
@ -184,6 +185,13 @@ namespace MWLua
{ "Curse", ESM::Spell::ST_Curse },
{ "Power", ESM::Spell::ST_Power },
}));
magicApi["ENCHANTMENT_TYPE"]
= LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs<std::string_view, ESM::Enchantment::Type>({
{ "CastOnce", ESM::Enchantment::Type::CastOnce },
{ "CastOnStrike", ESM::Enchantment::Type::WhenStrikes },
{ "CastOnUse", ESM::Enchantment::Type::WhenUsed },
{ "ConstantEffect", ESM::Enchantment::Type::ConstantEffect },
}));
sol::table effect(context.mLua->sol(), sol::create);
magicApi["EFFECT_TYPE"] = LuaUtil::makeStrictReadOnly(effect);
@ -209,6 +217,25 @@ namespace MWLua
magicApi["spells"] = spellStore;
// Enchantment store
using EnchantmentStore = MWWorld::Store<ESM::Enchantment>;
const EnchantmentStore* enchantmentStore
= &MWBase::Environment::get().getWorld()->getStore().get<ESM::Enchantment>();
sol::usertype<EnchantmentStore> enchantmentStoreT = lua.new_usertype<EnchantmentStore>("ESM3_EnchantmentStore");
enchantmentStoreT[sol::meta_function::to_string] = [](const EnchantmentStore& store) {
return "ESM3_EnchantmentStore{" + std::to_string(store.getSize()) + " enchantments}";
};
enchantmentStoreT[sol::meta_function::length] = [](const EnchantmentStore& store) { return store.getSize(); };
enchantmentStoreT[sol::meta_function::index] = sol::overload(
[](const EnchantmentStore& store, size_t index) -> const ESM::Enchantment* { return store.at(index - 1); },
[](const EnchantmentStore& store, std::string_view enchantmentId) -> const ESM::Enchantment* {
return store.find(ESM::RefId::deserializeText(enchantmentId));
});
enchantmentStoreT[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>();
enchantmentStoreT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>();
magicApi["enchantments"] = enchantmentStore;
// MagicEffect store
using MagicEffectStore = MWWorld::Store<ESM::MagicEffect>;
const MagicEffectStore* magicEffectStore
@ -255,6 +282,25 @@ namespace MWLua
return res;
});
// Enchantment record
auto enchantT = lua.new_usertype<ESM::Enchantment>("ESM3_Enchantment");
enchantT[sol::meta_function::to_string] = [](const ESM::Enchantment& rec) -> std::string {
return "ESM3_Enchantment[" + rec.mId.toDebugString() + "]";
};
enchantT["id"] = sol::readonly_property([](const ESM::Enchantment& rec) { return rec.mId.serializeText(); });
enchantT["type"] = sol::readonly_property([](const ESM::Enchantment& rec) -> int { return rec.mData.mType; });
enchantT["autocalcFlag"] = sol::readonly_property(
[](const ESM::Enchantment& rec) -> bool { return !!(rec.mData.mFlags & ESM::Enchantment::Autocalc); });
enchantT["cost"] = sol::readonly_property([](const ESM::Enchantment& rec) -> int { return rec.mData.mCost; });
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;
});
// Effect params
auto effectParamsT = lua.new_usertype<ESM::ENAMstruct>("ESM3_EffectParams");
effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::ENAMstruct& params) {

@ -19,6 +19,46 @@ namespace MWLua
{
using EquipmentItem = std::variant<std::string, ObjectId>;
using Equipment = std::map<int, EquipmentItem>;
static constexpr int sAnySlot = -1;
static std::pair<MWWorld::ContainerStoreIterator, bool> findInInventory(
MWWorld::InventoryStore& store, const EquipmentItem& item, int slot = sAnySlot)
{
auto old_it = slot != sAnySlot ? store.getSlot(slot) : store.end();
MWWorld::Ptr itemPtr;
if (std::holds_alternative<ObjectId>(item))
{
itemPtr = MWBase::Environment::get().getWorldModel()->getPtr(std::get<ObjectId>(item));
if (old_it != store.end() && *old_it == itemPtr)
return { old_it, true }; // already equipped
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0
|| itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store))
{
Log(Debug::Warning) << "Object" << std::get<ObjectId>(item).toString() << " is not in inventory";
return { store.end(), false };
}
}
else
{
ESM::RefId recordId = ESM::RefId::deserializeText(std::get<std::string>(item));
if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId)
return { old_it, true }; // already equipped
itemPtr = store.search(recordId);
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0)
{
Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory";
return { store.end(), false };
}
}
// TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search.
MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr);
if (it == store.end()) // should never happen
throw std::logic_error("Item not found in container");
return { it, false };
}
static void setEquipment(const MWWorld::Ptr& actor, const Equipment& equipment)
{
@ -26,34 +66,13 @@ namespace MWLua
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
std::fill(usedSlots.begin(), usedSlots.end(), false);
static constexpr int anySlot = -1;
auto tryEquipToSlot = [&store, &usedSlots](int slot, const EquipmentItem& item) -> bool {
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end();
MWWorld::Ptr itemPtr;
if (std::holds_alternative<ObjectId>(item))
{
itemPtr = MWBase::Environment::get().getWorldModel()->getPtr(std::get<ObjectId>(item));
if (old_it != store.end() && *old_it == itemPtr)
return true; // already equipped
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0
|| itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store))
{
Log(Debug::Warning) << "Object" << std::get<ObjectId>(item).toString() << " is not in inventory";
return false;
}
}
else
{
const ESM::RefId& recordId = ESM::RefId::stringRefId(std::get<std::string>(item));
if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId)
return true; // already equipped
itemPtr = store.search(recordId);
if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0)
{
Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory";
return false;
}
}
auto [it, alreadyEquipped] = findInInventory(store, item, slot);
if (alreadyEquipped)
return true;
if (it == store.end())
return false;
MWWorld::Ptr itemPtr = *it;
auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr);
bool requestedSlotIsAllowed
@ -70,11 +89,6 @@ namespace MWLua
slot = *firstAllowed;
}
// TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search.
MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr);
if (it == store.end()) // should never happen
throw std::logic_error("Item not found in container");
store.equip(slot, it);
return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed
};
@ -94,7 +108,42 @@ namespace MWLua
}
for (const auto& [slot, item] : equipment)
if (slot >= MWWorld::InventoryStore::Slots)
tryEquipToSlot(anySlot, item);
tryEquipToSlot(sAnySlot, item);
}
static void setSelectedEnchantedItem(const MWWorld::Ptr& actor, const EquipmentItem& item)
{
MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor);
// We're not passing in a specific slot, so ignore the already equipped return value
auto [it, _] = findInInventory(store, item, sAnySlot);
if (it == store.end())
return;
MWWorld::Ptr itemPtr = *it;
// Equip the item if applicable
auto slots = itemPtr.getClass().getEquipmentSlots(itemPtr);
if (!slots.first.empty())
{
bool alreadyEquipped = false;
for (auto slot : slots.first)
{
if (store.getSlot(slot) == it)
alreadyEquipped = true;
}
if (!alreadyEquipped)
{
MWBase::Environment::get().getWindowManager()->useItem(itemPtr);
// make sure that item was successfully equipped
if (!store.isEquipped(itemPtr))
return;
}
}
store.setSelectedEnchantItem(it);
// to reset WindowManager::mSelectedSpell immediately
MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(*it);
}
void addActorBindings(sol::table actor, const Context& context)
@ -173,8 +222,37 @@ namespace MWLua
stats.setDrawState(newDrawState);
};
// TODO
// getSelectedEnchantedItem, setSelectedEnchantedItem
actor["getSelectedEnchantedItem"] = [](sol::this_state lua, const Object& o) -> sol::object {
const MWWorld::Ptr& ptr = o.ptr();
if (!ptr.getClass().hasInventoryStore(ptr))
return sol::nil;
MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr);
auto it = store.getSelectedEnchantItem();
if (it == store.end())
return sol::nil;
MWBase::Environment::get().getWorldModel()->registerPtr(*it);
if (dynamic_cast<const GObject*>(&o))
return sol::make_object(lua, GObject(*it));
else
return sol::make_object(lua, LObject(*it));
};
actor["setSelectedEnchantedItem"] = [context](const SelfObject& obj, const sol::object& item) {
const MWWorld::Ptr& ptr = obj.ptr();
if (!ptr.getClass().hasInventoryStore(ptr))
return;
EquipmentItem ei;
if (item.is<Object>())
{
ei = LuaUtil::cast<Object>(item).id();
}
else
{
ei = LuaUtil::cast<std::string>(item);
}
context.mLuaManager->addAction([obj = Object(ptr), ei = ei] { setSelectedEnchantedItem(obj.ptr(), ei); },
"setSelectedEnchantedItemAction");
};
actor["canMove"] = [](const Object& o) {
const MWWorld::Class& cls = o.ptr().getClass();

@ -0,0 +1,15 @@
#include "../luabindings.hpp"
#include "../worldview.hpp"
#include "types.hpp"
namespace MWLua
{
void addItemBindings(sol::table item)
{
item["getEnchantmentCharge"]
= [](const Object& object) { return object.ptr().getCellRef().getEnchantmentCharge(); };
item["setEnchantmentCharge"]
= [](const GObject& object, float charge) { object.ptr().getCellRef().setEnchantmentCharge(charge); };
}
}

@ -183,9 +183,9 @@ namespace MWLua
addActorBindings(
addType(ObjectTypeName::Actor, { ESM::REC_INTERNAL_PLAYER, ESM::REC_CREA, ESM::REC_NPC_ }), context);
addType(ObjectTypeName::Item,
addItemBindings(addType(ObjectTypeName::Item,
{ ESM::REC_ARMO, ESM::REC_BOOK, ESM::REC_CLOT, ESM::REC_INGR, ESM::REC_LIGH, ESM::REC_MISC, ESM::REC_ALCH,
ESM::REC_WEAP, ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA });
ESM::REC_WEAP, ESM::REC_APPA, ESM::REC_LOCK, ESM::REC_PROB, ESM::REC_REPA }));
addLockableBindings(
addType(ObjectTypeName::Lockable, { ESM::REC_CONT, ESM::REC_DOOR, ESM::REC_CONT4, ESM::REC_DOOR4 }));

@ -47,6 +47,7 @@ namespace MWLua
void addBookBindings(sol::table book, const Context& context);
void addContainerBindings(sol::table container, const Context& context);
void addDoorBindings(sol::table door, const Context& context);
void addItemBindings(sol::table item);
void addActorBindings(sol::table actor, const Context& context);
void addWeaponBindings(sol::table weapon, const Context& context);
void addNpcBindings(sol::table npc, const Context& context);

@ -317,6 +317,41 @@
-- local weapons = cell:getAll(types.Weapon)
--- Possible @{#EnchantmentType} values
-- @field [parent=#Magic] #EnchantmentType ENCHANTMENT_TYPE
--- `core.magic.ENCHANTMENT_TYPE`
-- @type EnchantmentType
-- @field #number CastOnce Enchantment can be cast once, destroying the enchanted item.
-- @field #number CastOnStrike Enchantment is cast on strike, if there is enough charge.
-- @field #number CastOnUse Enchantment is cast when used, if there is enough charge.
-- @field #number ConstantEffect Enchantment is always active when equipped.
---
-- @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 #number cost
-- @field #number charge Charge capacity. Should not be confused with current charge.
-- @field #list<#MagicEffectWithParams> effects The effects (@{#MagicEffectWithParams}) of the enchantment
-- @usage -- Getting the enchantment of an arbitrary item, if it has one
-- local function getRecord(item)
-- if item.type and item.type.record then
-- return item.type.record(item)
-- end
-- return nil
-- end
-- local function getEnchantment(item)
-- local record = getRecord(item)
-- if record and record.enchant then
-- return core.magic.enchantments[record.enchant]
-- end
-- return nil
-- end
---
-- Inventory of a player/NPC or a content of a container.
-- @type Inventory
@ -583,7 +618,6 @@
-- @field #number SummonCreature04 "summoncreature04"
-- @field #number SummonCreature05 "summoncreature05"
--- Possible @{#SpellType} values
-- @field [parent=#Magic] #SpellType SPELL_TYPE
@ -617,6 +651,17 @@
-- end
-- end
--- List of all @{#Enchantment}s.
-- @field [parent=#Magic] #list<#Enchantment> enchantments
-- @usage local enchantment = core.magic.enchantments['marara's boon'] -- get by id
-- @usage local enchantment = core.magic.enchantments[1] -- get by index
-- @usage -- Print all enchantments with constant effect
-- for _, ench in pairs(core.magic.enchantments) do
-- if ench.type == core.magic.ENCHANTMENT_TYPE.ConstantEffect then
-- print(ench.id)
-- end
-- end
---
-- @type Spell
-- @field #string id Spell id

@ -161,6 +161,18 @@
-- @param openmw.core#GameObject actor
-- @param openmw.core#Spell spell Spell (can be nil)
---
-- Get currently selected enchanted item
-- @function [parent=#Actor] getSelectedEnchantedItem
-- @param openmw.core#GameObject actor
-- @return openmw.core#GameObject, nil enchanted item or nil
---
-- Set currently selected enchanted item, equipping it if applicable
-- @function [parent=#Actor] setSelectedEnchantedItem
-- @param openmw.core#GameObject actor
-- @param openmw.core#GameObject item enchanted item
---
-- Return the active magic effects (@{#ActorActiveEffects}) currently affecting the given actor.
-- @function [parent=#Actor] activeEffects
@ -592,7 +604,17 @@
-- @param openmw.core#GameObject object
-- @return #boolean
---
-- Get this item's current enchantment charge.
-- @function [parent=#Item] getEnchantmentCharge
-- @param #Item item
-- @return #number The charge remaining. -1 if the enchantment has never been used, implying the charge is full. Unenchanted items will always return a value of -1.
---
-- Set this item's enchantment charge.
-- @function [parent=#Item] setEnchantmentCharge
-- @param #Item item
-- @param #number charge
--- @{#Creature} functions
-- @field [parent=#types] #Creature Creature

Loading…
Cancel
Save