diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 73a87fc1e0..0bc8f20dc4 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -8,6 +8,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" +#include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/manualref.hpp" @@ -106,6 +107,16 @@ namespace MWLua // TODO: add here overloads for other records ); + api["_runStandardActivationAction"] = [context](const GObject& object, const GObject& actor) { + context.mLuaManager->addAction( + [object, actor] { + const MWWorld::Ptr& objPtr = object.ptr(); + const MWWorld::Ptr& actorPtr = actor.ptr(); + objPtr.getClass().activate(objPtr, actorPtr)->execute(actorPtr); + }, + "_runStandardActivationAction"); + }; + return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 767b056bdf..b8d54aec23 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -2,7 +2,6 @@ #include -#include "../mwworld/action.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -102,44 +101,6 @@ namespace MWLua osg::Vec3f mRot; }; - class ActivateAction final : public LuaManager::Action - { - public: - ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor) - : Action(state) - , mObject(object) - , mActor(actor) - { - } - - void apply() const override - { - MWWorld::Ptr object = MWBase::Environment::get().getWorldModel()->getPtr(mObject); - if (object.isEmpty()) - throw std::runtime_error(std::string("Object not found: " + mObject.toString())); - MWWorld::Ptr actor = MWBase::Environment::get().getWorldModel()->getPtr(mActor); - if (actor.isEmpty()) - throw std::runtime_error(std::string("Actor not found: " + mActor.toString())); - - if (object.getRefData().activate()) - { - MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); - std::unique_ptr action = object.getClass().activate(object, actor); - action->execute(actor); - } - } - - std::string toString() const override - { - return std::string("ActivateAction object=") + mObject.toString() + std::string(" actor=") - + mActor.toString(); - } - - private: - ObjectId mObject; - ObjectId mActor; - }; - template using Cell = std::conditional_t, LCell, GCell>; @@ -165,6 +126,7 @@ namespace MWLua template void addBasicBindings(sol::usertype& objectT, const Context& context) { + objectT["id"] = sol::readonly_property([](const ObjectT& o) -> std::string { return o.id().toString(); }); objectT["isValid"] = [](const ObjectT& o) { return !o.ptrOrNull().isEmpty(); }; objectT["recordId"] = sol::readonly_property( [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().serializeText(); }); @@ -192,13 +154,16 @@ namespace MWLua { dest.id(), std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); }; - objectT["activateBy"] = [context](const ObjectT& o, const ObjectT& actor) { - uint32_t esmRecordType = actor.ptr().getType(); + objectT["activateBy"] = [](const ObjectT& object, const ObjectT& actor) { + const MWWorld::Ptr& objPtr = object.ptr(); + const MWWorld::Ptr& actorPtr = actor.ptr(); + uint32_t esmRecordType = actorPtr.getType(); if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_) throw std::runtime_error( "The argument of `activateBy` must be an actor who activates the object. Got: " + actor.toString()); - context.mLuaManager->addAction(std::make_unique(context.mLua, o.id(), actor.id())); + if (objPtr.getRefData().activate()) + MWBase::Environment::get().getLuaManager()->objectActivated(objPtr, actorPtr); }; auto isEnabled = [](const ObjectT& o) { return o.ptr().getRefData().isEnabled(); }; diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index 7508d4a785..a2b818115d 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -419,13 +419,23 @@ namespace MWScript void InterpreterContext::executeActivation(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) { + // MWScripted activations don't go through Lua because 1-frame delay can brake mwscripts. +#if 0 MWBase::Environment::get().getLuaManager()->objectActivated(ptr, actor); + + // TODO: Enable this branch after implementing one of the options: + // 1) Pause this mwscript (or maybe all mwscripts) for one frame and continue from the same + // command when the activation is processed by Lua script. + // 2) Force Lua scripts to handle a zero-length extra frame right now, so when control + // returns to the mwscript, the activation is already processed. +#else std::unique_ptr action = (ptr.getClass().activate(ptr, actor)); action->execute(actor); if (action->getTarget() != MWWorld::Ptr() && action->getTarget() != ptr) { updatePtr(ptr, action->getTarget()); } +#endif } int InterpreterContext::getMemberShort(const ESM::RefId& id, std::string_view name, bool global) const diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f1e2a762f7..ffb8311e96 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3779,13 +3779,8 @@ namespace MWWorld void World::activate(const Ptr& object, const Ptr& actor) { breakInvisibility(actor); - if (object.getRefData().activate()) - { MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); - std::unique_ptr action = object.getClass().activate(object, actor); - action->execute(actor); - } } struct ResetActorsVisitor diff --git a/docs/source/luadoc_data_paths.sh b/docs/source/luadoc_data_paths.sh index 792e957c11..91bfcbd48d 100755 --- a/docs/source/luadoc_data_paths.sh +++ b/docs/source/luadoc_data_paths.sh @@ -1,5 +1,6 @@ paths=( openmw_aux/*lua + scripts/omw/activationhandlers.lua scripts/omw/ai.lua scripts/omw/playercontrols.lua scripts/omw/camera/camera.lua diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 061f2ce5c7..4165eb3119 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -27,6 +27,7 @@ Lua API reference openmw_aux_util openmw_aux_time openmw_aux_ui + interface_activation interface_ai interface_camera interface_controls @@ -68,6 +69,9 @@ Interfaces of built-in scripts * - 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. diff --git a/docs/source/reference/lua-scripting/interface_activation.rst b/docs/source/reference/lua-scripting/interface_activation.rst new file mode 100644 index 0000000000..ccc51ca457 --- /dev/null +++ b/docs/source/reference/lua-scripting/interface_activation.rst @@ -0,0 +1,6 @@ +Interface Activation +==================== + +.. raw:: html + :file: generated_html/scripts_omw_activationhandlers.html + diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 36334492a7..c4543cd47c 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -461,6 +461,9 @@ The order in which the scripts are started is important. So if one mod should ov * - 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. diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 64cbee0479..bc6d4b0055 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -63,6 +63,7 @@ set(BUILTIN_DATA_FILES builtin.omwscripts + scripts/omw/activationhandlers.lua scripts/omw/ai.lua scripts/omw/camera/camera.lua scripts/omw/camera/head_bobbing.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index a3eed5ea16..46a1f3f32f 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -1,9 +1,17 @@ +# UI framework PLAYER: scripts/omw/mwui/init.lua + +# Settings framework GLOBAL: scripts/omw/settings/global.lua PLAYER: scripts/omw/settings/player.lua + +# Mechanics +GLOBAL: scripts/omw/activationhandlers.lua PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua NPC,CREATURE: scripts/omw/ai.lua + +# Lua console PLAYER: scripts/omw/console/player.lua GLOBAL: scripts/omw/console/global.lua CUSTOM: scripts/omw/console/local.lua diff --git a/files/data/scripts/omw/activationhandlers.lua b/files/data/scripts/omw/activationhandlers.lua new file mode 100644 index 0000000000..c2ac4db884 --- /dev/null +++ b/files/data/scripts/omw/activationhandlers.lua @@ -0,0 +1,68 @@ +local types = require('openmw.types') +local world = require('openmw.world') + +local handlersPerObject = {} +local handlersPerType = {} + +local function onActivate(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._runStandardActivationAction(obj, actor) +end + +return { + interfaceName = 'Activation', + --- + -- @module Activation + -- @usage require('openmw.interfaces').Activation + interface = { + --- Interface version + -- @field [parent=#Activation] #number version + version = 0, + + --- Add new activation 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=#Activation] 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 activation 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=#Activation] addHandlerForType + -- @param #userdata 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 = { onActivate = onActivate }, +} diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 361bb9a3d5..cf7838c8dd 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -107,6 +107,7 @@ -- Player, actors, items, and statics are game objects. -- @type GameObject -- @extends #userdata +-- @field #string id A unique id of this object (not record id), can be used as a key in a table. -- @field #boolean enabled Whether the object is enabled or disabled. Global scripts can set the value. Items in containers or inventories can't be disabled. -- @field openmw.util#Vector3 position Object position. -- @field openmw.util#Vector3 rotation Object rotation (ZXY order). diff --git a/files/lua_api/openmw/interfaces.lua b/files/lua_api/openmw/interfaces.lua index 3a2e5627c2..36032acb2f 100644 --- a/files/lua_api/openmw/interfaces.lua +++ b/files/lua_api/openmw/interfaces.lua @@ -3,16 +3,19 @@ -- @usage local I = require('openmw.interfaces') --- --- @field [parent=#interfaces] scripts.omw.camera.camera#scripts.omw.camera.camera Camera +-- @field [parent=#interfaces] scripts.omw.ai#scripts.omw.activationhandlers Activation --- --- @field [parent=#interfaces] scripts.omw.settings.player#scripts.omw.settings.player Settings +-- @field [parent=#interfaces] scripts.omw.ai#scripts.omw.ai AI + +--- +-- @field [parent=#interfaces] scripts.omw.camera.camera#scripts.omw.camera.camera Camera --- -- @field [parent=#interfaces] scripts.omw.mwui.init#scripts.omw.mwui.init MWUI --- --- @field [parent=#interfaces] scripts.omw.ai#scripts.omw.ai AI +-- @field [parent=#interfaces] scripts.omw.settings.player#scripts.omw.settings.player Settings --- -- @function [parent=#interfaces] __index