Merge branch 'activation' into 'master'

Allow Lua scripts to extend or override standard activation mechanics

See merge request OpenMW/openmw!2935
7344-support-launching-the-example-suite
psi29a 2 years ago
commit 2a6e301925

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

@ -2,7 +2,6 @@
#include <components/lua/luastate.hpp>
#include "../mwworld/action.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
@ -91,44 +90,6 @@ namespace MWLua
world->enable(newPtr);
}
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<MWWorld::Action> 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 <typename ObjT>
using Cell = std::conditional_t<std::is_same_v<ObjT, LObject>, LCell, GCell>;
@ -154,6 +115,7 @@ namespace MWLua
template <class ObjectT>
void addBasicBindings(sol::usertype<ObjectT>& 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(); });
@ -181,13 +143,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<ActivateAction>(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(); };

@ -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<MWWorld::Action> 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

@ -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<MWWorld::Action> action = object.getClass().activate(object, actor);
action->execute(actor);
}
}
struct ResetActorsVisitor

@ -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

@ -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 <Interface Activation>`
- by global scripts
- Allows to extend or override built-in activation mechanics.
* - :ref:`AI <Interface AI>`
- by local scripts
- Control basic AI of NPCs and creatures.

@ -0,0 +1,6 @@
Interface Activation
====================
.. raw:: html
:file: generated_html/scripts_omw_activationhandlers.html

@ -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 <Interface Activation>`
- by global scripts
- Allows to extend or override built-in activation mechanics.
* - :ref:`AI <Interface AI>`
- by local scripts
- Control basic AI of NPCs and creatures.

@ -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

@ -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

@ -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 },
}

@ -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).

@ -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

Loading…
Cancel
Save