diff --git a/CHANGELOG.md b/CHANGELOG.md index ce530dfe3d..1c3e7cc347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -246,6 +246,7 @@ Feature #7964: Add Lua read access to MW Dialogue records Feature #7971: Make save's Time Played value display hours instead of days Feature #7985: Support dark mode on Windows + Feature #8028: Lua Engine Handler for MWScript `startscript` functionality. Task #5896: Do not use deprecated MyGUI properties Task #6085: Replace boost::filesystem with std::filesystem Task #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index a5d6fe1114..024afb739f 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -150,6 +150,9 @@ namespace MWBase = 0; virtual std::string formatResourceUsageStats() const = 0; + + virtual void globalMWScriptCalled(const ESM::RefId& scriptName, const MWWorld::Ptr& target, const bool started) + = 0; }; } diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 6c652bccba..ef91371384 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -113,6 +113,14 @@ namespace MWLua scripts->onSkillLevelUp(event.mSkill, event.mSource); } + void operator()(const OnGlobalScriptRequested& event) const + { + sol::optional target; + if (event.mTarget.isSet()) + target = GObject(event.mTarget); + mGlobalScripts.onGlobalScriptRequested(MWScriptRef{ event.mScript, {} }, event.mStarted, target); + } + private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index fb9183eb7c..e819fa7ee8 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -70,8 +70,14 @@ namespace MWLua std::string mSkill; std::string mSource; }; + struct OnGlobalScriptRequested + { + ESM::RefId mScript; + ESM::RefNum mTarget; + bool mStarted; + }; using Event = std::variant; + OnAnimationTextKey, OnSkillUse, OnSkillLevelUp, OnGlobalScriptRequested>; 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 37fff22f99..dafca6bf1a 100644 --- a/apps/openmw/mwlua/globalscripts.hpp +++ b/apps/openmw/mwlua/globalscripts.hpp @@ -4,6 +4,7 @@ #include #include +#include "mwscriptref.hpp" #include "object.hpp" namespace MWLua @@ -24,6 +25,7 @@ namespace MWLua &mOnActivateHandlers, &mOnUseItemHandlers, &mOnNewExteriorHandlers, + &mOnGlobalScriptRequestedHandlers, }); } @@ -41,6 +43,10 @@ namespace MWLua callEngineHandlers(mOnUseItemHandlers, obj, actor, force); } void onNewExterior(const GCell& cell) { callEngineHandlers(mOnNewExteriorHandlers, cell); } + void onGlobalScriptRequested(const MWScriptRef& scriptRef, const bool started, sol::optional target) + { + callEngineHandlers(mOnGlobalScriptRequestedHandlers, scriptRef, started, target); + } private: EngineHandlerList mObjectActiveHandlers{ "onObjectActive" }; @@ -51,6 +57,7 @@ namespace MWLua EngineHandlerList mOnActivateHandlers{ "onActivate" }; EngineHandlerList mOnUseItemHandlers{ "_onUseItem" }; EngineHandlerList mOnNewExteriorHandlers{ "onNewExterior" }; + EngineHandlerList mOnGlobalScriptRequestedHandlers{ "onGlobalScriptRequested" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 437b1db76d..8f2392d95c 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -1,5 +1,8 @@ #include "luamanagerimp.hpp" +#include +#include +#include #include #include @@ -601,6 +604,14 @@ namespace MWLua mGlobalScriptsStarted = true; } + void LuaManager::globalMWScriptCalled(const ESM::RefId& scriptName, const MWWorld::Ptr& target, const bool started) + { + ESM::RefNum targetId; + if (!target.isEmpty()) + targetId = getId(target); + mEngineEvents.addToQueue(EngineEvents::OnGlobalScriptRequested{ scriptName, targetId, started }); + } + void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data) { if (ptr.getRefData().getLuaScripts()) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index d245b3035b..16e9ad299c 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -156,6 +156,9 @@ namespace MWLua void reportStats(unsigned int frameNumber, osg::Stats& stats) const; std::string formatResourceUsageStats() const override; + void globalMWScriptCalled( + const ESM::RefId& scriptName, const MWWorld::Ptr& target, const bool started) override; + LuaUtil::InputAction::Registry& inputActions() { return mInputActions; } LuaUtil::InputTrigger::Registry& inputTriggers() { return mInputTriggers; } diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 1d06a5783e..21ce73479a 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -4,64 +4,14 @@ #include #include -#include "../mwbase/environment.hpp" -#include "../mwbase/scriptmanager.hpp" -#include "../mwbase/world.hpp" -#include "../mwscript/globalscripts.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/worldimp.hpp" +#include "mwscriptref.hpp" #include "object.hpp" #include -namespace MWLua -{ - struct MWScriptRef - { - ESM::RefId mId; - sol::optional mObj; - - MWScript::Locals& getLocals() - { - if (mObj) - return mObj->ptr().getRefData().getLocals(); - else - return MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocals(mId); - } - bool isRunning() const - { - if (mObj.has_value()) // local script - { - MWWorld::LocalScripts& localScripts = MWBase::Environment::get().getWorld()->getLocalScripts(); - return localScripts.isRunning(mId, mObj->ptr()); - } - - return MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning(mId); - } - }; - struct MWScriptVariables - { - MWScriptRef mRef; - }; -} - -namespace sol -{ - template <> - struct is_automagical : std::false_type - { - }; - template <> - struct is_automagical : std::false_type - { - }; - template <> - struct is_automagical : std::false_type - { - }; -} - namespace MWLua { diff --git a/apps/openmw/mwlua/mwscriptref.hpp b/apps/openmw/mwlua/mwscriptref.hpp new file mode 100644 index 0000000000..d2532be20c --- /dev/null +++ b/apps/openmw/mwlua/mwscriptref.hpp @@ -0,0 +1,62 @@ +#ifndef MWSCRIPTREF_H_ +#define MWSCRIPTREF_H_ + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/scriptmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwscript/globalscripts.hpp" +#include "../mwworld/localscripts.hpp" + +#include "object.hpp" + +namespace MWLua +{ + struct MWScriptRef + { + ESM::RefId mId; + sol::optional mObj; + + MWScript::Locals& getLocals() + { + if (mObj) + return mObj->ptr().getRefData().getLocals(); + else + return MWBase::Environment::get().getScriptManager()->getGlobalScripts().getLocals(mId); + } + + bool isRunning() const + { + if (mObj.has_value()) // local script + { + MWWorld::LocalScripts& localScripts = MWBase::Environment::get().getWorld()->getLocalScripts(); + return localScripts.isRunning(mId, mObj->ptr()); + } + + return MWBase::Environment::get().getScriptManager()->getGlobalScripts().isRunning(mId); + } + }; + struct MWScriptVariables + { + MWScriptRef mRef; + }; +} + +namespace sol +{ + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; + template <> + struct is_automagical : std::false_type + { + }; +} + +#endif // MWSCRIPTREF_H_ diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index d032515314..c5e75f12e1 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -8,6 +8,7 @@ #include #include +#include #include "../mwworld/ptr.hpp" diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index c20e2fe255..4fa99518cf 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -14,6 +14,8 @@ #include "../mwbase/world.hpp" #include "../mwworld/worldmodel.hpp" +#include "../mwbase/luamanager.hpp" + #include "interpretercontext.hpp" namespace @@ -128,6 +130,7 @@ namespace MWScript void GlobalScripts::addScript(const ESM::RefId& name, const MWWorld::Ptr& target) { const auto iter = mScripts.find(name); + bool started = false; if (iter == mScripts.end()) { @@ -136,21 +139,24 @@ namespace MWScript auto desc = std::make_shared(); MWWorld::Ptr ptr = target; desc->mTarget = ptr; - desc->mRunning = true; + started = desc->mRunning = true; desc->mLocals.configure(*script); mScripts.insert(std::make_pair(name, desc)); } else { Log(Debug::Error) << "Failed to add global script " << name << ": script record not found"; + return; } } else if (!iter->second->mRunning) { - iter->second->mRunning = true; + started = iter->second->mRunning = true; MWWorld::Ptr ptr = target; iter->second->mTarget = ptr; } + + MWBase::Environment::get().getLuaManager()->globalMWScriptCalled(name, target, started); } void GlobalScripts::removeScript(const ESM::RefId& name) diff --git a/components/lua/scriptscontainer.hpp b/components/lua/scriptscontainer.hpp index 631b1e58a8..5a538d0ef2 100644 --- a/components/lua/scriptscontainer.hpp +++ b/components/lua/scriptscontainer.hpp @@ -178,7 +178,7 @@ namespace LuaUtil // Calls given handlers in direct order. template - void callEngineHandlers(EngineHandlerList& handlers, const Args&... args) + void callEngineHandlers(EngineHandlerList& handlers, Args&&... args) { for (Handler& handler : handlers.mList) { diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index ac6979a236..669a45f774 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -58,6 +58,16 @@ Engine handler is a function defined by a script, that can be called by the engi - Object is activated by an actor. * - onNewExterior(cell) - A new exterior cell not defined by a content file has been generated. + * - onGlobalScriptRequested(script, started, target) + - | A `StartScript` was called. + | Note that this affects `Start Scripts` defined by content files, as well as calls to the MWScript function `StartScript`. + | This does not necessarily indicate the script in question is actually being started, as there may (presently) + | only be a single instance of a global script at a time. + | This can potentially be used to override vanilla functionality, such as: + | ``if script.recordId == 'dbattackscript' then script.variables.sleeponce = 1 end`` + | The `target` and `script.object` fields may be different. + | If `started` is true then refer to `script.object`, otherwise the `target`, if present, represents what the `startscript` call is actually targeted at. For example: + | ``if started and script.object then print(script.object.recordId) elseif target then print(target.recordId) end`` **Only for local scripts**