From cfef3312d94d9bcf2ea0ff59b08ecea2e085e6c5 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Thu, 31 Jul 2025 21:10:56 -0700 Subject: [PATCH 1/9] Add function --- apps/openmw/mwlua/objectbindings.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 3508fdcd44..9efbd7b32a 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -500,6 +500,32 @@ namespace MWLua (*delayedRemovalFn)(oldPtr); }); }; + objectT["setPosition"] = [context](const GObject& object, const osg::Vec3f& newPos, bool terrainClamp) { + MWWorld::Ptr ptr = object.ptr(); + if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell()) + throw std::runtime_error("Object is either removed or already in the process of teleporting"); + + osg::Vec3f finalPos = newPos; + + if (terrainClamp && ptr.getClass().isActor()) + { + float terrainHeight = -std::numeric_limits::max(); + if (ptr.getCell()->isExterior()) + { + terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt( + finalPos, ptr.getCell()->getCell()->getWorldSpace()); + } + + if (finalPos.z() < terrainHeight) + finalPos.z() = terrainHeight; + } + + context.mLuaManager->addAction( + [object, finalPos] { + MWBase::Environment::get().getWorld()->moveObject(object.ptr(), finalPos, true, false); + }, + "SetPositionAction"); + }; objectT["teleport"] = [removeFn, context](const GObject& object, const sol::object& cellOrName, const osg::Vec3f& pos, const sol::object& options) { MWWorld::CellStore* cell = findCell(cellOrName, pos); From a379cebc1f00fddaa798f3a7a52639b9e7379c81 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Thu, 31 Jul 2025 21:20:23 -0700 Subject: [PATCH 2/9] Add docs --- apps/openmw/mwlua/objectbindings.cpp | 2 +- files/lua_api/openmw/core.lua | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 9efbd7b32a..02e9d67a88 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -500,7 +500,7 @@ namespace MWLua (*delayedRemovalFn)(oldPtr); }); }; - objectT["setPosition"] = [context](const GObject& object, const osg::Vec3f& newPos, bool terrainClamp) { + objectT["setPosition"] = [context](const GObject& object, const osg::Vec3f& newPos, bool terrainClamp = true) { MWWorld::Ptr ptr = object.ptr(); if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell()) throw std::runtime_error("Object is either removed or already in the process of teleporting"); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 054e96674b..78270cd049 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -235,6 +235,16 @@ -- @param self -- @param #number scale Scale desired in game. +--- +-- Moves object to given position. +-- Can be called only from a global script. +-- The effect is not immediate: the position will be updated only in the next +-- frame. Can be called only from a global script. +-- @function [parent=#GameObject] setPosition +-- @param self +-- @param openmw.util#Vector3 position New position. +-- @param #boolean terrainClamp (optional, true by default) If true, actors will be limited to positions above the terrain. + --- -- Moves object to given cell and position. -- Can be called only from a global script. From 855fa1e06eebcfd86a8a4442f730a7c5631281fe Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Thu, 31 Jul 2025 22:08:26 -0700 Subject: [PATCH 3/9] Update to setTransform --- apps/openmw/mwlua/objectbindings.cpp | 17 +++++++++++++---- files/lua_api/openmw/core.lua | 10 +++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 02e9d67a88..6d32240565 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -500,7 +500,8 @@ namespace MWLua (*delayedRemovalFn)(oldPtr); }); }; - objectT["setPosition"] = [context](const GObject& object, const osg::Vec3f& newPos, bool terrainClamp = true) { + objectT["setTransform"] = [context](const GObject& object, const osg::Vec3f& newPos, + const sol::object& newRotObj, bool terrainClamp = true) { MWWorld::Ptr ptr = object.ptr(); if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell()) throw std::runtime_error("Object is either removed or already in the process of teleporting"); @@ -520,11 +521,19 @@ namespace MWLua finalPos.z() = terrainHeight; } + osg::Vec3f rotVec = ptr.getRefData().getPosition().asRotationVec3(); + if (newRotObj != sol::nil) + { + rotVec = toEulerRotation(newRotObj, ptr.getClass().isActor()); + } + context.mLuaManager->addAction( - [object, finalPos] { - MWBase::Environment::get().getWorld()->moveObject(object.ptr(), finalPos, true, false); + [object, finalPos, rotVec] { + auto world = MWBase::Environment::get().getWorld(); + world->moveObject(object.ptr(), finalPos, true, false); + world->rotateObject(object.ptr(), rotVec, MWBase::RotationFlag_none); }, - "SetPositionAction"); + "SetTransformAction"); }; objectT["teleport"] = [removeFn, context](const GObject& object, const sol::object& cellOrName, const osg::Vec3f& pos, const sol::object& options) { diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 78270cd049..64c9717aab 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -236,14 +236,14 @@ -- @param #number scale Scale desired in game. --- --- Moves object to given position. +-- Moves and/or rotates an object to a given position and rotation. -- Can be called only from a global script. --- The effect is not immediate: the position will be updated only in the next --- frame. Can be called only from a global script. --- @function [parent=#GameObject] setPosition +-- The effect is not immediate: the position and rotation will be updated only in the next frame. +-- @function [parent=#GameObject] setTransform -- @param self -- @param openmw.util#Vector3 position New position. --- @param #boolean terrainClamp (optional, true by default) If true, actors will be limited to positions above the terrain. +-- @param openmw.util#Transform rotation New rotation; if missing, then the current rotation is used. +-- @param #boolean terrainClamp (optional, true by default) If true, actors will be restricted to positions above the terrain. --- -- Moves object to given cell and position. From 20b9ca2fe9d9226957ad172328605afbde1be5b8 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 1 Aug 2025 08:03:51 -0700 Subject: [PATCH 4/9] Fix optional vals --- apps/openmw/mwlua/objectbindings.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 6d32240565..d87f0de37d 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -501,10 +501,11 @@ namespace MWLua }); }; objectT["setTransform"] = [context](const GObject& object, const osg::Vec3f& newPos, - const sol::object& newRotObj, bool terrainClamp = true) { + sol::object newRotObj, sol::optional terrainClampOpt) { + bool terrainClamp = terrainClampOpt.value_or(true); MWWorld::Ptr ptr = object.ptr(); if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell()) - throw std::runtime_error("Object is either removed or already in the process of teleporting"); + throw std::runtime_error("Object is either removed or in the process of teleporting"); osg::Vec3f finalPos = newPos; From b6510da81a91106a46fe3cac3e2f5f4f47b49c64 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 1 Aug 2025 08:35:59 -0700 Subject: [PATCH 5/9] Update revision --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b4ef872eb6..57ebeefcfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 86) +set(OPENMW_LUA_API_REVISION 87) set(OPENMW_POSTPROCESSING_API_REVISION 3) set(OPENMW_VERSION_COMMITHASH "") From 966f1f1b09df63dab0e1e39d580245420c01af98 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 1 Aug 2025 08:37:11 -0700 Subject: [PATCH 6/9] Add to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2df2e025e..ddb7c8b569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ Feature #8580: Sort characters in the save loading menu Feature #8597: Lua: Add more built-in event handlers Feature #8629: Expose path grid data to Lua + Feature #8645: Expose direct position/rotation setters 0.49.0 ------ From da8949e40aea9aff0138fb4a296e140df5201bca Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 1 Aug 2025 08:51:27 -0700 Subject: [PATCH 7/9] add setRotation and setPosition --- apps/openmw/mwlua/objectbindings.cpp | 52 ++++++++++++++++++++++++---- files/lua_api/openmw/core.lua | 21 ++++++++++- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index d87f0de37d..386e0b913b 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -501,7 +501,7 @@ namespace MWLua }); }; objectT["setTransform"] = [context](const GObject& object, const osg::Vec3f& newPos, - sol::object newRotObj, sol::optional terrainClampOpt) { + const sol::object& newRotObj, sol::optional terrainClampOpt) { bool terrainClamp = terrainClampOpt.value_or(true); MWWorld::Ptr ptr = object.ptr(); if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell()) @@ -522,11 +522,7 @@ namespace MWLua finalPos.z() = terrainHeight; } - osg::Vec3f rotVec = ptr.getRefData().getPosition().asRotationVec3(); - if (newRotObj != sol::nil) - { - rotVec = toEulerRotation(newRotObj, ptr.getClass().isActor()); - } + osg::Vec3f rotVec = toEulerRotation(newRotObj, ptr.getClass().isActor()); context.mLuaManager->addAction( [object, finalPos, rotVec] { @@ -536,6 +532,50 @@ namespace MWLua }, "SetTransformAction"); }; + objectT["setPosition"] + = [context](const GObject& object, const osg::Vec3f& newPos, sol::optional terrainClampOpt) { + bool terrainClamp = terrainClampOpt.value_or(true); + MWWorld::Ptr ptr = object.ptr(); + if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell()) + throw std::runtime_error("Object is either removed or in the process of teleporting"); + + osg::Vec3f finalPos = newPos; + + if (terrainClamp && ptr.getClass().isActor()) + { + float terrainHeight = -std::numeric_limits::max(); + if (ptr.getCell()->isExterior()) + { + terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt( + finalPos, ptr.getCell()->getCell()->getWorldSpace()); + } + + if (finalPos.z() < terrainHeight) + finalPos.z() = terrainHeight; + } + + context.mLuaManager->addAction( + [object, finalPos] { + auto world = MWBase::Environment::get().getWorld(); + world->moveObject(object.ptr(), finalPos, true, false); + }, + "SetPositionAction"); + }; + objectT["setRotation"] = [context](const GObject& object, const sol::object& newRotObj) { + MWWorld::Ptr ptr = object.ptr(); + if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell()) + throw std::runtime_error("Object is either removed or in the process of teleporting"); + + // newRotObj must always be valid + osg::Vec3f rotVec = toEulerRotation(newRotObj, ptr.getClass().isActor()); + + context.mLuaManager->addAction( + [object, rotVec] { + auto world = MWBase::Environment::get().getWorld(); + world->rotateObject(object.ptr(), rotVec, MWBase::RotationFlag_none); + }, + "SetRotationAction"); + }; objectT["teleport"] = [removeFn, context](const GObject& object, const sol::object& cellOrName, const osg::Vec3f& pos, const sol::object& options) { MWWorld::CellStore* cell = findCell(cellOrName, pos); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 64c9717aab..17064a402a 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -242,9 +242,28 @@ -- @function [parent=#GameObject] setTransform -- @param self -- @param openmw.util#Vector3 position New position. --- @param openmw.util#Transform rotation New rotation; if missing, then the current rotation is used. +-- @param openmw.util#Transform rotation New rotation. -- @param #boolean terrainClamp (optional, true by default) If true, actors will be restricted to positions above the terrain. +--- +-- Moves an object to a given position. +-- Can be called only from a global script. +-- The effect is not immediate: the position will be updated only in the next frame. +-- If both rotation and position need to be updated, use setTransform. +-- @function [parent=#GameObject] setPosition +-- @param self +-- @param openmw.util#Vector3 position New position. +-- @param #boolean terrainClamp (optional, true by default) If true, actors will be restricted to positions above the terrain. + +--- +-- Rotates an object to a given rotation. +-- Can be called only from a global script. +-- The effect is not immediate: the rotation will be updated only in the next frame. +-- If both rotation and position need to be updated, use setTransform. +-- @function [parent=#GameObject] setRotation +-- @param self +-- @param openmw.util#Transform rotation New rotation. + --- -- Moves object to given cell and position. -- Can be called only from a global script. From b0e22658581581e9e1d0457eeaa962865ac449f3 Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 1 Aug 2025 08:57:44 -0700 Subject: [PATCH 8/9] Add nil check --- apps/openmw/mwlua/objectbindings.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 386e0b913b..ec33a3dc4e 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -502,6 +502,10 @@ namespace MWLua }; objectT["setTransform"] = [context](const GObject& object, const osg::Vec3f& newPos, const sol::object& newRotObj, sol::optional terrainClampOpt) { + if (newRotObj == sol::nil) + { + throw std::runtime_error("setTransform requires a non-nil rotation argument"); + } bool terrainClamp = terrainClampOpt.value_or(true); MWWorld::Ptr ptr = object.ptr(); if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell()) @@ -562,6 +566,10 @@ namespace MWLua "SetPositionAction"); }; objectT["setRotation"] = [context](const GObject& object, const sol::object& newRotObj) { + if (newRotObj == sol::nil) + { + throw std::runtime_error("setTransform requires a non-nil rotation argument"); + } MWWorld::Ptr ptr = object.ptr(); if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell()) throw std::runtime_error("Object is either removed or in the process of teleporting"); From 0170f38d93f18fd8008037960fce8d8287098e7d Mon Sep 17 00:00:00 2001 From: SkyHasACat Date: Fri, 1 Aug 2025 09:01:33 -0700 Subject: [PATCH 9/9] fix copy --- apps/openmw/mwlua/objectbindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index ec33a3dc4e..e9595932cb 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -568,7 +568,7 @@ namespace MWLua objectT["setRotation"] = [context](const GObject& object, const sol::object& newRotObj) { if (newRotObj == sol::nil) { - throw std::runtime_error("setTransform requires a non-nil rotation argument"); + throw std::runtime_error("setRotation requires a non-nil rotation argument"); } MWWorld::Ptr ptr = object.ptr(); if (ptr.getCellRef().getCount() == 0 || !ptr.isInCell())