diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 12a894d343..e120b1403e 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -51,6 +51,7 @@ namespace MWBase virtual void objectTeleported(const MWWorld::Ptr& ptr) = 0; virtual void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) = 0; virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; + virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 9ae9ecf2b0..decbede856 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -19,6 +19,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/luamanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -600,7 +601,7 @@ namespace MWGui } } else - useItem(ptr); + MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer()); // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 // item diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 569eb22bcf..ac1ac687ed 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -65,6 +65,15 @@ namespace MWLua scripts->onActivated(LObject(actor)); } + void operator()(const OnUseItem& event) const + { + MWWorld::Ptr obj = getPtr(event.mObject); + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty() || obj.isEmpty()) + return; + mGlobalScripts.onUseItem(GObject(obj), GObject(actor)); + } + void operator()(const OnConsume& event) const { MWWorld::Ptr actor = getPtr(event.mActor); diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index ac854abd4a..3cbc366623 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -36,6 +36,11 @@ namespace MWLua ESM::RefNum mActor; ESM::RefNum mObject; }; + struct OnUseItem + { + ESM::RefNum mActor; + ESM::RefNum mObject; + }; struct OnConsume { ESM::RefNum mActor; @@ -45,7 +50,7 @@ namespace MWLua { MWWorld::CellStore& mCell; }; - using Event = std::variant; + using Event = std::variant; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/globalscripts.hpp b/apps/openmw/mwlua/globalscripts.hpp index 0190000586..314a4118e6 100644 --- a/apps/openmw/mwlua/globalscripts.hpp +++ b/apps/openmw/mwlua/globalscripts.hpp @@ -19,8 +19,16 @@ namespace MWLua GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global") { - registerEngineHandlers({ &mObjectActiveHandlers, &mActorActiveHandlers, &mItemActiveHandlers, - &mNewGameHandlers, &mPlayerAddedHandlers, &mOnActivateHandlers, &mOnNewExteriorHandlers }); + registerEngineHandlers({ + &mObjectActiveHandlers, + &mActorActiveHandlers, + &mItemActiveHandlers, + &mNewGameHandlers, + &mPlayerAddedHandlers, + &mOnActivateHandlers, + &mOnUseItemHandlers, + &mOnNewExteriorHandlers, + }); } void newGameStarted() { callEngineHandlers(mNewGameHandlers); } @@ -32,6 +40,7 @@ namespace MWLua { callEngineHandlers(mOnActivateHandlers, obj, actor); } + void onUseItem(const GObject& obj, const GObject& actor) { callEngineHandlers(mOnUseItemHandlers, obj, actor); } void onNewExterior(const GCell& cell) { callEngineHandlers(mOnNewExteriorHandlers, cell); } private: @@ -41,6 +50,7 @@ namespace MWLua EngineHandlerList mNewGameHandlers{ "onNewGame" }; EngineHandlerList mPlayerAddedHandlers{ "onPlayerAdded" }; EngineHandlerList mOnActivateHandlers{ "onActivate" }; + EngineHandlerList mOnUseItemHandlers{ "_onUseItem" }; EngineHandlerList mOnNewExteriorHandlers{ "onNewExterior" }; }; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 2bace90614..b202322f60 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -19,6 +19,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/datetimemanager.hpp" @@ -316,6 +317,21 @@ namespace MWLua }, "_runStandardActivationAction"); }; + api["_runStandardUseAction"] = [context](const GObject& object, const GObject& actor) { + context.mLuaManager->addAction( + [object, actor] { + const MWWorld::Ptr& actorPtr = actor.ptr(); + const MWWorld::Ptr& objectPtr = object.ptr(); + if (actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + MWBase::Environment::get().getWindowManager()->useItem(objectPtr, true); + else + { + std::unique_ptr action = objectPtr.getClass().use(objectPtr, true); + action->execute(actorPtr, true); + } + }, + "_runStandardUseAction"); + }; return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index d00fda9dda..03efa3f066 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -77,6 +77,10 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) }); } + void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override + { + mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object) }); + } void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index c4322dbc0a..7bcda5110c 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -7,5 +7,6 @@ paths=( scripts/omw/mwui/init.lua scripts/omw/settings/player.lua scripts/omw/ui.lua + scripts/omw/usehandlers.lua ) printf '%s\n' "${paths[@]}" \ No newline at end of file diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 77d4c9b14b..5a1f8236b9 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -34,6 +34,7 @@ Lua API reference interface_ai interface_camera interface_controls + interface_item_usage interface_mwui interface_settings interface_ui @@ -64,36 +65,6 @@ Sources can be found in ``resources/vfs/openmw_aux``. In theory mods can overrid .. include:: tables/aux_packages.rst -Interfaces of built-in scripts ------------------------------- +**Interfaces of built-in scripts** -.. list-table:: - :widths: 20 20 60 - - * - Interface - - Can be used - - Description - * - :ref:`Activation ` - - by global scripts - - Allows to extend or override built-in activation mechanics. - * - :ref:`AI ` - - by local scripts - - Control basic AI of NPCs and creatures. - * - :ref:`Camera ` - - by player scripts - - | Allows to alter behavior of the built-in camera script - | without overriding the script completely. - * - :ref:`Controls ` - - by player scripts - - | Allows to alter behavior of the built-in script - | that handles player controls. - * - :ref:`Settings ` - - by player and global scripts - - Save, display and track changes of setting values. - * - :ref:`MWUI ` - - by player scripts - - Morrowind-style UI templates. - * - :ref:`UI ` - - by player scripts - - | High-level UI modes interface. Allows to override parts - | of the interface. +.. include:: tables/interfaces.rst diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 513fd8c871..22cf4710d4 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -6,6 +6,8 @@ Built-in events Actor events ------------ +**StartAIPackage, RemoveAIPackages** + Any script can send to any actor (except player, for player will be ignored) events ``StartAIPackage`` and ``RemoveAIPackages``. The effect is equivalent to calling ``interfaces.AI.startPackage`` or ``interfaces.AI.removePackages`` in a local script on this actor. @@ -16,6 +18,17 @@ Examples: actor:sendEvent('StartAIPackage', {type='Combat', target=self.object}) actor:sendEvent('RemoveAIPackages', 'Pursue') +**UseItem** + +Any script can send global event ``UseItem`` with arguments ``object`` and ``actor``. +The actor will use (e.g. equip or consume) the object. The object should be in the actor's inventory. + +Example: + +.. code-block:: Lua + + core.sendGlobalEvent('UseItem', {object = potion, actor = player}) + UI events --------- diff --git a/docs/source/reference/lua-scripting/interface_item_usage.rst b/docs/source/reference/lua-scripting/interface_item_usage.rst new file mode 100644 index 0000000000..cdaa53353f --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_item_usage.rst @@ -0,0 +1,6 @@ +Interface ItemUsage +=================== + +.. raw:: html + :file: generated_html/scripts_omw_usehandlers.html + diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 157a9e686d..598d4a4c99 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -457,36 +457,7 @@ The order in which the scripts are started is important. So if one mod should ov **Interfaces of built-in scripts** -.. list-table:: - :widths: 20 20 60 - - * - Interface - - Can be used - - Description - * - :ref:`Activation ` - - by global scripts - - Allows to extend or override built-in activation mechanics. - * - :ref:`AI ` - - by local scripts - - Control basic AI of NPCs and creatures. - * - :ref:`Camera ` - - by player scripts - - | Allows to alter behavior of the built-in camera script - | without overriding the script completely. - * - :ref:`Controls ` - - by player scripts - - | Allows to alter behavior of the built-in script - | that handles player controls. - * - :ref:`Settings ` - - by player and global scripts - - Save, display and track changes of setting values. - * - :ref:`MWUI ` - - by player scripts - - Morrowind-style UI templates. - * - :ref:`UI ` - - by player scripts - - | High-level UI modes interface. Allows to override parts - | of the interface. +.. include:: tables/interfaces.rst Event system ============ diff --git a/docs/source/reference/lua-scripting/tables/interfaces.rst b/docs/source/reference/lua-scripting/tables/interfaces.rst new file mode 100644 index 0000000000..e05eb642f0 --- /dev/null +++ b/docs/source/reference/lua-scripting/tables/interfaces.rst @@ -0,0 +1,34 @@ +.. list-table:: + :widths: 20 20 60 + + * - Interface + - Can be used + - Description + * - :ref:`Activation ` + - by global scripts + - Allows to extend or override built-in activation mechanics. + * - :ref:`AI ` + - by local scripts + - Control basic AI of NPCs and creatures. + * - :ref:`Camera ` + - by player scripts + - | Allows to alter behavior of the built-in camera script + | without overriding the script completely. + * - :ref:`Controls ` + - by player scripts + - | Allows to alter behavior of the built-in script + | that handles player controls. + * - :ref:`ItemUsage ` + - by global scripts + - | Allows to extend or override built-in item usage + | mechanics. + * - :ref:`Settings ` + - by player and global scripts + - Save, display and track changes of setting values. + * - :ref:`MWUI ` + - by player scripts + - Morrowind-style UI templates. + * - :ref:`UI ` + - by player scripts + - | High-level UI modes interface. Allows to override parts + | of the interface. diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index f52be02451..afab9b4c79 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -90,6 +90,7 @@ set(BUILTIN_DATA_FILES scripts/omw/mwui/space.lua scripts/omw/mwui/init.lua scripts/omw/ui.lua + scripts/omw/usehandlers.lua shaders/adjustments.omwfx shaders/bloomlinear.omwfx diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 8999a98f80..b86bedc509 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -8,6 +8,7 @@ PLAYER: scripts/omw/settings/player.lua # Mechanics GLOBAL: scripts/omw/activationhandlers.lua GLOBAL: scripts/omw/cellhandlers.lua +GLOBAL: scripts/omw/usehandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua diff --git a/files/data/scripts/omw/activationhandlers.lua b/files/data/scripts/omw/activationhandlers.lua index ea69777582..7d185cf9da 100644 --- a/files/data/scripts/omw/activationhandlers.lua +++ b/files/data/scripts/omw/activationhandlers.lua @@ -23,7 +23,6 @@ local handlersPerType = {} handlersPerType[types.ESM4Door] = { ESM4DoorActivation } local function onActivate(obj, actor) - types.Actor.activeEffects(actor):remove('invisibility') local handlers = handlersPerObject[obj.id] if handlers then for i = #handlers, 1, -1 do @@ -40,6 +39,7 @@ local function onActivate(obj, actor) end end end + types.Actor.activeEffects(actor):remove('invisibility') world._runStandardActivationAction(obj, actor) end diff --git a/files/data/scripts/omw/usehandlers.lua b/files/data/scripts/omw/usehandlers.lua new file mode 100644 index 0000000000..7cc7e12f94 --- /dev/null +++ b/files/data/scripts/omw/usehandlers.lua @@ -0,0 +1,97 @@ +local types = require('openmw.types') +local world = require('openmw.world') + +local handlersPerObject = {} +local handlersPerType = {} + +local function useItem(obj, actor) + local handlers = handlersPerObject[obj.id] + if handlers then + for i = #handlers, 1, -1 do + if handlers[i](obj, actor) == false then + return -- skip other handlers + end + end + end + handlers = handlersPerType[obj.type] + if handlers then + for i = #handlers, 1, -1 do + if handlers[i](obj, actor) == false then + return -- skip other handlers + end + end + end + world._runStandardUseAction(obj, actor) +end + +return { + interfaceName = 'ItemUsage', + --- + -- Allows to extend or override built-in item usage mechanics. + -- Note: at the moment it can override item usage in inventory + -- (dragging an item on the character's model), but + -- + -- * can't intercept actions performed by mwscripts; + -- * can't intercept actions performed by the AI (i.e. drinking a potion in combat); + -- * can't intercept actions performed via quick keys menu. + -- @module ItemUsage + -- @usage local I = require('openmw.interfaces') + -- + -- -- Override Use action (global script). + -- -- Forbid equipping armor with weight > 5 + -- I.ItemUsage.addHandlerForType(types.Armor, function(armor, actor) + -- if types.Armor.record(armor).weight > 5 then + -- return false -- disable other handlers + -- end + -- end) + -- + -- -- Call Use action (any script). + -- core.sendGlobalEvent('UseItem', {object = armor, actor = player}) + interface = { + --- Interface version + -- @field [parent=#ItemUsage] #number version + version = 0, + + --- Add new use action handler for a specific object. + -- If `handler(object, actor)` returns false, other handlers for + -- the same object (including type handlers) will be skipped. + -- @function [parent=#ItemUsage] addHandlerForObject + -- @param openmw.core#GameObject obj The object. + -- @param #function handler The handler. + addHandlerForObject = function(obj, handler) + local handlers = handlersPerObject[obj.id] + if handlers == nil then + handlers = {} + handlersPerObject[obj.id] = handlers + end + handlers[#handlers + 1] = handler + end, + + --- Add new use action handler for a type of objects. + -- If `handler(object, actor)` returns false, other handlers for + -- the same object (including type handlers) will be skipped. + -- @function [parent=#ItemUsage] addHandlerForType + -- @param #any type A type from the `openmw.types` package. + -- @param #function handler The handler. + addHandlerForType = function(type, handler) + local handlers = handlersPerType[type] + if handlers == nil then + handlers = {} + handlersPerType[type] = handlers + end + handlers[#handlers + 1] = handler + end, + }, + engineHandlers = { _onUseItem = useItem }, + eventHandlers = { + UseItem = function(data) + if not data.object then + error('UseItem: missing argument "object"') + end + if not data.actor or not types.Actor.objectIsInstance(data.actor) then + error('UseItem: invalid argument "actor"') + end + useItem(data.object, data.actor) + end + } +} diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 17e4cc50f5..c55860ee26 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -149,6 +149,7 @@ -- Creates a custom record in the world database. -- Eventually meant to support all records, but the current -- set of supported types is limited to: +-- -- * @{openmw.types#PotionRecord}, -- * @{openmw.types#ArmorRecord}, -- * @{openmw.types#BookRecord},