1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-10-17 18:16:39 +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 #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

View file

@ -5,6 +5,7 @@
#include <components/lua/asyncpackage.hpp>
#include <components/lua/luastate.hpp>
#include <components/lua/utilpackage.hpp>
#include <components/misc/finitenumbers.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/settings/values.hpp>
@ -22,86 +23,75 @@
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<sol::optional<Priority>>("priority");
if (asPriorityEnum)
return asPriorityEnum.value();
auto asTable = args.get<sol::optional<sol::table>>("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<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;
}
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<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)
{
using Misc::FiniteFloat;
auto view = context.sol();
auto mechanics = MWBase::Environment::get().getMechanicsManager();
auto world = MWBase::Environment::get().getWorld();
sol::table api(view, sol::create);
@ -142,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> {
float time = getConstAnimationOrThrow(ObjectVariant(object))->getTextKeyTime(key);
api["getTextKeyTime"] = [](const LObject& object, std::string_view key) -> sol::optional<float> {
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> {
float time = getConstAnimationOrThrow(ObjectVariant(object))->getCurrentTime(groupname);
api["getCurrentTime"] = [](const LObject& object, std::string_view groupname) -> sol::optional<float> {
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<float> {
api["getCompletion"] = [](const LObject& object, std::string_view groupname) -> sol::optional<float> {
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<size_t> {
api["getLoopCount"] = [](const LObject& object, std::string groupname) -> sol::optional<size_t> {
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<float> {
api["getSpeed"] = [](const LObject& object, std::string groupname) -> sol::optional<float> {
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, float 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<uint32_t>::max());
float speed = options.get_or("speed", 1.f);
std::string startKey = options.get_or<std::string>("startKey", "start");
std::string stopKey = options.get_or<std::string>("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<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);
MWRender::Animation::AnimPriority priority = getPriorityArgument(options);
BlendMask blendMask = options.get_or("blendMask", BlendMask::BlendMask_All);
@ -243,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<sol::table> options) {
api["addVfx"] = [context](const SelfObject& object, std::string_view model, sol::optional<sol::table> options) {
if (options)
{
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),
boneName = options->get_or<std::string>("boneName", ""),
particleTexture = options->get_or<std::string>("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);
},
@ -278,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, "");
},
@ -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(
[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();
},
@ -310,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<sol::table> options) {
= [context](std::string_view model, const osg::Vec3f& worldPos, sol::optional<sol::table> options) {
if (options)
{
bool magicVfx = options->get_or("mwMagicVfx", true);
@ -321,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");
}
};

View file

@ -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();

View file

@ -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(); }) },
@ -59,6 +58,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) },

View file

@ -10,6 +10,7 @@
#include <components/esm3/loadskil.hpp>
#include <components/esm3/loadweap.hpp>
#include <components/lua/luastate.hpp>
#include <components/misc/finitenumbers.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp"
@ -56,10 +57,12 @@ namespace MWLua
static void addWorldTimeBindings(sol::table& api, const Context& context)
{
using 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 FiniteFloat scale) { timeManager->setGameTimeScale(scale); };
api["setSimulationTimeScale"] = [context, timeManager](const FiniteFloat 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++)
{
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())
throw std::logic_error("Error when converting number to base 16");
}

View file

@ -5,40 +5,48 @@
#include <cmath>
#include <stdexcept>
#include <utility>
namespace Misc
{
struct FiniteFloat
template <class T>
struct FiniteNumber
{
float mValue;
FiniteFloat(float v)
T mValue;
FiniteNumber(T v)
{
if (!std::isfinite(v))
throw std::invalid_argument("Value must be a finite number");
mValue = v;
}
operator float() const { return mValue; }
operator T() const { return mValue; }
};
using FiniteDouble = FiniteNumber<double>;
using FiniteFloat = FiniteNumber<float>;
}
namespace sol
{
using FiniteFloat = Misc::FiniteFloat;
template <typename Handler>
template <class Handler, class T>
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);
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);
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. |
| | 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. |
+------------------------------------------------------------+--------------------+---------------------------------------------------------------+

View file

@ -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,

View file

@ -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)

View file

@ -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

View file

@ -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,

View file

@ -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')