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 ------ diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 3508fdcd44..e9595932cb 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -500,6 +500,90 @@ namespace MWLua (*delayedRemovalFn)(oldPtr); }); }; + 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()) + 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; + } + + osg::Vec3f rotVec = toEulerRotation(newRotObj, ptr.getClass().isActor()); + + context.mLuaManager->addAction( + [object, finalPos, rotVec] { + auto world = MWBase::Environment::get().getWorld(); + world->moveObject(object.ptr(), finalPos, true, false); + world->rotateObject(object.ptr(), rotVec, MWBase::RotationFlag_none); + }, + "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) { + if (newRotObj == sol::nil) + { + throw std::runtime_error("setRotation 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"); + + // 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 054e96674b..17064a402a 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -235,6 +235,35 @@ -- @param self -- @param #number scale Scale desired in game. +--- +-- 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 and rotation will be updated only in the next frame. +-- @function [parent=#GameObject] setTransform +-- @param self +-- @param openmw.util#Vector3 position New position. +-- @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.