From 9929c35021bfc6c1bec1b677c85f6d732b7e01d6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 31 May 2025 15:43:50 +0200 Subject: [PATCH 1/8] Correctly format single digit hex values --- CHANGELOG.md | 1 + components/misc/color.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4117e86054..fa0c43be25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -235,6 +235,7 @@ Bug #8462: Crashes when resizing the window on macOS Bug #8465: Blue screen w/ antialiasing and post-processing on macOS Bug #8503: Camera does not handle NaN gracefully + Bug #8541: Lua: util.color:asHex produces wrong output for some colors Feature #1415: Infinite fall failsafe Feature #2566: Handle NAM9 records for manual cell references Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking diff --git a/components/misc/color.cpp b/components/misc/color.cpp index 5b0278570a..b6e7be1fa3 100644 --- a/components/misc/color.cpp +++ b/components/misc/color.cpp @@ -47,7 +47,10 @@ namespace Misc for (size_t i = 0; i < rgb.size(); i++) { int b = static_cast(rgb[i] * 255.0f); - auto [_, ec] = std::to_chars(result.data() + i * 2, result.data() + (i + 1) * 2, b, 16); + char* start = result.data() + i * 2; + if (b < 16) + start++; + auto [_, ec] = std::to_chars(start, result.data() + (i + 1) * 2, b, 16); if (ec != std::errc()) throw std::logic_error("Error when converting number to base 16"); } From 7113cef501d24f5de61f41db2ce49a17567a5e85 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 2 Jun 2025 00:59:32 +0300 Subject: [PATCH 2/8] Avoid negative x pow UB in linear bloom --- files/data/shaders/bloomlinear.omwfx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/files/data/shaders/bloomlinear.omwfx b/files/data/shaders/bloomlinear.omwfx index 4c583a9a89..c3e298772a 100644 --- a/files/data/shaders/bloomlinear.omwfx +++ b/files/data/shaders/bloomlinear.omwfx @@ -80,8 +80,9 @@ shared { radius = max(radius, 0.1); // hack: make the radius wider on the screen edges // (makes things in the corner of the screen look less "wrong" with not-extremely-low FOVs) - radius *= pow(texcoord.x*2.0-1.0, 2.0)+1.0; - radius *= pow(texcoord.y*2.0-1.0, 2.0)+1.0; + texcoord = texcoord * 2.0 - vec2(1.0); + radius *= texcoord.x * texcoord.x + 1.0; + radius *= texcoord.y * texcoord.y + 1.0; return radius; } vec3 powv(vec3 a, float x) From e2cf80e3a6a2d92561f1df98ef3647457290e588 Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Tue, 3 Jun 2025 19:34:10 -0600 Subject: [PATCH 3/8] Protect more bindings from non-finite numbers --- apps/openmw/mwlua/animationbindings.cpp | 5 ++++- apps/openmw/mwlua/worldbindings.cpp | 8 +++++-- components/misc/finitenumbers.hpp | 29 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 22ebd7e33a..2f6f60ea3e 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -99,6 +100,8 @@ namespace MWLua sol::table initAnimationPackage(const Context& context) { + using FiniteFloat = Misc::FiniteFloat; + auto view = context.sol(); auto mechanics = MWBase::Environment::get().getMechanicsManager(); auto world = MWBase::Environment::get().getWorld(); @@ -197,7 +200,7 @@ namespace MWLua return speed; return sol::nullopt; }; - api["setSpeed"] = [](const sol::object& object, std::string groupname, float speed) { + api["setSpeed"] = [](const sol::object& object, std::string groupname, const FiniteFloat speed) { getMutableAnimationOrThrow(ObjectVariant(object))->adjustSpeedMult(groupname, speed); }; api["getActiveGroup"] = [](const sol::object& object, MWRender::BoneGroup boneGroup) -> std::string_view { diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index ac7bd307cf..a4352c215a 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" @@ -56,10 +57,13 @@ namespace MWLua static void addWorldTimeBindings(sol::table& api, const Context& context) { + using FiniteDouble = Misc::FiniteDouble; + using FiniteFloat = Misc::FiniteFloat; + MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager(); - api["setGameTimeScale"] = [timeManager](double scale) { timeManager->setGameTimeScale(scale); }; - api["setSimulationTimeScale"] = [context, timeManager](float scale) { + api["setGameTimeScale"] = [timeManager](const FiniteDouble scale) { timeManager->setGameTimeScale(scale); }; + api["setSimulationTimeScale"] = [context, timeManager](const FiniteFloat scale) { context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); }); }; diff --git a/components/misc/finitenumbers.hpp b/components/misc/finitenumbers.hpp index 3d166ee26d..7d27987c5f 100644 --- a/components/misc/finitenumbers.hpp +++ b/components/misc/finitenumbers.hpp @@ -8,6 +8,18 @@ namespace Misc { + struct FiniteDouble + { + double mValue; + FiniteDouble(double v) + { + if (!std::isfinite(v)) + throw std::invalid_argument("Value must be a finite number"); + mValue = v; + } + operator double() const { return mValue; } + }; + struct FiniteFloat { float mValue; @@ -23,8 +35,25 @@ namespace Misc namespace sol { + using FiniteDouble = Misc::FiniteDouble; using FiniteFloat = Misc::FiniteFloat; + template + bool sol_lua_check( + sol::types, lua_State* L, int index, Handler&& handler, sol::stack::record& tracking) + { + bool success = sol::stack::check(L, lua_absindex(L, index), handler); + tracking.use(1); + return success; + } + + static FiniteDouble sol_lua_get(sol::types, lua_State* L, int index, sol::stack::record& tracking) + { + double val = sol::stack::get(L, lua_absindex(L, index)); + tracking.use(1); + return FiniteDouble(val); + } + template bool sol_lua_check( sol::types, lua_State* L, int index, Handler&& handler, sol::stack::record& tracking) From 0d96d71be685dc22562dda9e075f4a8de3a2e2c3 Mon Sep 17 00:00:00 2001 From: Aussiemon Date: Wed, 4 Jun 2025 11:40:53 -0600 Subject: [PATCH 4/8] Change World.setGameTimeScale to float --- apps/openmw/mwlua/worldbindings.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index a4352c215a..c960c4e9e4 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -57,12 +57,11 @@ namespace MWLua static void addWorldTimeBindings(sol::table& api, const Context& context) { - using FiniteDouble = Misc::FiniteDouble; using FiniteFloat = Misc::FiniteFloat; MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager(); - api["setGameTimeScale"] = [timeManager](const FiniteDouble scale) { timeManager->setGameTimeScale(scale); }; + api["setGameTimeScale"] = [timeManager](const FiniteFloat scale) { timeManager->setGameTimeScale(scale); }; api["setSimulationTimeScale"] = [context, timeManager](const FiniteFloat scale) { context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); }); }; From 360abd9b908d89ce42a2d9cb4f81abf2fc8751f5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 26 May 2025 19:34:03 +0200 Subject: [PATCH 5/8] Render openmw.animation inaccessible in menu scripts --- apps/openmw/mwlua/luabindings.cpp | 3 ++- docs/source/reference/lua-scripting/tables/packages.rst | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 8debbe153d..30bbab5be7 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -32,7 +32,6 @@ namespace MWLua sol::state_view lua = context.mLua->unsafeState(); MWWorld::DateTimeManager* tm = MWBase::Environment::get().getWorld()->getTimeManager(); return { - { "openmw.animation", initAnimationPackage(context) }, { "openmw.async", LuaUtil::getAsyncPackageInitializer( lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) }, @@ -47,6 +46,7 @@ namespace MWLua initObjectBindingsForGlobalScripts(context); initCellBindingsForGlobalScripts(context); return { + { "openmw.animation", initAnimationPackage(context) }, { "openmw.core", initCorePackage(context) }, { "openmw.types", initTypesPackage(context) }, { "openmw.world", initWorldPackage(context) }, @@ -59,6 +59,7 @@ namespace MWLua initCellBindingsForLocalScripts(context); LocalScripts::initializeSelfPackage(context); return { + { "openmw.animation", initAnimationPackage(context) }, { "openmw.core", initCorePackage(context) }, { "openmw.types", initTypesPackage(context) }, { "openmw.nearby", initNearbyPackage(context) }, diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index d0aa32abea..99edbb8378 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -4,7 +4,8 @@ |:ref:`openmw.ambient ` | by player and menu | | Controls background sounds for given player. | | | scripts | | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.animation ` | everywhere | | Animation controls | +|:ref:`openmw.animation ` | by global, local, | | Animation controls | +| | and player scripts | | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async ` | everywhere | | Timers and callbacks. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ From c05d2d1d38369f75122623e9aa28336bf73dd4a3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 31 May 2025 17:40:22 +0200 Subject: [PATCH 6/8] Restrict openmw.animation to local scripts --- apps/openmw/mwlua/animationbindings.cpp | 230 +++++++++--------- apps/openmw/mwlua/luabindings.cpp | 1 - .../lua-scripting/tables/packages.rst | 4 +- files/data/scripts/omw/console/global.lua | 1 - files/lua_api/openmw/animation.lua | 24 +- 5 files changed, 123 insertions(+), 137 deletions(-) diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 2f6f60ea3e..776ec2cee0 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -23,79 +23,67 @@ namespace MWLua { - using BlendMask = MWRender::Animation::BlendMask; - using BoneGroup = MWRender::Animation::BoneGroup; - using Priority = MWMechanics::Priority; - using AnimationPriorities = MWRender::Animation::AnimPriority; - - MWWorld::Ptr getMutablePtrOrThrow(const ObjectVariant& variant) + namespace { - if (variant.isLObject()) - throw std::runtime_error("Local scripts can only modify animations of the object they are attached to."); + using BlendMask = MWRender::Animation::BlendMask; + using BoneGroup = MWRender::Animation::BoneGroup; + using Priority = MWMechanics::Priority; + using AnimationPriorities = MWRender::Animation::AnimPriority; - MWWorld::Ptr ptr = variant.ptr(); - if (ptr.isEmpty()) - throw std::runtime_error("Invalid object"); - if (!ptr.getRefData().isEnabled()) - throw std::runtime_error("Can't use a disabled object"); - - return ptr; - } - - MWWorld::Ptr getPtrOrThrow(const ObjectVariant& variant) - { - MWWorld::Ptr ptr = variant.ptr(); - if (ptr.isEmpty()) - throw std::runtime_error("Invalid object"); - - return ptr; - } - - MWRender::Animation* getMutableAnimationOrThrow(const ObjectVariant& variant) - { - MWWorld::Ptr ptr = getMutablePtrOrThrow(variant); - auto world = MWBase::Environment::get().getWorld(); - MWRender::Animation* anim = world->getAnimation(ptr); - if (!anim) - throw std::runtime_error("Object has no animation"); - return anim; - } - - const MWRender::Animation* getConstAnimationOrThrow(const ObjectVariant& variant) - { - MWWorld::Ptr ptr = getPtrOrThrow(variant); - auto world = MWBase::Environment::get().getWorld(); - const MWRender::Animation* anim = world->getAnimation(ptr); - if (!anim) - throw std::runtime_error("Object has no animation"); - return anim; - } - - static AnimationPriorities getPriorityArgument(const sol::table& args) - { - auto asPriorityEnum = args.get>("priority"); - if (asPriorityEnum) - return asPriorityEnum.value(); - - auto asTable = args.get>("priority"); - if (asTable) + const MWWorld::Ptr& getMutablePtrOrThrow(const Object& object) { - AnimationPriorities priorities = AnimationPriorities(Priority::Priority_Default); - for (const auto& entry : asTable.value()) - { - if (!entry.first.is() || !entry.second.is()) - throw std::runtime_error("Priority table must consist of BoneGroup-Priority pairs only"); - auto group = entry.first.as(); - auto priority = entry.second.as(); - if (group < 0 || group >= BoneGroup::Num_BoneGroups) - throw std::runtime_error("Invalid bonegroup: " + std::to_string(group)); - priorities[group] = priority; - } + const MWWorld::Ptr& ptr = object.ptr(); + if (!ptr.getRefData().isEnabled()) + throw std::runtime_error("Can't use a disabled object"); - return priorities; + return ptr; } - return Priority::Priority_Default; + MWRender::Animation* getMutableAnimationOrThrow(const Object& object) + { + const MWWorld::Ptr& ptr = getMutablePtrOrThrow(object); + auto world = MWBase::Environment::get().getWorld(); + MWRender::Animation* anim = world->getAnimation(ptr); + if (!anim) + throw std::runtime_error("Object has no animation"); + return anim; + } + + const MWRender::Animation* getConstAnimationOrThrow(const Object& object) + { + auto world = MWBase::Environment::get().getWorld(); + const MWRender::Animation* anim = world->getAnimation(object.ptr()); + if (!anim) + throw std::runtime_error("Object has no animation"); + return anim; + } + + AnimationPriorities getPriorityArgument(const sol::table& args) + { + auto asPriorityEnum = args.get>("priority"); + if (asPriorityEnum) + return asPriorityEnum.value(); + + auto asTable = args.get>("priority"); + if (asTable) + { + AnimationPriorities priorities = AnimationPriorities(Priority::Priority_Default); + for (const auto& entry : asTable.value()) + { + if (!entry.first.is() || !entry.second.is()) + throw std::runtime_error("Priority table must consist of BoneGroup-Priority pairs only"); + auto group = entry.first.as(); + auto priority = entry.second.as(); + if (group < 0 || group >= BoneGroup::Num_BoneGroups) + throw std::runtime_error("Invalid bonegroup: " + std::to_string(group)); + priorities[group] = priority; + } + + return priorities; + } + + return Priority::Priority_Default; + } } sol::table initAnimationPackage(const Context& context) @@ -104,7 +92,6 @@ namespace MWLua auto view = context.sol(); auto mechanics = MWBase::Environment::get().getMechanicsManager(); - auto world = MWBase::Environment::get().getWorld(); sol::table api(view, sol::create); @@ -145,95 +132,95 @@ namespace MWLua { "RightArm", BoneGroup::BoneGroup_RightArm }, })); - api["hasAnimation"] = [world](const sol::object& object) -> bool { - return world->getAnimation(getPtrOrThrow(ObjectVariant(object))) != nullptr; + api["hasAnimation"] = [](const LObject& object) -> bool { + return MWBase::Environment::get().getWorld()->getAnimation(object.ptr()) != nullptr; }; // equivalent to MWScript's SkipAnim - api["skipAnimationThisFrame"] = [mechanics](const sol::object& object) { - MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + api["skipAnimationThisFrame"] = [mechanics](const SelfObject& object) { + const MWWorld::Ptr& ptr = getMutablePtrOrThrow(object); // This sets a flag that is only used during the update pass, so // there's no need to queue mechanics->skipAnimation(ptr); }; - api["getTextKeyTime"] = [](const sol::object& object, std::string_view key) -> sol::optional { - float time = getConstAnimationOrThrow(ObjectVariant(object))->getTextKeyTime(key); + api["getTextKeyTime"] = [](const LObject& object, std::string_view key) -> sol::optional { + float time = getConstAnimationOrThrow(object)->getTextKeyTime(key); if (time >= 0.f) return time; return sol::nullopt; }; - api["isPlaying"] = [](const sol::object& object, std::string_view groupname) { - return getConstAnimationOrThrow(ObjectVariant(object))->isPlaying(groupname); + api["isPlaying"] = [](const LObject& object, std::string_view groupname) { + return getConstAnimationOrThrow(object)->isPlaying(groupname); }; - api["getCurrentTime"] = [](const sol::object& object, std::string_view groupname) -> sol::optional { - float time = getConstAnimationOrThrow(ObjectVariant(object))->getCurrentTime(groupname); + api["getCurrentTime"] = [](const LObject& object, std::string_view groupname) -> sol::optional { + float time = getConstAnimationOrThrow(object)->getCurrentTime(groupname); if (time >= 0.f) return time; return sol::nullopt; }; - api["isLoopingAnimation"] = [](const sol::object& object, std::string_view groupname) { - return getConstAnimationOrThrow(ObjectVariant(object))->isLoopingAnimation(groupname); + api["isLoopingAnimation"] = [](const LObject& object, std::string_view groupname) { + return getConstAnimationOrThrow(object)->isLoopingAnimation(groupname); }; - api["cancel"] = [](const sol::object& object, std::string_view groupname) { - return getMutableAnimationOrThrow(ObjectVariant(object))->disable(groupname); + api["cancel"] = [](const SelfObject& object, std::string_view groupname) { + return getMutableAnimationOrThrow(object)->disable(groupname); }; - api["setLoopingEnabled"] = [](const sol::object& object, std::string_view groupname, bool enabled) { - return getMutableAnimationOrThrow(ObjectVariant(object))->setLoopingEnabled(groupname, enabled); + api["setLoopingEnabled"] = [](const SelfObject& object, std::string_view groupname, bool enabled) { + return getMutableAnimationOrThrow(object)->setLoopingEnabled(groupname, enabled); }; // MWRender::Animation::getInfo can also return the current speed multiplier, but this is never used. - api["getCompletion"] = [](const sol::object& object, std::string_view groupname) -> sol::optional { + api["getCompletion"] = [](const LObject& object, std::string_view groupname) -> sol::optional { float completion = 0.f; - if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, &completion)) + if (getConstAnimationOrThrow(object)->getInfo(groupname, &completion)) return completion; return sol::nullopt; }; - api["getLoopCount"] = [](const sol::object& object, std::string groupname) -> sol::optional { + api["getLoopCount"] = [](const LObject& object, std::string groupname) -> sol::optional { size_t loops = 0; - if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, nullptr, nullptr, &loops)) + if (getConstAnimationOrThrow(object)->getInfo(groupname, nullptr, nullptr, &loops)) return loops; return sol::nullopt; }; - api["getSpeed"] = [](const sol::object& object, std::string groupname) -> sol::optional { + api["getSpeed"] = [](const LObject& object, std::string groupname) -> sol::optional { float speed = 0.f; - if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, nullptr, &speed, nullptr)) + if (getConstAnimationOrThrow(object)->getInfo(groupname, nullptr, &speed, nullptr)) return speed; return sol::nullopt; }; - api["setSpeed"] = [](const sol::object& object, std::string groupname, const FiniteFloat speed) { - getMutableAnimationOrThrow(ObjectVariant(object))->adjustSpeedMult(groupname, speed); + api["setSpeed"] = [](const SelfObject& object, std::string groupname, const FiniteFloat speed) { + getMutableAnimationOrThrow(object)->adjustSpeedMult(groupname, speed); }; - api["getActiveGroup"] = [](const sol::object& object, MWRender::BoneGroup boneGroup) -> std::string_view { + api["getActiveGroup"] = [](const LObject& object, MWRender::BoneGroup boneGroup) -> std::string_view { if (boneGroup < 0 || boneGroup >= BoneGroup::Num_BoneGroups) throw std::runtime_error("Invalid bonegroup: " + std::to_string(boneGroup)); - return getConstAnimationOrThrow(ObjectVariant(object))->getActiveGroup(boneGroup); + return getConstAnimationOrThrow(object)->getActiveGroup(boneGroup); }; // Clears out the animation queue, and cancel any animation currently playing from the queue - api["clearAnimationQueue"] = [mechanics](const sol::object& object, bool clearScripted) { - MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + api["clearAnimationQueue"] = [mechanics](const SelfObject& object, bool clearScripted) { + const MWWorld::Ptr& ptr = getMutablePtrOrThrow(object); mechanics->clearAnimationQueue(ptr, clearScripted); }; // Extended variant of MWScript's PlayGroup and LoopGroup api["playQueued"] = sol::overload( - [mechanics](const sol::object& object, const std::string& groupname, const sol::table& options) { + [mechanics](const SelfObject& object, const std::string& groupname, const sol::table& options) { uint32_t numberOfLoops = options.get_or("loops", std::numeric_limits::max()); float speed = options.get_or("speed", 1.f); std::string startKey = options.get_or("startKey", "start"); std::string stopKey = options.get_or("stopKey", "stop"); bool forceLoop = options.get_or("forceLoop", false); - MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + const MWWorld::Ptr& ptr = getMutablePtrOrThrow(object); mechanics->playAnimationGroupLua(ptr, groupname, numberOfLoops, speed, startKey, stopKey, forceLoop); }, - [mechanics](const sol::object& object, const std::string& groupname) { - MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); + [mechanics](const SelfObject& object, const std::string& groupname) { + const MWWorld::Ptr& ptr = getMutablePtrOrThrow(object); mechanics->playAnimationGroupLua( ptr, groupname, std::numeric_limits::max(), 1, "start", "stop", false); }); - api["playBlended"] = [](const sol::object& object, std::string_view groupName, const sol::table& options) { + api["playBlended"] = [](const SelfObject& object, std::string_view groupName, const sol::table& options) { uint32_t loops = options.get_or("loops", 0u); MWRender::Animation::AnimPriority priority = getPriorityArgument(options); BlendMask blendMask = options.get_or("blendMask", BlendMask::BlendMask_All); @@ -246,33 +233,32 @@ namespace MWLua const std::string lowerGroup = Misc::StringUtils::lowerCase(groupName); - auto animation = getMutableAnimationOrThrow(ObjectVariant(object)); + auto animation = getMutableAnimationOrThrow(object); animation->play(lowerGroup, priority, blendMask, autoDisable, speed, start, stop, startPoint, loops, forceLoop || animation->isLoopingAnimation(lowerGroup)); }; - api["hasGroup"] = [](const sol::object& object, std::string_view groupname) -> bool { - const MWRender::Animation* anim = getConstAnimationOrThrow(ObjectVariant(object)); + api["hasGroup"] = [](const LObject& object, std::string_view groupname) -> bool { + const MWRender::Animation* anim = getConstAnimationOrThrow(object); return anim->hasAnimation(groupname); }; // Note: This checks the nodemap, and does not read the scene graph itself, and so should be thread safe. - api["hasBone"] = [](const sol::object& object, std::string_view bonename) -> bool { - const MWRender::Animation* anim = getConstAnimationOrThrow(ObjectVariant(object)); + api["hasBone"] = [](const LObject& object, std::string_view bonename) -> bool { + const MWRender::Animation* anim = getConstAnimationOrThrow(object); return anim->getNode(bonename) != nullptr; }; - api["addVfx"] = [context]( - const sol::object& object, std::string_view model, sol::optional options) { + api["addVfx"] = [context](const SelfObject& object, std::string_view model, sol::optional options) { if (options) { context.mLuaManager->addAction( - [object = ObjectVariant(object), model = std::string(model), + [object = Object(object), model = std::string(model), effectId = options->get_or("vfxId", ""), loop = options->get_or("loop", false), boneName = options->get_or("boneName", ""), particleTexture = options->get_or("particleTextureOverride", ""), useAmbientLight = options->get_or("useAmbientLight", true)] { - MWRender::Animation* anim = getMutableAnimationOrThrow(ObjectVariant(object)); + MWRender::Animation* anim = getMutableAnimationOrThrow(object); anim->addEffect(model, effectId, loop, boneName, particleTexture, useAmbientLight); }, @@ -281,7 +267,7 @@ namespace MWLua else { context.mLuaManager->addAction( - [object = ObjectVariant(object), model = std::string(model)] { + [object = Object(object), model = std::string(model)] { MWRender::Animation* anim = getMutableAnimationOrThrow(object); anim->addEffect(model, ""); }, @@ -289,18 +275,18 @@ namespace MWLua } }; - api["removeVfx"] = [context](const sol::object& object, std::string_view effectId) { + api["removeVfx"] = [context](const SelfObject& object, std::string_view effectId) { context.mLuaManager->addAction( - [object = ObjectVariant(object), effectId = std::string(effectId)] { + [object = Object(object), effectId = std::string(effectId)] { MWRender::Animation* anim = getMutableAnimationOrThrow(object); anim->removeEffect(effectId); }, "removeVfxAction"); }; - api["removeAllVfx"] = [context](const sol::object& object) { + api["removeAllVfx"] = [context](const SelfObject& object) { context.mLuaManager->addAction( - [object = ObjectVariant(object)] { + [object = Object(object)] { MWRender::Animation* anim = getMutableAnimationOrThrow(object); anim->removeEffects(); }, @@ -313,10 +299,9 @@ namespace MWLua sol::table initWorldVfxBindings(const Context& context) { sol::table api(context.mLua->unsafeState(), sol::create); - auto world = MWBase::Environment::get().getWorld(); api["spawn"] - = [world, context](std::string_view model, const osg::Vec3f& worldPos, sol::optional options) { + = [context](std::string_view model, const osg::Vec3f& worldPos, sol::optional options) { if (options) { bool magicVfx = options->get_or("mwMagicVfx", true); @@ -324,16 +309,19 @@ namespace MWLua float scale = options->get_or("scale", 1.f); bool useAmbientLight = options->get_or("useAmbientLight", true); context.mLuaManager->addAction( - [world, model = VFS::Path::Normalized(model), texture = std::move(texture), worldPos, scale, + [model = VFS::Path::Normalized(model), texture = std::move(texture), worldPos, scale, magicVfx, useAmbientLight]() { - world->spawnEffect(model, texture, worldPos, scale, magicVfx, useAmbientLight); + MWBase::Environment::get().getWorld()->spawnEffect( + model, texture, worldPos, scale, magicVfx, useAmbientLight); }, "openmw.vfx.spawn"); } else { - context.mLuaManager->addAction([world, model = VFS::Path::Normalized(model), - worldPos]() { world->spawnEffect(model, "", worldPos, 1.f); }, + context.mLuaManager->addAction( + [model = VFS::Path::Normalized(model), worldPos]() { + MWBase::Environment::get().getWorld()->spawnEffect(model, "", worldPos, 1.f); + }, "openmw.vfx.spawn"); } }; diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 30bbab5be7..0e9c56a482 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -46,7 +46,6 @@ namespace MWLua initObjectBindingsForGlobalScripts(context); initCellBindingsForGlobalScripts(context); return { - { "openmw.animation", initAnimationPackage(context) }, { "openmw.core", initCorePackage(context) }, { "openmw.types", initTypesPackage(context) }, { "openmw.world", initWorldPackage(context) }, diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index 99edbb8378..e66926e5e4 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -4,8 +4,8 @@ |:ref:`openmw.ambient ` | by player and menu | | Controls background sounds for given player. | | | scripts | | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ -|:ref:`openmw.animation ` | by global, local, | | Animation controls | -| | and player scripts | | +|:ref:`openmw.animation ` | by local and | | Animation controls | +| | player scripts | | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async ` | everywhere | | Timers and callbacks. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ diff --git a/files/data/scripts/omw/console/global.lua b/files/data/scripts/omw/console/global.lua index 0ec557b376..8b3e18eb99 100644 --- a/files/data/scripts/omw/console/global.lua +++ b/files/data/scripts/omw/console/global.lua @@ -29,7 +29,6 @@ local env = { aux_util = require('openmw_aux.util'), calendar = require('openmw_aux.calendar'), time = require('openmw_aux.time'), - anim = require('openmw.animation'), view = require('openmw_aux.util').deepToString, print = printToConsole, exit = function() player:sendEvent('OMWConsoleExit') end, diff --git a/files/lua_api/openmw/animation.lua b/files/lua_api/openmw/animation.lua index d15241afff..cde046b443 100644 --- a/files/lua_api/openmw/animation.lua +++ b/files/lua_api/openmw/animation.lua @@ -56,7 +56,7 @@ --- -- Skips animations for one frame, equivalent to mwscript's SkipAnim. --- Can be used only in local scripts on self. +-- Can only be used on self. -- @function [parent=#animation] skipAnimationThisFrame -- @param openmw.core#GameObject actor @@ -99,14 +99,14 @@ --- -- Cancels and removes the animation group from the list of active animations. --- Can be used only in local scripts on self. +-- Can only be used on self. -- @function [parent=#animation] cancel -- @param openmw.core#GameObject actor -- @param #string groupName --- -- Enables or disables looping for the given animation group. Looping is enabled by default. --- Can be used only in local scripts on self. +-- Can only be used on self. -- @function [parent=#animation] setLoopingEnabled -- @param openmw.core#GameObject actor -- @param #string groupName @@ -136,7 +136,7 @@ --- -- Modifies the playback speed of an animation group. -- Note that this is not sticky and only affects the speed until the currently playing sequence ends. --- Can be used only in local scripts on self. +-- Can only be used on self. -- @function [parent=#animation] setSpeed -- @param openmw.core#GameObject actor -- @param #string groupName @@ -144,7 +144,7 @@ --- -- Clears all animations currently in the animation queue. This affects animations played by mwscript, @{openmw.animation#playQueued}, and ai packages, but does not affect animations played using @{openmw.animation#playBlended}. --- Can be used only in local scripts on self. +-- Can only be used on self. -- @function [parent=#animation] clearAnimationQueue -- @param openmw.core#GameObject actor -- @param #boolean clearScripted whether to keep animation with priority Scripted or not. @@ -153,7 +153,7 @@ -- Acts as a slightly extended version of MWScript's LoopGroup. Plays this animation exclusively -- until it ends, or the queue is cleared using #clearAnimationQueue. Use #clearAnimationQueue and the `startkey` option -- to imitate the behavior of LoopGroup's play modes. --- Can be used only in local scripts on self. +-- Can only be used on self. -- @function [parent=#animation] playQueued -- @param openmw.core#GameObject actor -- @param #string groupName @@ -179,7 +179,7 @@ -- instead of calling this directly. Note that the still hardcoded character controller may at any time and for any reason alter -- or cancel currently playing animations, so making your own calls to this function either directly or through the [AnimationController](interface_animation.html) -- interface may be of limited utility. For now, use openmw.animation#playQueued to script your own animations. --- Can be used only in local scripts on self. +-- Can only be used on self. -- @function [parent=#animation] playBlended -- @param openmw.core#GameObject actor -- @param #string groupName @@ -218,7 +218,7 @@ --- -- Plays a VFX on the actor. --- Can be used only in local scripts on self. Can also be evoked by sending an AddVfx event to the target actor. +-- Can only be used on self. Can also be evoked by sending an AddVfx event to the target actor. -- @function [parent=#animation] addVfx -- @param openmw.core#GameObject actor -- @param #string model path (normally taken from a record such as @{openmw.types#StaticRecord.model} or similar) @@ -249,15 +249,15 @@ --- --- Removes a specific VFX --- Can be used only in local scripts on self. +-- Removes a specific VFX. +-- Can only be used on self. -- @function [parent=#animation] removeVfx -- @param openmw.core#GameObject actor -- @param #number vfxId an integer ID that uniquely identifies the VFX to remove --- --- Removes all vfx from the actor --- Can be used only in local scripts on self. +-- Removes all vfx from the actor. +-- Can only be used on self. -- @function [parent=#animation] removeAllVfx -- @param openmw.core#GameObject actor From 178f21631793d74365c0e777d2063b4fbabbde8d Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 5 Jun 2025 23:58:41 +0200 Subject: [PATCH 7/8] Add Lua test for passing a NaN value --- scripts/data/integration_tests/test_lua_api/global.lua | 7 +++++++ scripts/data/integration_tests/test_lua_api/menu.lua | 1 + 2 files changed, 8 insertions(+) diff --git a/scripts/data/integration_tests/test_lua_api/global.lua b/scripts/data/integration_tests/test_lua_api/global.lua index 225660b858..31d1b040db 100644 --- a/scripts/data/integration_tests/test_lua_api/global.lua +++ b/scripts/data/integration_tests/test_lua_api/global.lua @@ -344,6 +344,13 @@ testing.registerGlobalTest('load while teleporting - teleport', function() landracer:teleport(player.cell, player.position) end) +testing.registerGlobalTest('nan', function() + local nan = 0.0 / 0.0 + local ok, err = pcall(function() world.setGameTimeScale(nan) end) + testing.expectEqual(ok, false) + testing.expectEqual(err, 'Value must be a finite number') +end) + return { engineHandlers = { onUpdate = testing.updateGlobal, diff --git a/scripts/data/integration_tests/test_lua_api/menu.lua b/scripts/data/integration_tests/test_lua_api/menu.lua index 8c6895a5d8..4f8dbd99c6 100644 --- a/scripts/data/integration_tests/test_lua_api/menu.lua +++ b/scripts/data/integration_tests/test_lua_api/menu.lua @@ -72,6 +72,7 @@ registerGlobalTest('memory limit') registerGlobalTest('vfs') registerGlobalTest('commit crime') registerGlobalTest('record model property') +registerGlobalTest('nan', 'world.setGameTimeScale should not accept nan') registerGlobalTest('player yaw rotation', 'rotating player with controls.yawChange should change rotation') registerGlobalTest('player pitch rotation', 'rotating player with controls.pitchChange should change rotation') From 267ce1ec9bb484fd1e4eabb56658e886b621d55a Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 5 Jun 2025 23:49:30 +0200 Subject: [PATCH 8/8] Reduce code duplication for finite number --- apps/openmw/mwlua/animationbindings.cpp | 2 +- apps/openmw/mwlua/camerabindings.cpp | 2 +- apps/openmw/mwlua/worldbindings.cpp | 2 +- components/misc/finitenumbers.hpp | 59 ++++++++----------------- 4 files changed, 22 insertions(+), 43 deletions(-) diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 2f6f60ea3e..ee096a5a35 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -100,7 +100,7 @@ namespace MWLua sol::table initAnimationPackage(const Context& context) { - using FiniteFloat = Misc::FiniteFloat; + using Misc::FiniteFloat; auto view = context.sol(); auto mechanics = MWBase::Environment::get().getMechanicsManager(); diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index 345ca9af65..c6461209c5 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -16,7 +16,7 @@ namespace MWLua sol::table initCameraPackage(sol::state_view lua) { - using FiniteFloat = Misc::FiniteFloat; + using Misc::FiniteFloat; MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera(); MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager(); diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index c960c4e9e4..c02bad3bd3 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -57,7 +57,7 @@ namespace MWLua static void addWorldTimeBindings(sol::table& api, const Context& context) { - using FiniteFloat = Misc::FiniteFloat; + using Misc::FiniteFloat; MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager(); diff --git a/components/misc/finitenumbers.hpp b/components/misc/finitenumbers.hpp index 7d27987c5f..1670791349 100644 --- a/components/misc/finitenumbers.hpp +++ b/components/misc/finitenumbers.hpp @@ -5,69 +5,48 @@ #include #include +#include namespace Misc { - struct FiniteDouble + template + struct FiniteNumber { - double mValue; - FiniteDouble(double v) + T mValue; + + FiniteNumber(T v) { if (!std::isfinite(v)) throw std::invalid_argument("Value must be a finite number"); mValue = v; } - operator double() const { return mValue; } + + operator T() const { return mValue; } }; - struct FiniteFloat - { - float mValue; - FiniteFloat(float v) - { - if (!std::isfinite(v)) - throw std::invalid_argument("Value must be a finite number"); - mValue = v; - } - operator float() const { return mValue; } - }; + using FiniteDouble = FiniteNumber; + + using FiniteFloat = FiniteNumber; } namespace sol { - using FiniteDouble = Misc::FiniteDouble; - using FiniteFloat = Misc::FiniteFloat; - - template + template bool sol_lua_check( - sol::types, lua_State* L, int index, Handler&& handler, sol::stack::record& tracking) + types>, lua_State* state, int index, Handler&& handler, stack::record& tracking) { - bool success = sol::stack::check(L, lua_absindex(L, index), handler); + bool success = stack::check(state, lua_absindex(state, index), std::forward(handler)); tracking.use(1); return success; } - static FiniteDouble sol_lua_get(sol::types, lua_State* L, int index, sol::stack::record& tracking) + template + static Misc::FiniteNumber sol_lua_get( + types>, lua_State* state, int index, stack::record& tracking) { - double val = sol::stack::get(L, lua_absindex(L, index)); + T value = stack::get(state, lua_absindex(state, index)); tracking.use(1); - return FiniteDouble(val); - } - - template - bool sol_lua_check( - sol::types, lua_State* L, int index, Handler&& handler, sol::stack::record& tracking) - { - bool success = sol::stack::check(L, lua_absindex(L, index), handler); - tracking.use(1); - return success; - } - - static FiniteFloat sol_lua_get(sol::types, lua_State* L, int index, sol::stack::record& tracking) - { - float val = sol::stack::get(L, lua_absindex(L, index)); - tracking.use(1); - return FiniteFloat(val); + return value; } }