diff --git a/CMakeLists.txt b/CMakeLists.txt index b44ed44fa1..834668e92a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 94) +set(OPENMW_LUA_API_REVISION 95) set(OPENMW_POSTPROCESSING_API_REVISION 3) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/animationbindings.cpp b/apps/openmw/mwlua/animationbindings.cpp index 5ecd3bd1fa..3e22af85d9 100644 --- a/apps/openmw/mwlua/animationbindings.cpp +++ b/apps/openmw/mwlua/animationbindings.cpp @@ -1,7 +1,7 @@ #include "animationbindings.hpp" #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index c6461209c5..454f966538 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwlua/weatherbindings.cpp b/apps/openmw/mwlua/weatherbindings.cpp index 1d813a2b92..09c930d889 100644 --- a/apps/openmw/mwlua/weatherbindings.cpp +++ b/apps/openmw/mwlua/weatherbindings.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,18 @@ #include "context.hpp" #include "object.hpp" +namespace sol +{ + template <> + struct is_automagical> : std::false_type + { + }; + template <> + struct is_automagical> : std::false_type + { + }; +} + namespace { class WeatherStore @@ -36,11 +49,6 @@ namespace size_t size() const { return MWBase::Environment::get().getWorld()->getAllWeather().size(); } }; - Misc::Color color(const osg::Vec4f& color) - { - return Misc::Color(color.r(), color.g(), color.b(), color.a()); - } - template bool hasWeather(const Cell& cell, bool requireExterior) { @@ -77,110 +85,146 @@ namespace return (world.*getter)(); }); } + + void createFloatInterpolator(sol::state_view lua) + { + using Misc::FiniteFloat; + using T = MWWorld::TimeOfDayInterpolator; + + auto interT = lua.new_usertype("TimeOfDayInterpolatorFloat"); + + interT["sunrise"] = sol::property([](const T& inter) { return inter.getSunriseValue(); }, + [](T& inter, FiniteFloat value) { inter.setSunriseValue(value); }); + interT["sunset"] = sol::property([](const T& inter) { return inter.getSunsetValue(); }, + [](T& inter, FiniteFloat value) { inter.setSunsetValue(value); }); + interT["day"] = sol::property([](const T& inter) { return inter.getDayValue(); }, + [](T& inter, FiniteFloat value) { inter.setDayValue(value); }); + interT["night"] = sol::property([](const T& inter) { return inter.getNightValue(); }, + [](T& inter, FiniteFloat value) { inter.setNightValue(value); }); + } + + void createColorInterpolator(sol::state_view lua) + { + using Misc::Color; + using T = MWWorld::TimeOfDayInterpolator; + + auto interT = lua.new_usertype("TimeOfDayInterpolatorColor"); + + interT["sunrise"] = sol::property([](const T& inter) { return Color::fromVec(inter.getSunriseValue()); }, + [](T& inter, const Color& value) { inter.setSunriseValue(value.toVec()); }); + interT["sunset"] = sol::property([](const T& inter) { return Color::fromVec(inter.getSunsetValue()); }, + [](T& inter, const Color& value) { inter.setSunsetValue(value.toVec()); }); + interT["day"] = sol::property([](const T& inter) { return Color::fromVec(inter.getDayValue()); }, + [](T& inter, const Color& value) { inter.setDayValue(value.toVec()); }); + interT["night"] = sol::property([](const T& inter) { return Color::fromVec(inter.getNightValue()); }, + [](T& inter, const Color& value) { inter.setNightValue(value.toVec()); }); + } } namespace MWLua { sol::table initCoreWeatherBindings(const Context& context) { + using Misc::FiniteFloat; + using Misc::FiniteVec3f; + sol::state_view lua = context.sol(); sol::table api(lua, sol::create); auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + auto weatherT = lua.new_usertype("Weather"); + createFloatInterpolator(lua); + createColorInterpolator(lua); + weatherT[sol::meta_function::to_string] = [](const MWWorld::Weather& w) -> std::string { return "Weather[" + w.mName + "]"; }; weatherT["name"] = sol::readonly_property([](const MWWorld::Weather& w) -> std::string_view { return w.mName; }); - weatherT["windSpeed"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mWindSpeed; }); - weatherT["cloudSpeed"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mCloudSpeed; }); - weatherT["cloudTexture"] = sol::readonly_property([vfs](const MWWorld::Weather& w) { - return Misc::ResourceHelpers::correctTexturePath(w.mCloudTexture, vfs); - }); - weatherT["cloudsMaximumPercent"] - = sol::readonly_property([](const MWWorld::Weather& w) { return w.mCloudsMaximumPercent; }); - weatherT["isStorm"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mIsStorm; }); - weatherT["stormDirection"] - = sol::readonly_property([](const MWWorld::Weather& w) { return w.mStormDirection; }); - weatherT["glareView"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mGlareView; }); - weatherT["rainSpeed"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mRainSpeed; }); - weatherT["rainEntranceSpeed"] - = sol::readonly_property([](const MWWorld::Weather& w) { return w.mRainEntranceSpeed; }); - weatherT["rainEffect"] = sol::readonly_property([](const MWWorld::Weather& w) -> sol::optional { - if (w.mRainEffect.empty()) - return sol::nullopt; - return w.mRainEffect; - }); - weatherT["rainMaxRaindrops"] - = sol::readonly_property([](const MWWorld::Weather& w) { return w.mRainMaxRaindrops; }); - weatherT["rainDiameter"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mRainDiameter; }); - weatherT["rainThreshold"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mRainThreshold; }); - weatherT["rainMaxHeight"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mRainMaxHeight; }); - weatherT["rainMinHeight"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mRainMinHeight; }); - weatherT["rainLoopSoundID"] - = sol::readonly_property([](const MWWorld::Weather& w) { return w.mRainLoopSoundID.serializeText(); }); + weatherT["scriptId"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mScriptId; }); + weatherT["recordId"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mId.serializeText(); }); weatherT["thunderSoundID"] = sol::readonly_property([lua](const MWWorld::Weather& w) { sol::table result(lua, sol::create); for (const auto& soundId : w.mThunderSoundID) result.add(soundId.serializeText()); return result; }); + + weatherT["windSpeed"] = sol::property([](const MWWorld::Weather& w) { return w.mWindSpeed; }, + [](MWWorld::Weather& w, const FiniteFloat windSpeed) { w.mWindSpeed = windSpeed; }); + weatherT["cloudSpeed"] = sol::property([](const MWWorld::Weather& w) { return w.mCloudSpeed; }, + [](MWWorld::Weather& w, const FiniteFloat cloudSpeed) { w.mCloudSpeed = cloudSpeed; }); + weatherT["cloudTexture"] = sol::property( + [vfs]( + const MWWorld::Weather& w) { return Misc::ResourceHelpers::correctTexturePath(w.mCloudTexture, vfs); }, + [](MWWorld::Weather& w, std::string_view cloudTexture) { w.mCloudTexture = cloudTexture; }); + weatherT["cloudsMaximumPercent"] + = sol::property([](const MWWorld::Weather& w) { return w.mCloudsMaximumPercent; }, + [](MWWorld::Weather& w, const FiniteFloat cloudsMaximumPercent) { + w.mCloudsMaximumPercent = cloudsMaximumPercent; + }); + weatherT["isStorm"] = sol::property([](const MWWorld::Weather& w) { return w.mIsStorm; }, + [](MWWorld::Weather& w, bool isStorm) { w.mIsStorm = isStorm; }); + weatherT["stormDirection"] = sol::property([](const MWWorld::Weather& w) { return w.mStormDirection; }, + [](MWWorld::Weather& w, const FiniteVec3f& stormDirection) { w.mStormDirection = stormDirection; }); + weatherT["glareView"] = sol::property([](const MWWorld::Weather& w) { return w.mGlareView; }, + [](MWWorld::Weather& w, const FiniteFloat glareView) { w.mGlareView = glareView; }); + weatherT["rainSpeed"] = sol::property([](const MWWorld::Weather& w) { return w.mRainSpeed; }, + [](MWWorld::Weather& w, const FiniteFloat rainSpeed) { w.mRainSpeed = rainSpeed; }); + weatherT["rainEntranceSpeed"] = sol::property([](const MWWorld::Weather& w) { return w.mRainEntranceSpeed; }, + [](MWWorld::Weather& w, const FiniteFloat rainEntranceSpeed) { w.mRainEntranceSpeed = rainEntranceSpeed; }); + weatherT["rainEffect"] = sol::property( + [](const MWWorld::Weather& w) -> sol::optional { + if (w.mRainEffect.empty()) + return sol::nullopt; + return w.mRainEffect; + }, + [](MWWorld::Weather& w, sol::optional rainEffect) { + w.mRainEffect = rainEffect.value_or(""); + }); + weatherT["rainMaxRaindrops"] = sol::property([](const MWWorld::Weather& w) { return w.mRainMaxRaindrops; }, + [](MWWorld::Weather& w, int rainMaxRaindrops) { w.mRainMaxRaindrops = rainMaxRaindrops; }); + weatherT["rainDiameter"] = sol::property([](const MWWorld::Weather& w) { return w.mRainDiameter; }, + [](MWWorld::Weather& w, const FiniteFloat rainDiameter) { w.mRainDiameter = rainDiameter; }); + weatherT["rainThreshold"] = sol::property([](const MWWorld::Weather& w) { return w.mRainThreshold; }, + [](MWWorld::Weather& w, const FiniteFloat rainThreshold) { w.mRainThreshold = rainThreshold; }); + weatherT["rainMaxHeight"] = sol::property([](const MWWorld::Weather& w) { return w.mRainMaxHeight; }, + [](MWWorld::Weather& w, const FiniteFloat rainMaxHeight) { w.mRainMaxHeight = rainMaxHeight; }); + weatherT["rainMinHeight"] = sol::property([](const MWWorld::Weather& w) { return w.mRainMinHeight; }, + [](MWWorld::Weather& w, const FiniteFloat rainMinHeight) { w.mRainMinHeight = rainMinHeight; }); + weatherT["rainLoopSoundID"] + = sol::property([](const MWWorld::Weather& w) { return LuaUtil::serializeRefId(w.mRainLoopSoundID); }, + [](MWWorld::Weather& w, sol::optional rainLoopSoundID) { + w.mRainLoopSoundID = ESM::RefId::deserializeText(rainLoopSoundID.value_or("")); + }); weatherT["sunDiscSunsetColor"] - = sol::readonly_property([](const MWWorld::Weather& w) { return color(w.mSunDiscSunsetColor); }); + = sol::property([](const MWWorld::Weather& w) { return Misc::Color::fromVec(w.mSunDiscSunsetColor); }, + [](MWWorld::Weather& w, const Misc::Color& sunDiscSunsetColor) { + w.mSunDiscSunsetColor = sunDiscSunsetColor.toVec(); + }); weatherT["ambientLoopSoundID"] - = sol::readonly_property([](const MWWorld::Weather& w) { return w.mAmbientLoopSoundID.serializeText(); }); - weatherT["ambientColor"] = sol::readonly_property([lua](const MWWorld::Weather& w) { - sol::table result(lua, sol::create); - result["sunrise"] = color(w.mAmbientColor.getSunriseValue()); - result["day"] = color(w.mAmbientColor.getDayValue()); - result["sunset"] = color(w.mAmbientColor.getSunsetValue()); - result["night"] = color(w.mAmbientColor.getNightValue()); - return result; - }); - weatherT["fogColor"] = sol::readonly_property([lua](const MWWorld::Weather& w) { - sol::table result(lua, sol::create); - result["sunrise"] = color(w.mFogColor.getSunriseValue()); - result["day"] = color(w.mFogColor.getDayValue()); - result["sunset"] = color(w.mFogColor.getSunsetValue()); - result["night"] = color(w.mFogColor.getNightValue()); - return result; - }); - weatherT["skyColor"] = sol::readonly_property([lua](const MWWorld::Weather& w) { - sol::table result(lua, sol::create); - result["sunrise"] = color(w.mSkyColor.getSunriseValue()); - result["day"] = color(w.mSkyColor.getDayValue()); - result["sunset"] = color(w.mSkyColor.getSunsetValue()); - result["night"] = color(w.mSkyColor.getNightValue()); - return result; - }); - weatherT["sunColor"] = sol::readonly_property([lua](const MWWorld::Weather& w) { - sol::table result(lua, sol::create); - result["sunrise"] = color(w.mSunColor.getSunriseValue()); - result["day"] = color(w.mSunColor.getDayValue()); - result["sunset"] = color(w.mSunColor.getSunsetValue()); - result["night"] = color(w.mSunColor.getNightValue()); - return result; - }); - weatherT["landFogDepth"] = sol::readonly_property([lua](const MWWorld::Weather& w) { - sol::table result(lua, sol::create); - result["sunrise"] = w.mLandFogDepth.getSunriseValue(); - result["day"] = w.mLandFogDepth.getDayValue(); - result["sunset"] = w.mLandFogDepth.getSunsetValue(); - result["night"] = w.mLandFogDepth.getNightValue(); - return result; - }); - weatherT["particleEffect"] - = sol::readonly_property([](const MWWorld::Weather& w) -> sol::optional { - if (w.mParticleEffect.empty()) - return sol::nullopt; - return w.mParticleEffect; - }); - weatherT["distantLandFogFactor"] - = sol::readonly_property([](const MWWorld::Weather& w) { return w.mDL.FogFactor; }); - weatherT["distantLandFogOffset"] - = sol::readonly_property([](const MWWorld::Weather& w) { return w.mDL.FogOffset; }); - weatherT["scriptId"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mScriptId; }); - weatherT["recordId"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mId.serializeText(); }); + = sol::property([](const MWWorld::Weather& w) { return LuaUtil::serializeRefId(w.mAmbientLoopSoundID); }, + [](MWWorld::Weather& w, sol::optional ambientLoopSoundId) { + w.mAmbientLoopSoundID = ESM::RefId::deserializeText(ambientLoopSoundId.value_or("")); + }); + weatherT["ambientColor"] = sol::readonly_property([](const MWWorld::Weather& w) { return &w.mAmbientColor; }); + weatherT["fogColor"] = sol::readonly_property([](const MWWorld::Weather& w) { return &w.mFogColor; }); + weatherT["skyColor"] = sol::readonly_property([](const MWWorld::Weather& w) { return &w.mSkyColor; }); + weatherT["sunColor"] = sol::readonly_property([](const MWWorld::Weather& w) { return &w.mSunColor; }); + weatherT["landFogDepth"] = sol::readonly_property([](const MWWorld::Weather& w) { return &w.mLandFogDepth; }); + weatherT["particleEffect"] = sol::property( + [](const MWWorld::Weather& w) -> sol::optional { + if (w.mParticleEffect.empty()) + return sol::nullopt; + return w.mParticleEffect; + }, + [](MWWorld::Weather& w, sol::optional particleEffect) { + w.mParticleEffect = particleEffect.value_or(""); + }); + weatherT["distantLandFogFactor"] = sol::property([](const MWWorld::Weather& w) { return w.mDL.FogFactor; }, + [](MWWorld::Weather& w, const FiniteFloat fogFactor) { w.mDL.FogFactor = fogFactor; }); + weatherT["distantLandFogOffset"] = sol::property([](const MWWorld::Weather& w) { return w.mDL.FogOffset; }, + [](MWWorld::Weather& w, const FiniteFloat fogOffset) { w.mDL.FogOffset = fogOffset; }); api["changeWeather"] = [](std::string_view regionId, const MWWorld::Weather& weather) { ESM::RefId region = ESM::RefId::deserializeText(regionId); diff --git a/apps/openmw/mwlua/worldbindings.cpp b/apps/openmw/mwlua/worldbindings.cpp index 5539d8d77c..d80631c273 100644 --- a/apps/openmw/mwlua/worldbindings.cpp +++ b/apps/openmw/mwlua/worldbindings.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index ddc96f0ae6..2b4f44620a 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -114,6 +114,11 @@ namespace MWWorld const T& getSunsetValue() const { return mSunsetValue; } const T& getNightValue() const { return mNightValue; } + void setSunriseValue(const T& sunriseValue) { mSunriseValue = sunriseValue; } + void setDayValue(const T& dayValue) { mDayValue = dayValue; } + void setSunsetValue(const T& sunsetValue) { mSunsetValue = sunsetValue; } + void setNightValue(const T& nightValue) { mNightValue = nightValue; } + private: T mSunriseValue, mDayValue, mSunsetValue, mNightValue; }; diff --git a/components/misc/color.cpp b/components/misc/color.cpp index 0652e6dbb7..5ad52580d4 100644 --- a/components/misc/color.cpp +++ b/components/misc/color.cpp @@ -45,6 +45,11 @@ namespace Misc return Color(SceneUtil::colourFromRGB(value)); } + Color Color::fromVec(osg::Vec4f value) + { + return Color(std::move(value)); + } + std::string Color::toHex() const { std::string result(6, '0'); diff --git a/components/misc/color.hpp b/components/misc/color.hpp index 20a24a73ad..83932bb3ee 100644 --- a/components/misc/color.hpp +++ b/components/misc/color.hpp @@ -27,9 +27,11 @@ namespace Misc static Color fromHex(std::string_view hex); static Color fromRGB(unsigned int value); + static Color fromVec(osg::Vec4f value); std::string toHex() const; unsigned int toRGBA() const { return mValue.asRGBA(); } + osg::Vec4f toVec() const { return mValue; } friend bool operator==(const Color& l, const Color& r); diff --git a/components/misc/finitenumbers.hpp b/components/misc/finitenumbers.hpp deleted file mode 100644 index 1670791349..0000000000 --- a/components/misc/finitenumbers.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef OPENMW_COMPONENTS_MISC_FINITENUMBERS_HPP -#define OPENMW_COMPONENTS_MISC_FINITENUMBERS_HPP - -#include - -#include -#include -#include - -namespace Misc -{ - template - struct FiniteNumber - { - T mValue; - - FiniteNumber(T v) - { - if (!std::isfinite(v)) - throw std::invalid_argument("Value must be a finite number"); - mValue = v; - } - - operator T() const { return mValue; } - }; - - using FiniteDouble = FiniteNumber; - - using FiniteFloat = FiniteNumber; -} - -namespace sol -{ - template - bool sol_lua_check( - types>, lua_State* state, int index, Handler&& handler, stack::record& tracking) - { - bool success = stack::check(state, lua_absindex(state, index), std::forward(handler)); - tracking.use(1); - return success; - } - - template - static Misc::FiniteNumber sol_lua_get( - types>, lua_State* state, int index, stack::record& tracking) - { - T value = stack::get(state, lua_absindex(state, index)); - tracking.use(1); - return value; - } -} - -#endif diff --git a/components/misc/finitevalues.hpp b/components/misc/finitevalues.hpp new file mode 100644 index 0000000000..7a5126bed7 --- /dev/null +++ b/components/misc/finitevalues.hpp @@ -0,0 +1,76 @@ +#ifndef OPENMW_COMPONENTS_MISC_FINITEVALUES_HPP +#define OPENMW_COMPONENTS_MISC_FINITEVALUES_HPP + +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace Misc +{ + template + struct FiniteValue + { + T mValue; + + FiniteValue(T v) + { + constexpr bool isVecType + = std::is_same_v || std::is_same_v || std::is_same_v; + + if constexpr (isVecType) + { + for (int i = 0; i < T::num_components; ++i) + if (!std::isfinite(v[i])) + throw std::invalid_argument("Vector must contain finite numbers"); + } + else + { + if (!std::isfinite(v)) + throw std::invalid_argument("Value must be a finite number"); + } + mValue = v; + } + + operator T() const { return mValue; } + }; + + using FiniteDouble = FiniteValue; + + using FiniteFloat = FiniteValue; + + using FiniteVec2f = FiniteValue; + + using FiniteVec3f = FiniteValue; + + using FiniteVec4f = FiniteValue; +} + +namespace sol +{ + template + bool sol_lua_check( + types>, lua_State* state, int index, Handler&& handler, stack::record& tracking) + { + bool success = stack::check(state, lua_absindex(state, index), std::forward(handler)); + tracking.use(1); + return success; + } + + template + static Misc::FiniteValue sol_lua_get( + types>, lua_State* state, int index, stack::record& tracking) + { + T value = stack::get(state, lua_absindex(state, index)); + tracking.use(1); + return value; + } +} + +#endif diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 56ebdf5e9b..5dc4bfb4e2 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -1304,7 +1304,6 @@ --- -- Weather data -- @type WeatherRecord --- @extends #userdata -- @field #string recordId -- @field #number scriptId -- @field #string name @@ -1329,10 +1328,26 @@ -- @field #number distantLandFogFactor -- @field #number distantLandFogOffset -- @field openmw.util#Color sunDiscSunsetColor --- @field #table landFogDepth A table with the keys "sunrise", "day", "sunset" and "night" --- @field #table skyColor A table with the keys "sunrise", "day", "sunset" and "night". Each is a @{openmw.util#Color}. --- @field #table ambientColor A table with the keys "sunrise", "day", "sunset" and "night". Each is a @{openmw.util#Color}. --- @field #table fogColor A table with the keys "sunrise", "day", "sunset" and "night". Each is a @{openmw.util#Color}. --- @field #table sunColor A table with the keys "sunrise", "day", "sunset" and "night". Each is a @{openmw.util#Color}. +-- @field #TimeOfDayInterpolatorFloat landFogDepth +-- @field #TimeOfDayInterpolatorColor skyColor +-- @field #TimeOfDayInterpolatorColor ambientColor +-- @field #TimeOfDayInterpolatorColor fogColor +-- @field #TimeOfDayInterpolatorColor sunColor + +--- +-- Interpolates numbers for weathers based on time of day +-- @type TimeOfDayInterpolatorFloat +-- @field #number sunrise +-- @field #number sunset +-- @field #number day +-- @field #number night + +--- +-- Interpolates colors for weathers based on time of day +-- @type TimeOfDayInterpolatorColor +-- @field openmw.util#Color sunrise +-- @field openmw.util#Color sunset +-- @field openmw.util#Color day +-- @field openmw.util#Color night return nil diff --git a/scripts/data/integration_tests/test_lua_api/global.lua b/scripts/data/integration_tests/test_lua_api/global.lua index e231e009e9..fec0b4ead6 100644 --- a/scripts/data/integration_tests/test_lua_api/global.lua +++ b/scripts/data/integration_tests/test_lua_api/global.lua @@ -351,13 +351,20 @@ testing.registerGlobalTest('load while teleporting - teleport', function() landracer:teleport(player.cell, player.position) end) -testing.registerGlobalTest('nan', function() +testing.registerGlobalTest('nan float', 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) +testing.registerGlobalTest('nan vector', function() + local nan = 0.0 / 0.0 + local ok, err = pcall(function() core.weather.records[1].stormDirection = util.vector3(nan, nan, nan) end) + testing.expectEqual(ok, false) + testing.expectEqual(err, 'Vector must contain finite numbers') +end) + testing.registerGlobalTest('mwscript magic interactions', function() local player = world.players[1] local script = world.mwscript.getGlobalScript('OpenMW_Tests', player) diff --git a/scripts/data/integration_tests/test_lua_api/menu.lua b/scripts/data/integration_tests/test_lua_api/menu.lua index 8a2a278046..ca5f40c526 100644 --- a/scripts/data/integration_tests/test_lua_api/menu.lua +++ b/scripts/data/integration_tests/test_lua_api/menu.lua @@ -73,7 +73,8 @@ registerGlobalTest('memory limit') registerGlobalTest('vfs') registerGlobalTest('commit crime') registerGlobalTest('record model property') -registerGlobalTest('nan', 'world.setGameTimeScale should not accept nan') +registerGlobalTest('nan float', 'world.setGameTimeScale should not accept nan') +registerGlobalTest('nan vector', 'weather.stormDirection should not accept a vector with a nan component') registerGlobalTest('player yaw rotation', 'rotating player with controls.yawChange should change rotation') registerGlobalTest('player pitch rotation', 'rotating player with controls.pitchChange should change rotation') diff --git a/scripts/data/morrowind_tests/global.lua b/scripts/data/morrowind_tests/global.lua index 4e4974c727..0de1d8cb30 100644 --- a/scripts/data/morrowind_tests/global.lua +++ b/scripts/data/morrowind_tests/global.lua @@ -12,6 +12,7 @@ require('global_issues') require('global_dialogues') require('global_mwscript') require('global_regions') +require('global_weather') return { engineHandlers = { diff --git a/scripts/data/morrowind_tests/global_weather.lua b/scripts/data/morrowind_tests/global_weather.lua new file mode 100644 index 0000000000..15eefc55a0 --- /dev/null +++ b/scripts/data/morrowind_tests/global_weather.lua @@ -0,0 +1,10 @@ +local testing = require('testing_util') +local core = require('openmw.core') +local util = require('openmw.util') + +testing.registerGlobalTest('[weather] Should be able to change weather records', function() + local record = core.weather.records['clear'] + testing.expect(record ~= nil, 'Clear weather to exist') + record.skyColor.sunrise = util.color.hex('2227FF') + testing.expectEqual(record.skyColor.sunrise, util.color.hex('2227FF')) +end)