From 773669e69bc42b3f25df4d9612b4d71fd2e6da60 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Thu, 25 May 2023 01:39:51 +0200 Subject: [PATCH 1/4] Add Lua script to handler ESM4 doors with Flag_AutomaticDoor --- apps/openmw/mwlua/types/door.cpp | 2 + apps/openmw/mwlua/worldview.cpp | 2 +- files/data/CMakeLists.txt | 1 + files/data/builtin.omwscripts | 1 + .../omw/mechanics/playercontroller.lua | 46 +++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 files/data/scripts/omw/mechanics/playercontroller.lua diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index e537f7c377..f13952a6b5 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -106,5 +106,7 @@ namespace MWLua record["model"] = sol::readonly_property([vfs](const ESM4::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); }); + record["isAutomatic"] = sol::readonly_property( + [](const ESM4::Door& rec) -> bool { return rec.mDoorFlags & ESM4::Door::Flag_AutomaticDoor; }); } } diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index f5699dff89..6d8c01c56d 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -47,7 +47,7 @@ namespace MWLua return &mActivatorsInScene; if (cls.isActor()) return &mActorsInScene; - if (cls.isDoor()) + if (ptr.mRef->getType() == ESM::REC_DOOR || ptr.mRef->getType() == ESM::REC_DOOR4) return &mDoorsInScene; if (typeid(cls) == typeid(MWClass::Container)) return &mContainersInScene; diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index bc6d4b0055..eb61f3c118 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -74,6 +74,7 @@ set(BUILTIN_DATA_FILES scripts/omw/console/player.lua scripts/omw/console/global.lua scripts/omw/console/local.lua + scripts/omw/mechanics/playercontroller.lua scripts/omw/playercontrols.lua scripts/omw/settings/player.lua scripts/omw/settings/global.lua diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index 46a1f3f32f..cbc94496cd 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -7,6 +7,7 @@ PLAYER: scripts/omw/settings/player.lua # Mechanics GLOBAL: scripts/omw/activationhandlers.lua +PLAYER: scripts/omw/mechanics/playercontroller.lua PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua NPC,CREATURE: scripts/omw/ai.lua diff --git a/files/data/scripts/omw/mechanics/playercontroller.lua b/files/data/scripts/omw/mechanics/playercontroller.lua new file mode 100644 index 0000000000..19e62d02c7 --- /dev/null +++ b/files/data/scripts/omw/mechanics/playercontroller.lua @@ -0,0 +1,46 @@ +local core = require('openmw.core') +local nearby = require('openmw.nearby') +local self = require('openmw.self') +local types = require('openmw.types') + +local cell = nil +local autodoors = {} + +local function onCellChange() + autodoors = {} + for _, door in ipairs(nearby.doors) do + if door.type == types.ESM4Door and types.ESM4Door.record(door).isAutomatic then + autodoors[#autodoors + 1] = door + end + end +end + +local autodoorActivationDist = 300 + +local lastAutoActivation = 0 +local function processAutomaticDoors() + if core.getRealTime() - lastAutoActivation < 2 then + return + end + for _, door in ipairs(autodoors) do + if door.enabled and (door.position - self.position):length() < autodoorActivationDist then + print('Automatic activation of', door) + door:activateBy(self) + lastAutoActivation = core.getRealTime() + end + end +end + +local function onUpdate() + if self.cell ~= cell then + cell = self.cell + onCellChange() + end + processAutomaticDoors() +end + +return { + engineHandlers = { + onUpdate = onUpdate, + }, +} From a778dff61dc0d56fc73a8a10a6304da20c472ba1 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 30 May 2023 01:44:09 +0200 Subject: [PATCH 2/4] Add Lua functions to get the current load order and search objects by RefNum/FormId --- apps/openmw/mwlua/luabindings.cpp | 30 +++++++++++++++++++++++++++- apps/openmw/mwlua/nearbybindings.cpp | 7 +++++++ files/lua_api/openmw/core.lua | 24 ++++++++++++++++++++++ files/lua_api/openmw/nearby.lua | 7 +++++++ files/lua_api/openmw/world.lua | 7 +++++++ 5 files changed, 74 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 04df6b6b16..9d1416b813 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -88,7 +88,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 38; + api["API_REVISION"] = 39; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); MWBase::Environment::get().getStateManager()->requestQuit(); @@ -97,6 +97,28 @@ namespace MWLua context.mLuaEvents->addGlobalEvent( { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); }; + api["getContentList"] = [](sol::this_state lua) -> sol::table { + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + sol::table res(lua, sol::create); + int i = 1; + for (const std::string& s : contentList) + res[i++] = Misc::StringUtils::lowerCase(s); + return res; + }; + api["getContentFileIndex"] = [](std::string_view contentFile) -> sol::optional { + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return i + 1; + return sol::nullopt; + }; + api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string { + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return ESM::RefId(ESM::FormIdRefId(ESM::FormId{ index, int(i) })).serializeText(); + throw std::runtime_error("Content file not found: " + std::string(contentFile)); + }; addTimeBindings(api, context, false); api["magic"] = initCoreMagicBindings(context); api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); @@ -189,6 +211,12 @@ namespace MWLua MWWorld::Ptr newPtr = ptr.getClass().copyToCell(ptr, *cell, count.value_or(1)); return GObject(newPtr); }; + api["getObjectByFormId"] = [](std::string_view formIdStr) -> GObject { + ESM::RefId refId = ESM::RefId::deserializeText(formIdStr); + if (!refId.is()) + throw std::runtime_error("FormId expected, got " + std::string(formIdStr) + "; use core.getFormId"); + return GObject(refId.getIf()->getValue()); + }; // Creates a new record in the world database. api["createRecord"] = sol::overload( diff --git a/apps/openmw/mwlua/nearbybindings.cpp b/apps/openmw/mwlua/nearbybindings.cpp index c9f9163648..88c4907d81 100644 --- a/apps/openmw/mwlua/nearbybindings.cpp +++ b/apps/openmw/mwlua/nearbybindings.cpp @@ -124,6 +124,13 @@ namespace MWLua }); }; + api["getObjectByFormId"] = [](std::string_view formIdStr) -> LObject { + ESM::RefId refId = ESM::RefId::deserializeText(formIdStr); + if (!refId.is()) + throw std::runtime_error("FormId expected, got " + std::string(formIdStr) + "; use core.getFormId"); + return LObject(refId.getIf()->getValue()); + }; + api["activators"] = LObjectList{ worldView->getActivatorsInScene() }; api["actors"] = LObjectList{ worldView->getActorsInScene() }; api["containers"] = LObjectList{ worldView->getContainersInScene() }; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index fa56a1c80e..9661d7bb14 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -102,6 +102,30 @@ -- print( myMsg('Hello {name}!', {name='World'}) ) +--- +-- Return the current load order (list of content file names). +-- @function [parent=#core] getContentList +-- @return #list<#string> + +--- +-- Return the index of a specific content file in the load order (or `nil` if there is no such content file). +-- @function [parent=#core] getContentFileIndex +-- @param #string contentFile +-- @return #number + +--- +-- Construct FormId string from content file name and the index in the file. +-- @function [parent=#core] getFormId +-- @param #string contentFile +-- @param #number index +-- @return #string +-- @usage if obj.recordId == core.getFormId('Skyrim.esm', 0x4d7da) then ... end +-- @usage -- local scripts +-- local obj = nearby.getObjectByFormId(core.getFormId('Morrowind.esm', 128964)) +-- @usage -- global scripts +-- local obj = world.getObjectByFormId(core.getFormId('Morrowind.esm', 128964)) + + --- -- Any object that exists in the game world and has a specific location. -- Player, actors, items, and statics are game objects. diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index e28f867d38..27fa442621 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -26,6 +26,13 @@ -- Everything that can be picked up in the nearby. -- @field [parent=#nearby] openmw.core#ObjectList items +--- +-- Return an object by RefNum/FormId. +-- @function [parent=#nearby] getObjectByFormId +-- @param #string formId String returned by `core.getFormId` +-- @return openmw.core#GameObject +-- @usage local obj = nearby.getObjectByFormId(core.getFormId('Morrowind.esm', 128964)) + --- -- @type COLLISION_TYPE -- @field [parent=#COLLISION_TYPE] #number World diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 2cc80063fd..0977825313 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -65,6 +65,13 @@ -- @function [parent=#world] isWorldPaused -- @return #boolean +--- +-- Return an object by RefNum/FormId. +-- @function [parent=#world] getObjectByFormId +-- @param #string formId String returned by `core.getFormId` +-- @return openmw.core#GameObject +-- @usage local obj = world.getObjectByFormId(core.getFormId('Morrowind.esm', 128964)) + --- -- Create a new instance of the given record. -- After creation the object is in the disabled state. Use :teleport to place to the world or :moveInto to put it into a container or an inventory. From 3b5849add8f2122dfa06206cb234d228b85c1dd6 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 7 Jun 2023 20:56:57 +0200 Subject: [PATCH 3/4] Move content files functions to `core.contentFiles` and add `obj.contentFile` --- apps/openmw/mwlua/luabindings.cpp | 39 +++++++++++++++++----------- apps/openmw/mwlua/objectbindings.cpp | 7 +++++ files/lua_api/openmw/core.lua | 19 +++++++++++--- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 9d1416b813..a25f1db124 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -84,6 +84,29 @@ namespace MWLua // api["resume"] = []() {}; } + static sol::table initContentFilesBindings(sol::state_view& lua) + { + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + sol::table list(lua, sol::create); + for (size_t i = 0; i < contentList.size(); ++i) + list[i + 1] = Misc::StringUtils::lowerCase(contentList[i]); + sol::table res(lua, sol::create); + res["list"] = LuaUtil::makeReadOnly(list); + res["indexOf"] = [&contentList](std::string_view contentFile) -> sol::optional { + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return i + 1; + return sol::nullopt; + }; + res["has"] = [&contentList](std::string_view contentFile) -> bool { + for (size_t i = 0; i < contentList.size(); ++i) + if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) + return true; + return false; + }; + return LuaUtil::makeReadOnly(res); + } + static sol::table initCorePackage(const Context& context) { auto* lua = context.mLua; @@ -97,21 +120,7 @@ namespace MWLua context.mLuaEvents->addGlobalEvent( { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); }; - api["getContentList"] = [](sol::this_state lua) -> sol::table { - const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); - sol::table res(lua, sol::create); - int i = 1; - for (const std::string& s : contentList) - res[i++] = Misc::StringUtils::lowerCase(s); - return res; - }; - api["getContentFileIndex"] = [](std::string_view contentFile) -> sol::optional { - const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); - for (size_t i = 0; i < contentList.size(); ++i) - if (Misc::StringUtils::ciEqual(contentList[i], contentFile)) - return i + 1; - return sol::nullopt; - }; + api["contentFiles"] = initContentFilesBindings(lua->sol()); api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string { const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); for (size_t i = 0; i < contentList.size(); ++i) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 01ef08ffdb..d72d778688 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -145,6 +145,13 @@ namespace MWLua void addBasicBindings(sol::usertype& objectT, const Context& context) { objectT["id"] = sol::readonly_property([](const ObjectT& o) -> std::string { return o.id().toString(); }); + objectT["contentFile"] = sol::readonly_property([](const ObjectT& o) -> sol::optional { + int contentFileIndex = o.id().mContentFile; + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + if (contentFileIndex < 0 || contentFileIndex >= static_cast(contentList.size())) + return sol::nullopt; + return Misc::StringUtils::lowerCase(contentList[contentFileIndex]); + }); 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(); }); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 9661d7bb14..78fd395d91 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -103,16 +103,26 @@ --- --- Return the current load order (list of content file names). --- @function [parent=#core] getContentList --- @return #list<#string> +-- @{#ContentFiles}: functions working with the list of currently loaded content files. +-- @field [parent=#core] #ContentFiles contentFiles + +--- +-- Functions working with the list of currently loaded content files. +-- @type ContentFiles +-- @field #list<#string> list The current load order (list of content file names). --- -- Return the index of a specific content file in the load order (or `nil` if there is no such content file). --- @function [parent=#core] getContentFileIndex +-- @function [parent=#ContentFiles] indexOf -- @param #string contentFile -- @return #number +--- +-- Check if the content file with given name present in the load order. +-- @function [parent=#ContentFiles] has +-- @param #string contentFile +-- @return #boolean + --- -- Construct FormId string from content file name and the index in the file. -- @function [parent=#core] getFormId @@ -132,6 +142,7 @@ -- @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 #string contentFile Lower cased file name of the content file that defines this object; nil for dynamically created objects. -- @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). From e84ef1d5d9e3205241c2d898fd73e58840eba7fa Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 7 Jun 2023 22:20:35 +0200 Subject: [PATCH 4/4] More Lua documentation about FormId --- files/lua_api/openmw/core.lua | 6 ++++++ files/lua_api/openmw/nearby.lua | 3 +++ files/lua_api/openmw/world.lua | 3 +++ 3 files changed, 12 insertions(+) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 78fd395d91..eefdc322e6 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -125,11 +125,17 @@ --- -- Construct FormId string from content file name and the index in the file. +-- In ESM3 games (e.g. Morrowind) FormIds are used to reference game objects. +-- In ESM4 games (e.g. Skyrim) FormIds are used both for game objects and as record ids. -- @function [parent=#core] getFormId -- @param #string contentFile -- @param #number index -- @return #string -- @usage if obj.recordId == core.getFormId('Skyrim.esm', 0x4d7da) then ... end +-- @usage -- In ESM3 content files (e.g. Morrowind) ids are human-readable strings +-- obj.ownerFactionId = 'blades' +-- -- In ESM4 (e.g. Skyrim) ids should be constructed using `core.getFormId`: +-- obj.ownerFactionId = core.getFormId('Skyrim.esm', 0x72834) -- @usage -- local scripts -- local obj = nearby.getObjectByFormId(core.getFormId('Morrowind.esm', 128964)) -- @usage -- global scripts diff --git a/files/lua_api/openmw/nearby.lua b/files/lua_api/openmw/nearby.lua index 27fa442621..bec284a10b 100644 --- a/files/lua_api/openmw/nearby.lua +++ b/files/lua_api/openmw/nearby.lua @@ -28,6 +28,9 @@ --- -- Return an object by RefNum/FormId. +-- Note: the function always returns @{openmw.core#GameObject} and doesn't validate that +-- the object exists in the game world. If it doesn't exist or not yet loaded to memory), +-- then `obj:isValid()` will be `false`. -- @function [parent=#nearby] getObjectByFormId -- @param #string formId String returned by `core.getFormId` -- @return openmw.core#GameObject diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 0977825313..5f7a46f284 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -67,6 +67,9 @@ --- -- Return an object by RefNum/FormId. +-- Note: the function always returns @{openmw.core#GameObject} and doesn't validate that +-- the object exists in the game world. If it doesn't exist or not yet loaded to memory), +-- then `obj:isValid()` will be `false`. -- @function [parent=#world] getObjectByFormId -- @param #string formId String returned by `core.getFormId` -- @return openmw.core#GameObject