diff --git a/apps/openmw/mwlua/itemdata.cpp b/apps/openmw/mwlua/itemdata.cpp index 3e2b755af8..9ae9f57b34 100644 --- a/apps/openmw/mwlua/itemdata.cpp +++ b/apps/openmw/mwlua/itemdata.cpp @@ -1,22 +1,28 @@ #include "itemdata.hpp" +#include +#include + #include "context.hpp" #include "luamanagerimp.hpp" #include "objectvariant.hpp" +#include "../mwbase/environment.hpp" +#include "../mwmechanics/spellutil.hpp" + #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" namespace { using SelfObject = MWLua::SelfObject; using Index = const SelfObject::CachedStat::Index&; - constexpr std::array properties = { "condition", /*"enchantmentCharge", "soul", "owner", etc..*/ }; + constexpr std::array properties = { "condition", "enchantmentCharge", "soul" }; - void invalidPropErr(std::string_view prop, const MWWorld::Ptr& ptr) + void valueErr(std::string_view prop, std::string type) { - throw std::runtime_error("'" + std::string(prop) + "'" + " property does not exist for item " - + std::string(ptr.getClass().getName(ptr)) + "(" + std::string(ptr.getTypeDescription()) + ")"); + throw std::logic_error("'" + std::string(prop) + "'" + " received invalid value type (" + type + ")"); } } @@ -54,26 +60,56 @@ namespace MWLua if (it != self->mStatsCache.end()) return it->second; } - return sol::make_object(context.mLua->sol(), getValue(context, prop)); + return sol::make_object(context.mLua->sol(), getValue(context, prop, mObject.ptr())); } void set(const Context& context, std::string_view prop, const sol::object& value) const { - SelfObject* obj = mObject.asSelfObject(); - addStatUpdateAction(context.mLuaManager, *obj); - obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value; + if (mObject.isGObject()) + setValue({}, prop, mObject.ptr(), value); + else if (mObject.isSelfObject()) + { + SelfObject* obj = mObject.asSelfObject(); + addStatUpdateAction(context.mLuaManager, *obj); + obj->mStatsCache[SelfObject::CachedStat{ &ItemData::setValue, std::monostate{}, prop }] = value; + } + else + throw std::runtime_error("Only global or self scripts can set the value"); } - sol::object getValue(const Context& context, std::string_view prop) const + static sol::object getValue(const Context& context, std::string_view prop, const MWWorld::Ptr& ptr) { if (prop == "condition") { - MWWorld::Ptr o = mObject.ptr(); - if (o.mRef->getType() == ESM::REC_LIGH) - return sol::make_object(context.mLua->sol(), o.getClass().getRemainingUsageTime(o)); - else if (o.getClass().hasItemHealth(o)) - return sol::make_object( - context.mLua->sol(), o.getClass().getItemHealth(o) + o.getCellRef().getChargeIntRemainder()); + if (ptr.mRef->getType() == ESM::REC_LIGH) + return sol::make_object(context.mLua->sol(), ptr.getClass().getRemainingUsageTime(ptr)); + else if (ptr.getClass().hasItemHealth(ptr)) + return sol::make_object(context.mLua->sol(), + ptr.getClass().getItemHealth(ptr) + ptr.getCellRef().getChargeIntRemainder()); + } + else if (prop == "enchantmentCharge") + { + const ESM::RefId& enchantmentName = ptr.getClass().getEnchantment(ptr); + + if (enchantmentName.empty()) + return sol::lua_nil; + + float charge = ptr.getCellRef().getEnchantmentCharge(); + const auto& store = MWBase::Environment::get().getESMStore(); + const auto* enchantment = store->get().find(enchantmentName); + + if (charge == -1) // return the full charge + return sol::make_object(context.mLua->sol(), MWMechanics::getEnchantmentCharge(*enchantment)); + + return sol::make_object(context.mLua->sol(), charge); + } + else if (prop == "soul") + { + ESM::RefId soul = ptr.getCellRef().getSoul(); + if (soul.empty()) + return sol::lua_nil; + + return sol::make_object(context.mLua->sol(), soul.serializeText()); } return sol::lua_nil; @@ -83,17 +119,48 @@ namespace MWLua { if (prop == "condition") { - float cond = LuaUtil::cast(value); - if (ptr.mRef->getType() == ESM::REC_LIGH) - ptr.getClass().setRemainingUsageTime(ptr, cond); - else if (ptr.getClass().hasItemHealth(ptr)) + if (value.get_type() == sol::type::number) { - // if the value set is less than 0, chargeInt and chargeIntRemainder is set to 0 - ptr.getCellRef().setChargeIntRemainder(std::max(0.f, std::modf(cond, &cond))); - ptr.getCellRef().setCharge(std::max(0.f, cond)); + float cond = LuaUtil::cast(value); + if (ptr.mRef->getType() == ESM::REC_LIGH) + ptr.getClass().setRemainingUsageTime(ptr, cond); + else if (ptr.getClass().hasItemHealth(ptr)) + { + // if the value set is less than 0, chargeInt and chargeIntRemainder is set to 0 + ptr.getCellRef().setChargeIntRemainder(std::max(0.f, std::modf(cond, &cond))); + ptr.getCellRef().setCharge(std::max(0.f, cond)); + } } else - invalidPropErr(prop, ptr); + valueErr(prop, sol::type_name(value.lua_state(), value.get_type())); + } + else if (prop == "enchantmentCharge") + { + if (value.get_type() == sol::type::lua_nil) + ptr.getCellRef().setEnchantmentCharge(-1); + else if (value.get_type() == sol::type::number) + ptr.getCellRef().setEnchantmentCharge(std::max(0.0f, LuaUtil::cast(value))); + else + valueErr(prop, sol::type_name(value.lua_state(), value.get_type())); + } + else if (prop == "soul") + { + if (value.get_type() == sol::type::lua_nil) + ptr.getCellRef().setSoul(ESM::RefId{}); + else if (value.get_type() == sol::type::string) + { + std::string_view souldId = LuaUtil::cast(value); + ESM::RefId creature = ESM::RefId::deserializeText(souldId); + const auto& store = *MWBase::Environment::get().getESMStore(); + + // TODO: Add Support for NPC Souls + if (store.get().search(creature)) + ptr.getCellRef().setSoul(creature); + else + throw std::runtime_error("Cannot use non-existent creature as a soul: " + std::string(souldId)); + } + else + valueErr(prop, sol::type_name(value.lua_state(), value.get_type())); } } }; diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index 8f05ce8e93..ba995a9808 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -11,6 +11,7 @@ namespace MWLua { void addItemBindings(sol::table item, const Context& context) { + // Deprecated. Moved to itemData; should be removed later item["getEnchantmentCharge"] = [](const Object& object) -> sol::optional { float charge = object.ptr().getCellRef().getEnchantmentCharge(); if (charge == -1) diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp index f83864477f..8fc9406cfd 100644 --- a/apps/openmw/mwlua/types/misc.cpp +++ b/apps/openmw/mwlua/types/misc.cpp @@ -55,6 +55,7 @@ namespace MWLua addRecordFunctionBinding(miscellaneous, context); miscellaneous["createRecordDraft"] = tableToMisc; + // Deprecated. Moved to itemData; should be removed later miscellaneous["setSoul"] = [](const GObject& object, std::string_view soulId) { ESM::RefId creature = ESM::RefId::deserializeText(soulId); const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); @@ -75,6 +76,7 @@ namespace MWLua return soul.serializeText(); }; miscellaneous["soul"] = miscellaneous["getSoul"]; // for compatibility; should be removed later + sol::usertype record = context.mLua->sol().new_usertype("ESM3_Miscellaneous"); record[sol::meta_function::to_string] diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 1e0b486524..f9aeaf97af 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -750,7 +750,7 @@ -- @return #boolean --- --- Get this item's current enchantment charge. +-- (DEPRECATED, use itemData(item).enchantmentCharge) Get this item's current enchantment charge. -- @function [parent=#Item] getEnchantmentCharge -- @param openmw.core#GameObject item -- @return #number The charge remaining. `nil` if the enchantment has never been used, implying the charge is full. Unenchanted items will always return a value of `nil`. @@ -763,7 +763,7 @@ -- @return #boolean --- --- Set this item's enchantment charge. +-- (DEPRECATED, use itemData(item).enchantmentCharge) Set this item's enchantment charge. -- @function [parent=#Item] setEnchantmentCharge -- @param openmw.core#GameObject item -- @param #number charge Can be `nil` to reset the unused state / full @@ -777,14 +777,16 @@ -- @return #boolean --- --- Set of properties that differentiates one item from another of the same record type. +-- Set of properties that differentiates one item from another of the same record type; can be used by any script, but only global and self scripts can change values. -- @function [parent=#Item] itemData -- @param openmw.core#GameObject item -- @return #ItemData --- -- @type ItemData --- @field #number condition The item's current condition. Time remaining for lights. Uses left for lockpicks and probes. Current health for weapons and armor. +-- @field #number condition The item's current condition. Time remaining for lights. Uses left for repairs, lockpicks and probes. Current health for weapons and armor. +-- @field #number enchantmentCharge The item's current enchantment charge. Unenchanted items will always return a value of `nil`. Setting this to `nil` will reset the charge of the item. +-- @field #string soul The recordId of the item's current soul. Items without soul will always return a value of `nil`. Setting this to `nil` will remove the soul from the item. -------------------------------------------------------------------------------- -- @{#Creature} functions @@ -1689,7 +1691,7 @@ -- @return #MiscellaneousRecord --- --- Returns the read-only soul of a miscellaneous item +-- (DEPRECATED, use itemData(item).soul) Returns the read-only soul of a miscellaneous item -- @function [parent=#Miscellaneous] getSoul -- @param openmw.core#GameObject object -- @return #string @@ -1702,7 +1704,7 @@ -- @return #MiscellaneousRecord A strongly typed Miscellaneous record. --- --- Sets the soul of a miscellaneous item, intended for soul gem objects; Must be used in a global script. +-- (DEPRECATED, use itemData(item).soul) Sets the soul of a miscellaneous item, intended for soul gem objects; Must be used in a global script. -- @function [parent=#Miscellaneous] setSoul -- @param openmw.core#GameObject object -- @param #string soulId Record ID for the soul of the creature to use