Merge branch 'fargoths_lost_weather_ring' into 'master'

Make most weather bindings read/write

See merge request OpenMW/openmw!4866
This commit is contained in:
Alexei Kotov 2025-08-28 03:22:34 +03:00
commit 15b9d70cbe
15 changed files with 264 additions and 151 deletions

View File

@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...")
set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MAJOR 0)
set(OPENMW_VERSION_MINOR 50) set(OPENMW_VERSION_MINOR 50)
set(OPENMW_VERSION_RELEASE 0) 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_POSTPROCESSING_API_REVISION 3)
set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_COMMITHASH "")

View File

@ -1,7 +1,7 @@
#include "animationbindings.hpp" #include "animationbindings.hpp"
#include <components/lua/luastate.hpp> #include <components/lua/luastate.hpp>
#include <components/misc/finitenumbers.hpp> #include <components/misc/finitevalues.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/mechanicsmanager.hpp"

View File

@ -2,7 +2,7 @@
#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/finitevalues.hpp>
#include <components/settings/values.hpp> #include <components/settings/values.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"

View File

@ -7,6 +7,7 @@
#include <components/esm3/loadregn.hpp> #include <components/esm3/loadregn.hpp>
#include <components/lua/util.hpp> #include <components/lua/util.hpp>
#include <components/misc/color.hpp> #include <components/misc/color.hpp>
#include <components/misc/finitevalues.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
@ -20,6 +21,18 @@
#include "context.hpp" #include "context.hpp"
#include "object.hpp" #include "object.hpp"
namespace sol
{
template <>
struct is_automagical<MWWorld::TimeOfDayInterpolator<float>> : std::false_type
{
};
template <>
struct is_automagical<MWWorld::TimeOfDayInterpolator<osg::Vec4f>> : std::false_type
{
};
}
namespace namespace
{ {
class WeatherStore class WeatherStore
@ -36,11 +49,6 @@ namespace
size_t size() const { return MWBase::Environment::get().getWorld()->getAllWeather().size(); } 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 <class Cell> template <class Cell>
bool hasWeather(const Cell& cell, bool requireExterior) bool hasWeather(const Cell& cell, bool requireExterior)
{ {
@ -77,110 +85,146 @@ namespace
return (world.*getter)(); return (world.*getter)();
}); });
} }
void createFloatInterpolator(sol::state_view lua)
{
using Misc::FiniteFloat;
using T = MWWorld::TimeOfDayInterpolator<float>;
auto interT = lua.new_usertype<T>("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<osg::Vec4f>;
auto interT = lua.new_usertype<T>("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 namespace MWLua
{ {
sol::table initCoreWeatherBindings(const Context& context) sol::table initCoreWeatherBindings(const Context& context)
{ {
using Misc::FiniteFloat;
using Misc::FiniteVec3f;
sol::state_view lua = context.sol(); sol::state_view lua = context.sol();
sol::table api(lua, sol::create); sol::table api(lua, sol::create);
auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS();
auto weatherT = lua.new_usertype<MWWorld::Weather>("Weather"); auto weatherT = lua.new_usertype<MWWorld::Weather>("Weather");
createFloatInterpolator(lua);
createColorInterpolator(lua);
weatherT[sol::meta_function::to_string] weatherT[sol::meta_function::to_string]
= [](const MWWorld::Weather& w) -> std::string { return "Weather[" + w.mName + "]"; }; = [](const MWWorld::Weather& w) -> std::string { return "Weather[" + w.mName + "]"; };
weatherT["name"] weatherT["name"]
= sol::readonly_property([](const MWWorld::Weather& w) -> std::string_view { return w.mName; }); = 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["scriptId"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mScriptId; });
weatherT["cloudSpeed"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mCloudSpeed; }); weatherT["recordId"] = sol::readonly_property([](const MWWorld::Weather& w) { return w.mId.serializeText(); });
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<std::string> {
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["thunderSoundID"] = sol::readonly_property([lua](const MWWorld::Weather& w) { weatherT["thunderSoundID"] = sol::readonly_property([lua](const MWWorld::Weather& w) {
sol::table result(lua, sol::create); sol::table result(lua, sol::create);
for (const auto& soundId : w.mThunderSoundID) for (const auto& soundId : w.mThunderSoundID)
result.add(soundId.serializeText()); result.add(soundId.serializeText());
return result; 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<std::string> {
if (w.mRainEffect.empty())
return sol::nullopt;
return w.mRainEffect;
},
[](MWWorld::Weather& w, sol::optional<std::string_view> 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<std::string_view> rainLoopSoundID) {
w.mRainLoopSoundID = ESM::RefId::deserializeText(rainLoopSoundID.value_or(""));
});
weatherT["sunDiscSunsetColor"] 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"] weatherT["ambientLoopSoundID"]
= sol::readonly_property([](const MWWorld::Weather& w) { return w.mAmbientLoopSoundID.serializeText(); }); = sol::property([](const MWWorld::Weather& w) { return LuaUtil::serializeRefId(w.mAmbientLoopSoundID); },
weatherT["ambientColor"] = sol::readonly_property([lua](const MWWorld::Weather& w) { [](MWWorld::Weather& w, sol::optional<std::string_view> ambientLoopSoundId) {
sol::table result(lua, sol::create); w.mAmbientLoopSoundID = ESM::RefId::deserializeText(ambientLoopSoundId.value_or(""));
result["sunrise"] = color(w.mAmbientColor.getSunriseValue()); });
result["day"] = color(w.mAmbientColor.getDayValue()); weatherT["ambientColor"] = sol::readonly_property([](const MWWorld::Weather& w) { return &w.mAmbientColor; });
result["sunset"] = color(w.mAmbientColor.getSunsetValue()); weatherT["fogColor"] = sol::readonly_property([](const MWWorld::Weather& w) { return &w.mFogColor; });
result["night"] = color(w.mAmbientColor.getNightValue()); weatherT["skyColor"] = sol::readonly_property([](const MWWorld::Weather& w) { return &w.mSkyColor; });
return result; 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["fogColor"] = sol::readonly_property([lua](const MWWorld::Weather& w) { weatherT["particleEffect"] = sol::property(
sol::table result(lua, sol::create); [](const MWWorld::Weather& w) -> sol::optional<std::string> {
result["sunrise"] = color(w.mFogColor.getSunriseValue()); if (w.mParticleEffect.empty())
result["day"] = color(w.mFogColor.getDayValue()); return sol::nullopt;
result["sunset"] = color(w.mFogColor.getSunsetValue()); return w.mParticleEffect;
result["night"] = color(w.mFogColor.getNightValue()); },
return result; [](MWWorld::Weather& w, sol::optional<std::string_view> particleEffect) {
}); w.mParticleEffect = particleEffect.value_or("");
weatherT["skyColor"] = sol::readonly_property([lua](const MWWorld::Weather& w) { });
sol::table result(lua, sol::create); weatherT["distantLandFogFactor"] = sol::property([](const MWWorld::Weather& w) { return w.mDL.FogFactor; },
result["sunrise"] = color(w.mSkyColor.getSunriseValue()); [](MWWorld::Weather& w, const FiniteFloat fogFactor) { w.mDL.FogFactor = fogFactor; });
result["day"] = color(w.mSkyColor.getDayValue()); weatherT["distantLandFogOffset"] = sol::property([](const MWWorld::Weather& w) { return w.mDL.FogOffset; },
result["sunset"] = color(w.mSkyColor.getSunsetValue()); [](MWWorld::Weather& w, const FiniteFloat fogOffset) { w.mDL.FogOffset = fogOffset; });
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<std::string> {
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(); });
api["changeWeather"] = [](std::string_view regionId, const MWWorld::Weather& weather) { api["changeWeather"] = [](std::string_view regionId, const MWWorld::Weather& weather) {
ESM::RefId region = ESM::RefId::deserializeText(regionId); ESM::RefId region = ESM::RefId::deserializeText(regionId);

View File

@ -10,7 +10,7 @@
#include <components/esm3/loadnpc.hpp> #include <components/esm3/loadnpc.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 <components/misc/finitevalues.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/statemanager.hpp" #include "../mwbase/statemanager.hpp"

View File

@ -114,6 +114,11 @@ namespace MWWorld
const T& getSunsetValue() const { return mSunsetValue; } const T& getSunsetValue() const { return mSunsetValue; }
const T& getNightValue() const { return mNightValue; } 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: private:
T mSunriseValue, mDayValue, mSunsetValue, mNightValue; T mSunriseValue, mDayValue, mSunsetValue, mNightValue;
}; };

View File

@ -45,6 +45,11 @@ namespace Misc
return Color(SceneUtil::colourFromRGB(value)); return Color(SceneUtil::colourFromRGB(value));
} }
Color Color::fromVec(osg::Vec4f value)
{
return Color(std::move(value));
}
std::string Color::toHex() const std::string Color::toHex() const
{ {
std::string result(6, '0'); std::string result(6, '0');

View File

@ -27,9 +27,11 @@ namespace Misc
static Color fromHex(std::string_view hex); static Color fromHex(std::string_view hex);
static Color fromRGB(unsigned int value); static Color fromRGB(unsigned int value);
static Color fromVec(osg::Vec4f value);
std::string toHex() const; std::string toHex() const;
unsigned int toRGBA() const { return mValue.asRGBA(); } unsigned int toRGBA() const { return mValue.asRGBA(); }
osg::Vec4f toVec() const { return mValue; }
friend bool operator==(const Color& l, const Color& r); friend bool operator==(const Color& l, const Color& r);

View File

@ -1,53 +0,0 @@
#ifndef OPENMW_COMPONENTS_MISC_FINITENUMBERS_HPP
#define OPENMW_COMPONENTS_MISC_FINITENUMBERS_HPP
#include <components/lua/luastate.hpp>
#include <cmath>
#include <stdexcept>
#include <utility>
namespace Misc
{
template <class T>
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<double>;
using FiniteFloat = FiniteNumber<float>;
}
namespace sol
{
template <class Handler, class T>
bool sol_lua_check(
types<Misc::FiniteNumber<T>>, lua_State* state, int index, Handler&& handler, stack::record& tracking)
{
bool success = stack::check<T>(state, lua_absindex(state, index), std::forward<Handler>(handler));
tracking.use(1);
return success;
}
template <class T>
static Misc::FiniteNumber<T> sol_lua_get(
types<Misc::FiniteNumber<T>>, lua_State* state, int index, stack::record& tracking)
{
T value = stack::get<T>(state, lua_absindex(state, index));
tracking.use(1);
return value;
}
}
#endif

View File

@ -0,0 +1,76 @@
#ifndef OPENMW_COMPONENTS_MISC_FINITEVALUES_HPP
#define OPENMW_COMPONENTS_MISC_FINITEVALUES_HPP
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
#include <components/lua/luastate.hpp>
#include <cmath>
#include <stdexcept>
#include <type_traits>
#include <utility>
namespace Misc
{
template <class T>
struct FiniteValue
{
T mValue;
FiniteValue(T v)
{
constexpr bool isVecType
= std::is_same_v<osg::Vec2f, T> || std::is_same_v<osg::Vec3f, T> || std::is_same_v<osg::Vec4f, T>;
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<double>;
using FiniteFloat = FiniteValue<float>;
using FiniteVec2f = FiniteValue<osg::Vec2f>;
using FiniteVec3f = FiniteValue<osg::Vec3f>;
using FiniteVec4f = FiniteValue<osg::Vec4f>;
}
namespace sol
{
template <class Handler, class T>
bool sol_lua_check(
types<Misc::FiniteValue<T>>, lua_State* state, int index, Handler&& handler, stack::record& tracking)
{
bool success = stack::check<T>(state, lua_absindex(state, index), std::forward<Handler>(handler));
tracking.use(1);
return success;
}
template <class T>
static Misc::FiniteValue<T> sol_lua_get(
types<Misc::FiniteValue<T>>, lua_State* state, int index, stack::record& tracking)
{
T value = stack::get<T>(state, lua_absindex(state, index));
tracking.use(1);
return value;
}
}
#endif

View File

@ -1304,7 +1304,6 @@
--- ---
-- Weather data -- Weather data
-- @type WeatherRecord -- @type WeatherRecord
-- @extends #userdata
-- @field #string recordId -- @field #string recordId
-- @field #number scriptId -- @field #number scriptId
-- @field #string name -- @field #string name
@ -1329,10 +1328,26 @@
-- @field #number distantLandFogFactor -- @field #number distantLandFogFactor
-- @field #number distantLandFogOffset -- @field #number distantLandFogOffset
-- @field openmw.util#Color sunDiscSunsetColor -- @field openmw.util#Color sunDiscSunsetColor
-- @field #table landFogDepth A table with the keys "sunrise", "day", "sunset" and "night" -- @field #TimeOfDayInterpolatorFloat landFogDepth
-- @field #table skyColor A table with the keys "sunrise", "day", "sunset" and "night". Each is a @{openmw.util#Color}. -- @field #TimeOfDayInterpolatorColor skyColor
-- @field #table ambientColor A table with the keys "sunrise", "day", "sunset" and "night". Each is a @{openmw.util#Color}. -- @field #TimeOfDayInterpolatorColor ambientColor
-- @field #table fogColor A table with the keys "sunrise", "day", "sunset" and "night". Each is a @{openmw.util#Color}. -- @field #TimeOfDayInterpolatorColor fogColor
-- @field #table sunColor A table with the keys "sunrise", "day", "sunset" and "night". Each is a @{openmw.util#Color}. -- @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 return nil

View File

@ -351,13 +351,20 @@ 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() testing.registerGlobalTest('nan float', function()
local nan = 0.0 / 0.0 local nan = 0.0 / 0.0
local ok, err = pcall(function() world.setGameTimeScale(nan) end) local ok, err = pcall(function() world.setGameTimeScale(nan) end)
testing.expectEqual(ok, false) testing.expectEqual(ok, false)
testing.expectEqual(err, 'Value must be a finite number') testing.expectEqual(err, 'Value must be a finite number')
end) 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() testing.registerGlobalTest('mwscript magic interactions', function()
local player = world.players[1] local player = world.players[1]
local script = world.mwscript.getGlobalScript('OpenMW_Tests', player) local script = world.mwscript.getGlobalScript('OpenMW_Tests', player)

View File

@ -73,7 +73,8 @@ 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('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 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')

View File

@ -12,6 +12,7 @@ require('global_issues')
require('global_dialogues') require('global_dialogues')
require('global_mwscript') require('global_mwscript')
require('global_regions') require('global_regions')
require('global_weather')
return { return {
engineHandlers = { engineHandlers = {

View File

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