1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-10-18 16:46:44 +00:00

Merge branch 'OpenMW:master' into master

This commit is contained in:
Andy Lanzone 2025-06-07 20:39:57 -07:00 committed by GitHub
commit 0ca6bc012c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 169 additions and 154 deletions

View file

@ -235,6 +235,7 @@
Bug #8462: Crashes when resizing the window on macOS Bug #8462: Crashes when resizing the window on macOS
Bug #8465: Blue screen w/ antialiasing and post-processing on macOS Bug #8465: Blue screen w/ antialiasing and post-processing on macOS
Bug #8503: Camera does not handle NaN gracefully 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 #1415: Infinite fall failsafe
Feature #2566: Handle NAM9 records for manual cell references Feature #2566: Handle NAM9 records for manual cell references
Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking

View file

@ -5,6 +5,7 @@
#include <components/lua/asyncpackage.hpp> #include <components/lua/asyncpackage.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/lua/utilpackage.hpp> #include <components/lua/utilpackage.hpp>
#include <components/misc/finitenumbers.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
@ -22,86 +23,75 @@
namespace MWLua namespace MWLua
{ {
using BlendMask = MWRender::Animation::BlendMask; namespace
using BoneGroup = MWRender::Animation::BoneGroup;
using Priority = MWMechanics::Priority;
using AnimationPriorities = MWRender::Animation::AnimPriority;
MWWorld::Ptr getMutablePtrOrThrow(const ObjectVariant& variant)
{ {
if (variant.isLObject()) using BlendMask = MWRender::Animation::BlendMask;
throw std::runtime_error("Local scripts can only modify animations of the object they are attached to."); using BoneGroup = MWRender::Animation::BoneGroup;
using Priority = MWMechanics::Priority;
using AnimationPriorities = MWRender::Animation::AnimPriority;
MWWorld::Ptr ptr = variant.ptr(); const MWWorld::Ptr& getMutablePtrOrThrow(const Object& object)
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<sol::optional<Priority>>("priority");
if (asPriorityEnum)
return asPriorityEnum.value();
auto asTable = args.get<sol::optional<sol::table>>("priority");
if (asTable)
{ {
AnimationPriorities priorities = AnimationPriorities(Priority::Priority_Default); const MWWorld::Ptr& ptr = object.ptr();
for (const auto& entry : asTable.value()) if (!ptr.getRefData().isEnabled())
{ throw std::runtime_error("Can't use a disabled object");
if (!entry.first.is<BoneGroup>() || !entry.second.is<Priority>())
throw std::runtime_error("Priority table must consist of BoneGroup-Priority pairs only");
auto group = entry.first.as<BoneGroup>();
auto priority = entry.second.as<Priority>();
if (group < 0 || group >= BoneGroup::Num_BoneGroups)
throw std::runtime_error("Invalid bonegroup: " + std::to_string(group));
priorities[group] = priority;
}
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<sol::optional<Priority>>("priority");
if (asPriorityEnum)
return asPriorityEnum.value();
auto asTable = args.get<sol::optional<sol::table>>("priority");
if (asTable)
{
AnimationPriorities priorities = AnimationPriorities(Priority::Priority_Default);
for (const auto& entry : asTable.value())
{
if (!entry.first.is<BoneGroup>() || !entry.second.is<Priority>())
throw std::runtime_error("Priority table must consist of BoneGroup-Priority pairs only");
auto group = entry.first.as<BoneGroup>();
auto priority = entry.second.as<Priority>();
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) sol::table initAnimationPackage(const Context& context)
{ {
using Misc::FiniteFloat;
auto view = context.sol(); auto view = context.sol();
auto mechanics = MWBase::Environment::get().getMechanicsManager(); auto mechanics = MWBase::Environment::get().getMechanicsManager();
auto world = MWBase::Environment::get().getWorld();
sol::table api(view, sol::create); sol::table api(view, sol::create);
@ -142,95 +132,95 @@ namespace MWLua
{ "RightArm", BoneGroup::BoneGroup_RightArm }, { "RightArm", BoneGroup::BoneGroup_RightArm },
})); }));
api["hasAnimation"] = [world](const sol::object& object) -> bool { api["hasAnimation"] = [](const LObject& object) -> bool {
return world->getAnimation(getPtrOrThrow(ObjectVariant(object))) != nullptr; return MWBase::Environment::get().getWorld()->getAnimation(object.ptr()) != nullptr;
}; };
// equivalent to MWScript's SkipAnim // equivalent to MWScript's SkipAnim
api["skipAnimationThisFrame"] = [mechanics](const sol::object& object) { api["skipAnimationThisFrame"] = [mechanics](const SelfObject& object) {
MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); const MWWorld::Ptr& ptr = getMutablePtrOrThrow(object);
// This sets a flag that is only used during the update pass, so // This sets a flag that is only used during the update pass, so
// there's no need to queue // there's no need to queue
mechanics->skipAnimation(ptr); mechanics->skipAnimation(ptr);
}; };
api["getTextKeyTime"] = [](const sol::object& object, std::string_view key) -> sol::optional<float> { api["getTextKeyTime"] = [](const LObject& object, std::string_view key) -> sol::optional<float> {
float time = getConstAnimationOrThrow(ObjectVariant(object))->getTextKeyTime(key); float time = getConstAnimationOrThrow(object)->getTextKeyTime(key);
if (time >= 0.f) if (time >= 0.f)
return time; return time;
return sol::nullopt; return sol::nullopt;
}; };
api["isPlaying"] = [](const sol::object& object, std::string_view groupname) { api["isPlaying"] = [](const LObject& object, std::string_view groupname) {
return getConstAnimationOrThrow(ObjectVariant(object))->isPlaying(groupname); return getConstAnimationOrThrow(object)->isPlaying(groupname);
}; };
api["getCurrentTime"] = [](const sol::object& object, std::string_view groupname) -> sol::optional<float> { api["getCurrentTime"] = [](const LObject& object, std::string_view groupname) -> sol::optional<float> {
float time = getConstAnimationOrThrow(ObjectVariant(object))->getCurrentTime(groupname); float time = getConstAnimationOrThrow(object)->getCurrentTime(groupname);
if (time >= 0.f) if (time >= 0.f)
return time; return time;
return sol::nullopt; return sol::nullopt;
}; };
api["isLoopingAnimation"] = [](const sol::object& object, std::string_view groupname) { api["isLoopingAnimation"] = [](const LObject& object, std::string_view groupname) {
return getConstAnimationOrThrow(ObjectVariant(object))->isLoopingAnimation(groupname); return getConstAnimationOrThrow(object)->isLoopingAnimation(groupname);
}; };
api["cancel"] = [](const sol::object& object, std::string_view groupname) { api["cancel"] = [](const SelfObject& object, std::string_view groupname) {
return getMutableAnimationOrThrow(ObjectVariant(object))->disable(groupname); return getMutableAnimationOrThrow(object)->disable(groupname);
}; };
api["setLoopingEnabled"] = [](const sol::object& object, std::string_view groupname, bool enabled) { api["setLoopingEnabled"] = [](const SelfObject& object, std::string_view groupname, bool enabled) {
return getMutableAnimationOrThrow(ObjectVariant(object))->setLoopingEnabled(groupname, enabled); return getMutableAnimationOrThrow(object)->setLoopingEnabled(groupname, enabled);
}; };
// MWRender::Animation::getInfo can also return the current speed multiplier, but this is never used. // 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<float> { api["getCompletion"] = [](const LObject& object, std::string_view groupname) -> sol::optional<float> {
float completion = 0.f; float completion = 0.f;
if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, &completion)) if (getConstAnimationOrThrow(object)->getInfo(groupname, &completion))
return completion; return completion;
return sol::nullopt; return sol::nullopt;
}; };
api["getLoopCount"] = [](const sol::object& object, std::string groupname) -> sol::optional<size_t> { api["getLoopCount"] = [](const LObject& object, std::string groupname) -> sol::optional<size_t> {
size_t loops = 0; size_t loops = 0;
if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, nullptr, nullptr, &loops)) if (getConstAnimationOrThrow(object)->getInfo(groupname, nullptr, nullptr, &loops))
return loops; return loops;
return sol::nullopt; return sol::nullopt;
}; };
api["getSpeed"] = [](const sol::object& object, std::string groupname) -> sol::optional<float> { api["getSpeed"] = [](const LObject& object, std::string groupname) -> sol::optional<float> {
float speed = 0.f; float speed = 0.f;
if (getConstAnimationOrThrow(ObjectVariant(object))->getInfo(groupname, nullptr, &speed, nullptr)) if (getConstAnimationOrThrow(object)->getInfo(groupname, nullptr, &speed, nullptr))
return speed; return speed;
return sol::nullopt; return sol::nullopt;
}; };
api["setSpeed"] = [](const sol::object& object, std::string groupname, float speed) { api["setSpeed"] = [](const SelfObject& object, std::string groupname, const FiniteFloat speed) {
getMutableAnimationOrThrow(ObjectVariant(object))->adjustSpeedMult(groupname, 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) if (boneGroup < 0 || boneGroup >= BoneGroup::Num_BoneGroups)
throw std::runtime_error("Invalid bonegroup: " + std::to_string(boneGroup)); 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 // Clears out the animation queue, and cancel any animation currently playing from the queue
api["clearAnimationQueue"] = [mechanics](const sol::object& object, bool clearScripted) { api["clearAnimationQueue"] = [mechanics](const SelfObject& object, bool clearScripted) {
MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); const MWWorld::Ptr& ptr = getMutablePtrOrThrow(object);
mechanics->clearAnimationQueue(ptr, clearScripted); mechanics->clearAnimationQueue(ptr, clearScripted);
}; };
// Extended variant of MWScript's PlayGroup and LoopGroup // Extended variant of MWScript's PlayGroup and LoopGroup
api["playQueued"] = sol::overload( 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<uint32_t>::max()); uint32_t numberOfLoops = options.get_or("loops", std::numeric_limits<uint32_t>::max());
float speed = options.get_or("speed", 1.f); float speed = options.get_or("speed", 1.f);
std::string startKey = options.get_or<std::string>("startKey", "start"); std::string startKey = options.get_or<std::string>("startKey", "start");
std::string stopKey = options.get_or<std::string>("stopKey", "stop"); std::string stopKey = options.get_or<std::string>("stopKey", "stop");
bool forceLoop = options.get_or("forceLoop", false); 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->playAnimationGroupLua(ptr, groupname, numberOfLoops, speed, startKey, stopKey, forceLoop);
}, },
[mechanics](const sol::object& object, const std::string& groupname) { [mechanics](const SelfObject& object, const std::string& groupname) {
MWWorld::Ptr ptr = getMutablePtrOrThrow(ObjectVariant(object)); const MWWorld::Ptr& ptr = getMutablePtrOrThrow(object);
mechanics->playAnimationGroupLua( mechanics->playAnimationGroupLua(
ptr, groupname, std::numeric_limits<int>::max(), 1, "start", "stop", false); ptr, groupname, std::numeric_limits<int>::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); uint32_t loops = options.get_or("loops", 0u);
MWRender::Animation::AnimPriority priority = getPriorityArgument(options); MWRender::Animation::AnimPriority priority = getPriorityArgument(options);
BlendMask blendMask = options.get_or("blendMask", BlendMask::BlendMask_All); BlendMask blendMask = options.get_or("blendMask", BlendMask::BlendMask_All);
@ -243,33 +233,32 @@ namespace MWLua
const std::string lowerGroup = Misc::StringUtils::lowerCase(groupName); 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, animation->play(lowerGroup, priority, blendMask, autoDisable, speed, start, stop, startPoint, loops,
forceLoop || animation->isLoopingAnimation(lowerGroup)); forceLoop || animation->isLoopingAnimation(lowerGroup));
}; };
api["hasGroup"] = [](const sol::object& object, std::string_view groupname) -> bool { api["hasGroup"] = [](const LObject& object, std::string_view groupname) -> bool {
const MWRender::Animation* anim = getConstAnimationOrThrow(ObjectVariant(object)); const MWRender::Animation* anim = getConstAnimationOrThrow(object);
return anim->hasAnimation(groupname); return anim->hasAnimation(groupname);
}; };
// Note: This checks the nodemap, and does not read the scene graph itself, and so should be thread safe. // 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 { api["hasBone"] = [](const LObject& object, std::string_view bonename) -> bool {
const MWRender::Animation* anim = getConstAnimationOrThrow(ObjectVariant(object)); const MWRender::Animation* anim = getConstAnimationOrThrow(object);
return anim->getNode(bonename) != nullptr; return anim->getNode(bonename) != nullptr;
}; };
api["addVfx"] = [context]( api["addVfx"] = [context](const SelfObject& object, std::string_view model, sol::optional<sol::table> options) {
const sol::object& object, std::string_view model, sol::optional<sol::table> options) {
if (options) if (options)
{ {
context.mLuaManager->addAction( context.mLuaManager->addAction(
[object = ObjectVariant(object), model = std::string(model), [object = Object(object), model = std::string(model),
effectId = options->get_or<std::string>("vfxId", ""), loop = options->get_or("loop", false), effectId = options->get_or<std::string>("vfxId", ""), loop = options->get_or("loop", false),
boneName = options->get_or<std::string>("boneName", ""), boneName = options->get_or<std::string>("boneName", ""),
particleTexture = options->get_or<std::string>("particleTextureOverride", ""), particleTexture = options->get_or<std::string>("particleTextureOverride", ""),
useAmbientLight = options->get_or("useAmbientLight", true)] { 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); anim->addEffect(model, effectId, loop, boneName, particleTexture, useAmbientLight);
}, },
@ -278,7 +267,7 @@ namespace MWLua
else else
{ {
context.mLuaManager->addAction( context.mLuaManager->addAction(
[object = ObjectVariant(object), model = std::string(model)] { [object = Object(object), model = std::string(model)] {
MWRender::Animation* anim = getMutableAnimationOrThrow(object); MWRender::Animation* anim = getMutableAnimationOrThrow(object);
anim->addEffect(model, ""); anim->addEffect(model, "");
}, },
@ -286,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( context.mLuaManager->addAction(
[object = ObjectVariant(object), effectId = std::string(effectId)] { [object = Object(object), effectId = std::string(effectId)] {
MWRender::Animation* anim = getMutableAnimationOrThrow(object); MWRender::Animation* anim = getMutableAnimationOrThrow(object);
anim->removeEffect(effectId); anim->removeEffect(effectId);
}, },
"removeVfxAction"); "removeVfxAction");
}; };
api["removeAllVfx"] = [context](const sol::object& object) { api["removeAllVfx"] = [context](const SelfObject& object) {
context.mLuaManager->addAction( context.mLuaManager->addAction(
[object = ObjectVariant(object)] { [object = Object(object)] {
MWRender::Animation* anim = getMutableAnimationOrThrow(object); MWRender::Animation* anim = getMutableAnimationOrThrow(object);
anim->removeEffects(); anim->removeEffects();
}, },
@ -310,10 +299,9 @@ namespace MWLua
sol::table initWorldVfxBindings(const Context& context) sol::table initWorldVfxBindings(const Context& context)
{ {
sol::table api(context.mLua->unsafeState(), sol::create); sol::table api(context.mLua->unsafeState(), sol::create);
auto world = MWBase::Environment::get().getWorld();
api["spawn"] api["spawn"]
= [world, context](std::string_view model, const osg::Vec3f& worldPos, sol::optional<sol::table> options) { = [context](std::string_view model, const osg::Vec3f& worldPos, sol::optional<sol::table> options) {
if (options) if (options)
{ {
bool magicVfx = options->get_or("mwMagicVfx", true); bool magicVfx = options->get_or("mwMagicVfx", true);
@ -321,16 +309,19 @@ namespace MWLua
float scale = options->get_or("scale", 1.f); float scale = options->get_or("scale", 1.f);
bool useAmbientLight = options->get_or("useAmbientLight", true); bool useAmbientLight = options->get_or("useAmbientLight", true);
context.mLuaManager->addAction( 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]() { magicVfx, useAmbientLight]() {
world->spawnEffect(model, texture, worldPos, scale, magicVfx, useAmbientLight); MWBase::Environment::get().getWorld()->spawnEffect(
model, texture, worldPos, scale, magicVfx, useAmbientLight);
}, },
"openmw.vfx.spawn"); "openmw.vfx.spawn");
} }
else else
{ {
context.mLuaManager->addAction([world, model = VFS::Path::Normalized(model), context.mLuaManager->addAction(
worldPos]() { world->spawnEffect(model, "", worldPos, 1.f); }, [model = VFS::Path::Normalized(model), worldPos]() {
MWBase::Environment::get().getWorld()->spawnEffect(model, "", worldPos, 1.f);
},
"openmw.vfx.spawn"); "openmw.vfx.spawn");
} }
}; };

View file

@ -16,7 +16,7 @@ namespace MWLua
sol::table initCameraPackage(sol::state_view lua) sol::table initCameraPackage(sol::state_view lua)
{ {
using FiniteFloat = Misc::FiniteFloat; using Misc::FiniteFloat;
MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera(); MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera();
MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager(); MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager();

View file

@ -32,7 +32,6 @@ namespace MWLua
sol::state_view lua = context.mLua->unsafeState(); sol::state_view lua = context.mLua->unsafeState();
MWWorld::DateTimeManager* tm = MWBase::Environment::get().getWorld()->getTimeManager(); MWWorld::DateTimeManager* tm = MWBase::Environment::get().getWorld()->getTimeManager();
return { return {
{ "openmw.animation", initAnimationPackage(context) },
{ "openmw.async", { "openmw.async",
LuaUtil::getAsyncPackageInitializer( LuaUtil::getAsyncPackageInitializer(
lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) }, lua, [tm] { return tm->getSimulationTime(); }, [tm] { return tm->getGameTime(); }) },
@ -59,6 +58,7 @@ namespace MWLua
initCellBindingsForLocalScripts(context); initCellBindingsForLocalScripts(context);
LocalScripts::initializeSelfPackage(context); LocalScripts::initializeSelfPackage(context);
return { return {
{ "openmw.animation", initAnimationPackage(context) },
{ "openmw.core", initCorePackage(context) }, { "openmw.core", initCorePackage(context) },
{ "openmw.types", initTypesPackage(context) }, { "openmw.types", initTypesPackage(context) },
{ "openmw.nearby", initNearbyPackage(context) }, { "openmw.nearby", initNearbyPackage(context) },

View file

@ -10,6 +10,7 @@
#include <components/esm3/loadskil.hpp> #include <components/esm3/loadskil.hpp>
#include <components/esm3/loadweap.hpp> #include <components/esm3/loadweap.hpp>
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/misc/finitenumbers.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"
@ -56,10 +57,12 @@ namespace MWLua
static void addWorldTimeBindings(sol::table& api, const Context& context) static void addWorldTimeBindings(sol::table& api, const Context& context)
{ {
using Misc::FiniteFloat;
MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager(); MWWorld::DateTimeManager* timeManager = MWBase::Environment::get().getWorld()->getTimeManager();
api["setGameTimeScale"] = [timeManager](double scale) { timeManager->setGameTimeScale(scale); }; api["setGameTimeScale"] = [timeManager](const FiniteFloat scale) { timeManager->setGameTimeScale(scale); };
api["setSimulationTimeScale"] = [context, timeManager](float scale) { api["setSimulationTimeScale"] = [context, timeManager](const FiniteFloat scale) {
context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); }); context.mLuaManager->addAction([scale, timeManager] { timeManager->setSimulationTimeScale(scale); });
}; };

View file

@ -47,7 +47,10 @@ namespace Misc
for (size_t i = 0; i < rgb.size(); i++) for (size_t i = 0; i < rgb.size(); i++)
{ {
int b = static_cast<int>(rgb[i] * 255.0f); int b = static_cast<int>(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()) if (ec != std::errc())
throw std::logic_error("Error when converting number to base 16"); throw std::logic_error("Error when converting number to base 16");
} }

View file

@ -5,40 +5,48 @@
#include <cmath> #include <cmath>
#include <stdexcept> #include <stdexcept>
#include <utility>
namespace Misc namespace Misc
{ {
struct FiniteFloat template <class T>
struct FiniteNumber
{ {
float mValue; T mValue;
FiniteFloat(float v)
FiniteNumber(T v)
{ {
if (!std::isfinite(v)) if (!std::isfinite(v))
throw std::invalid_argument("Value must be a finite number"); throw std::invalid_argument("Value must be a finite number");
mValue = v; mValue = v;
} }
operator float() const { return mValue; }
operator T() const { return mValue; }
}; };
using FiniteDouble = FiniteNumber<double>;
using FiniteFloat = FiniteNumber<float>;
} }
namespace sol namespace sol
{ {
using FiniteFloat = Misc::FiniteFloat; template <class Handler, class T>
template <typename Handler>
bool sol_lua_check( bool sol_lua_check(
sol::types<FiniteFloat>, lua_State* L, int index, Handler&& handler, sol::stack::record& tracking) types<Misc::FiniteNumber<T>>, lua_State* state, int index, Handler&& handler, stack::record& tracking)
{ {
bool success = sol::stack::check<float>(L, lua_absindex(L, index), handler); bool success = stack::check<T>(state, lua_absindex(state, index), std::forward<Handler>(handler));
tracking.use(1); tracking.use(1);
return success; return success;
} }
static FiniteFloat sol_lua_get(sol::types<FiniteFloat>, lua_State* L, int index, sol::stack::record& tracking) template <class T>
static Misc::FiniteNumber<T> sol_lua_get(
types<Misc::FiniteNumber<T>>, lua_State* state, int index, stack::record& tracking)
{ {
float val = sol::stack::get<float>(L, lua_absindex(L, index)); T value = stack::get<T>(state, lua_absindex(state, index));
tracking.use(1); tracking.use(1);
return FiniteFloat(val); return value;
} }
} }

View file

@ -4,7 +4,8 @@
|:ref:`openmw.ambient <Package openmw.ambient>` | by player and menu | | Controls background sounds for given player. | |:ref:`openmw.ambient <Package openmw.ambient>` | by player and menu | | Controls background sounds for given player. |
| | scripts | | | | scripts | |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.animation <Package openmw.animation>` | everywhere | | Animation controls | |:ref:`openmw.animation <Package openmw.animation>` | by local and | | Animation controls |
| | player scripts | |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+
|:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers and callbacks. | |:ref:`openmw.async <Package openmw.async>` | everywhere | | Timers and callbacks. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+

View file

@ -29,7 +29,6 @@ local env = {
aux_util = require('openmw_aux.util'), aux_util = require('openmw_aux.util'),
calendar = require('openmw_aux.calendar'), calendar = require('openmw_aux.calendar'),
time = require('openmw_aux.time'), time = require('openmw_aux.time'),
anim = require('openmw.animation'),
view = require('openmw_aux.util').deepToString, view = require('openmw_aux.util').deepToString,
print = printToConsole, print = printToConsole,
exit = function() player:sendEvent('OMWConsoleExit') end, exit = function() player:sendEvent('OMWConsoleExit') end,

View file

@ -80,8 +80,9 @@ shared {
radius = max(radius, 0.1); radius = max(radius, 0.1);
// hack: make the radius wider on the screen edges // 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) // (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; texcoord = texcoord * 2.0 - vec2(1.0);
radius *= pow(texcoord.y*2.0-1.0, 2.0)+1.0; radius *= texcoord.x * texcoord.x + 1.0;
radius *= texcoord.y * texcoord.y + 1.0;
return radius; return radius;
} }
vec3 powv(vec3 a, float x) vec3 powv(vec3 a, float x)

View file

@ -56,7 +56,7 @@
--- ---
-- Skips animations for one frame, equivalent to mwscript's SkipAnim. -- 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 -- @function [parent=#animation] skipAnimationThisFrame
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
@ -99,14 +99,14 @@
--- ---
-- Cancels and removes the animation group from the list of active animations. -- 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 -- @function [parent=#animation] cancel
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param #string groupName -- @param #string groupName
--- ---
-- Enables or disables looping for the given animation group. Looping is enabled by default. -- 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 -- @function [parent=#animation] setLoopingEnabled
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param #string groupName -- @param #string groupName
@ -136,7 +136,7 @@
--- ---
-- Modifies the playback speed of an animation group. -- 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. -- 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 -- @function [parent=#animation] setSpeed
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param #string groupName -- @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}. -- 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 -- @function [parent=#animation] clearAnimationQueue
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param #boolean clearScripted whether to keep animation with priority Scripted or not. -- @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 -- 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 -- 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. -- 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 -- @function [parent=#animation] playQueued
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param #string groupName -- @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 -- 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) -- 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. -- 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 -- @function [parent=#animation] playBlended
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param #string groupName -- @param #string groupName
@ -218,7 +218,7 @@
--- ---
-- Plays a VFX on the actor. -- 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 -- @function [parent=#animation] addVfx
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param #string model path (normally taken from a record such as @{openmw.types#StaticRecord.model} or similar) -- @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 -- Removes a specific VFX.
-- Can be used only in local scripts on self. -- Can only be used on self.
-- @function [parent=#animation] removeVfx -- @function [parent=#animation] removeVfx
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor
-- @param #number vfxId an integer ID that uniquely identifies the VFX to remove -- @param #number vfxId an integer ID that uniquely identifies the VFX to remove
--- ---
-- Removes all vfx from the actor -- Removes all vfx from the actor.
-- Can be used only in local scripts on self. -- Can only be used on self.
-- @function [parent=#animation] removeAllVfx -- @function [parent=#animation] removeAllVfx
-- @param openmw.core#GameObject actor -- @param openmw.core#GameObject actor

View file

@ -344,6 +344,13 @@ testing.registerGlobalTest('load while teleporting - teleport', function()
landracer:teleport(player.cell, player.position) landracer:teleport(player.cell, player.position)
end) 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 { return {
engineHandlers = { engineHandlers = {
onUpdate = testing.updateGlobal, onUpdate = testing.updateGlobal,

View file

@ -72,6 +72,7 @@ registerGlobalTest('memory limit')
registerGlobalTest('vfs') registerGlobalTest('vfs')
registerGlobalTest('commit crime') registerGlobalTest('commit crime')
registerGlobalTest('record model property') 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 yaw rotation', 'rotating player with controls.yawChange should change rotation')
registerGlobalTest('player pitch rotation', 'rotating player with controls.pitchChange should change rotation') registerGlobalTest('player pitch rotation', 'rotating player with controls.pitchChange should change rotation')