1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-21 06:53:53 +00:00

Merge branch 'Enchanted' into 'master'

Lua: ESM::Enchantment bindings

See merge request OpenMW/openmw!3149
This commit is contained in:
psi29a 2023-06-18 20:57:21 +00:00
commit 66ca0dba38
8 changed files with 246 additions and 39 deletions

View file

@ -62,7 +62,7 @@ add_openmw_dir (mwlua
luamanagerimp object worldview userdataserializer luaevents engineevents objectvariant luamanagerimp object worldview userdataserializer luaevents engineevents objectvariant
context globalscripts localscripts playerscripts luabindings objectbindings cellbindings context globalscripts localscripts playerscripts luabindings objectbindings cellbindings
camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings 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 worker magicbindings
) )

View file

@ -1,6 +1,7 @@
#include "magicbindings.hpp" #include "magicbindings.hpp"
#include <components/esm3/activespells.hpp> #include <components/esm3/activespells.hpp>
#include <components/esm3/loadench.hpp>
#include <components/esm3/loadmgef.hpp> #include <components/esm3/loadmgef.hpp>
#include <components/esm3/loadspel.hpp> #include <components/esm3/loadspel.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
@ -184,6 +185,13 @@ namespace MWLua
{ "Curse", ESM::Spell::ST_Curse }, { "Curse", ESM::Spell::ST_Curse },
{ "Power", ESM::Spell::ST_Power }, { "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); sol::table effect(context.mLua->sol(), sol::create);
magicApi["EFFECT_TYPE"] = LuaUtil::makeStrictReadOnly(effect); magicApi["EFFECT_TYPE"] = LuaUtil::makeStrictReadOnly(effect);
@ -209,6 +217,25 @@ namespace MWLua
magicApi["spells"] = spellStore; 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 // MagicEffect store
using MagicEffectStore = MWWorld::Store<ESM::MagicEffect>; using MagicEffectStore = MWWorld::Store<ESM::MagicEffect>;
const MagicEffectStore* magicEffectStore const MagicEffectStore* magicEffectStore
@ -255,6 +282,25 @@ namespace MWLua
return res; 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 // Effect params
auto effectParamsT = lua.new_usertype<ESM::ENAMstruct>("ESM3_EffectParams"); auto effectParamsT = lua.new_usertype<ESM::ENAMstruct>("ESM3_EffectParams");
effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::ENAMstruct& params) { effectParamsT[sol::meta_function::to_string] = [magicEffectStore](const ESM::ENAMstruct& params) {

View file

@ -19,6 +19,46 @@ namespace MWLua
{ {
using EquipmentItem = std::variant<std::string, ObjectId>; using EquipmentItem = std::variant<std::string, ObjectId>;
using Equipment = std::map<int, EquipmentItem>; 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) static void setEquipment(const MWWorld::Ptr& actor, const Equipment& equipment)
{ {
@ -26,34 +66,13 @@ namespace MWLua
std::array<bool, MWWorld::InventoryStore::Slots> usedSlots; std::array<bool, MWWorld::InventoryStore::Slots> usedSlots;
std::fill(usedSlots.begin(), usedSlots.end(), false); std::fill(usedSlots.begin(), usedSlots.end(), false);
static constexpr int anySlot = -1;
auto tryEquipToSlot = [&store, &usedSlots](int slot, const EquipmentItem& item) -> bool { auto tryEquipToSlot = [&store, &usedSlots](int slot, const EquipmentItem& item) -> bool {
auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); auto [it, alreadyEquipped] = findInInventory(store, item, slot);
MWWorld::Ptr itemPtr; if (alreadyEquipped)
if (std::holds_alternative<ObjectId>(item)) return true;
{ if (it == store.end())
itemPtr = MWBase::Environment::get().getWorldModel()->getPtr(std::get<ObjectId>(item)); return false;
if (old_it != store.end() && *old_it == itemPtr) MWWorld::Ptr itemPtr = *it;
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 [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr);
bool requestedSlotIsAllowed bool requestedSlotIsAllowed
@ -70,11 +89,6 @@ namespace MWLua
slot = *firstAllowed; 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); store.equip(slot, it);
return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed 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) for (const auto& [slot, item] : equipment)
if (slot >= MWWorld::InventoryStore::Slots) 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) void addActorBindings(sol::table actor, const Context& context)
@ -173,8 +222,37 @@ namespace MWLua
stats.setDrawState(newDrawState); stats.setDrawState(newDrawState);
}; };
// TODO actor["getSelectedEnchantedItem"] = [](sol::this_state lua, const Object& o) -> sol::object {
// getSelectedEnchantedItem, setSelectedEnchantedItem 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) { actor["canMove"] = [](const Object& o) {
const MWWorld::Class& cls = o.ptr().getClass(); const MWWorld::Class& cls = o.ptr().getClass();

View file

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

View file

@ -183,9 +183,9 @@ namespace MWLua
addActorBindings( addActorBindings(
addType(ObjectTypeName::Actor, { ESM::REC_INTERNAL_PLAYER, ESM::REC_CREA, ESM::REC_NPC_ }), context); 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_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( addLockableBindings(
addType(ObjectTypeName::Lockable, { ESM::REC_CONT, ESM::REC_DOOR, ESM::REC_CONT4, ESM::REC_DOOR4 })); addType(ObjectTypeName::Lockable, { ESM::REC_CONT, ESM::REC_DOOR, ESM::REC_CONT4, ESM::REC_DOOR4 }));

View file

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

View file

@ -317,6 +317,41 @@
-- local weapons = cell:getAll(types.Weapon) -- 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. -- Inventory of a player/NPC or a content of a container.
-- @type Inventory -- @type Inventory
@ -583,7 +618,6 @@
-- @field #number SummonCreature04 "summoncreature04" -- @field #number SummonCreature04 "summoncreature04"
-- @field #number SummonCreature05 "summoncreature05" -- @field #number SummonCreature05 "summoncreature05"
--- Possible @{#SpellType} values --- Possible @{#SpellType} values
-- @field [parent=#Magic] #SpellType SPELL_TYPE -- @field [parent=#Magic] #SpellType SPELL_TYPE
@ -617,6 +651,17 @@
-- end -- end
-- 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 -- @type Spell
-- @field #string id Spell id -- @field #string id Spell id

View file

@ -161,6 +161,18 @@
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param openmw.core#Spell spell Spell (can be nil) -- @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. -- Return the active magic effects (@{#ActorActiveEffects}) currently affecting the given actor.
-- @function [parent=#Actor] activeEffects -- @function [parent=#Actor] activeEffects
@ -592,7 +604,17 @@
-- @param openmw.core#GameObject object -- @param openmw.core#GameObject object
-- @return #boolean -- @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 --- @{#Creature} functions
-- @field [parent=#types] #Creature Creature -- @field [parent=#types] #Creature Creature