1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-12-09 05:04:30 +00:00
openmw/apps/openmw/mwlua/weatherbindings.cpp
Andrzej Głuszak c4b28a39c3 Lua: Return nil instead of empty strings for optional RefId fields
Implement sol_lua_push for ESM::RefId to automatically convert empty
RefIds to nil in Lua. This fixes cell.region and cell.worldSpaceId
returning empty strings, and applies the same pattern consistently
across all Lua bindings.

Removes LuaUtil::serializeRefId as it's no longer needed.

Fixes #8718
2025-11-02 05:08:25 +03:00

280 lines
14 KiB
C++

#include "weatherbindings.hpp"
#include <type_traits>
#include <osg/Vec4f>
#include <components/esm3/loadregn.hpp>
#include <components/lua/util.hpp>
#include <components/misc/color.hpp>
#include <components/misc/finitevalues.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/resource/resourcesystem.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/cellstore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/scene.hpp"
#include "../mwworld/weather.hpp"
#include "context.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
{
class WeatherStore
{
public:
const MWWorld::Weather* get(size_t index) const
{
return MWBase::Environment::get().getWorld()->getWeather(index);
}
const MWWorld::Weather* get(const ESM::RefId& id) const
{
return MWBase::Environment::get().getWorld()->getWeather(id);
}
size_t size() const { return MWBase::Environment::get().getWorld()->getAllWeather().size(); }
};
template <class Cell>
bool hasWeather(const Cell& cell, bool requireExterior)
{
if (requireExterior && !cell.mStore->isQuasiExterior() && !cell.mStore->isExterior())
return false;
return MWBase::Environment::get().getWorldScene()->isCellActive(*cell.mStore);
}
template <class Getter>
auto overloadForActiveCell(Getter&& getter, bool requireExterior = true)
{
using Result = std::invoke_result_t<Getter>;
return sol::overload(
[=](const MWLua::GCell& cell) -> Result {
if (!hasWeather(cell, requireExterior))
return Result{};
return getter();
},
[=](const MWLua::LCell& cell) -> Result {
if (!hasWeather(cell, requireExterior))
return Result{};
return getter();
});
}
template <class T>
using WeatherGetter = T (MWBase::World::*)() const;
template <class T>
auto overloadWeatherGetter(WeatherGetter<T> getter)
{
return overloadForActiveCell([=]() -> std::optional<T> {
const MWBase::World& world = *MWBase::Environment::get().getWorld();
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
{
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<MWWorld::Weather>("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["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) {
if (cloudsMaximumPercent <= 0.f)
throw std::runtime_error("Value must be greater than 0");
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) {
if (rainEntranceSpeed <= 0.f)
throw std::runtime_error("Value must be greater than 0");
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) -> ESM::RefId { return w.mRainLoopSoundID; },
[](MWWorld::Weather& w, sol::optional<std::string_view> rainLoopSoundID) {
w.mRainLoopSoundID = ESM::RefId::deserializeText(rainLoopSoundID.value_or(""));
});
weatherT["sunDiscSunsetColor"]
= 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::property([](const MWWorld::Weather& w) -> ESM::RefId { return w.mAmbientLoopSoundID; },
[](MWWorld::Weather& w, sol::optional<std::string_view> 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<std::string> {
if (w.mParticleEffect.empty())
return sol::nullopt;
return w.mParticleEffect;
},
[](MWWorld::Weather& w, sol::optional<std::string_view> 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);
MWBase::Environment::get().getESMStore()->get<ESM::Region>().find(region);
MWBase::Environment::get().getWorld()->changeWeather(region, weather.mId);
};
sol::usertype<WeatherStore> storeT = lua.new_usertype<WeatherStore>("WeatherWorldStore");
storeT[sol::meta_function::to_string]
= [](const WeatherStore& store) { return "{" + std::to_string(store.size()) + " Weather records}"; };
storeT[sol::meta_function::length] = [](const WeatherStore& store) { return store.size(); };
storeT[sol::meta_function::index] = sol::overload(
[](const WeatherStore& store, size_t index) -> const MWWorld::Weather* {
return store.get(LuaUtil::fromLuaIndex(index));
},
[](const WeatherStore& store, std::string_view id) -> const MWWorld::Weather* {
return store.get(ESM::RefId::deserializeText(id));
});
storeT[sol::meta_function::ipairs] = lua["ipairsForArray"].template get<sol::function>();
storeT[sol::meta_function::pairs] = lua["ipairsForArray"].template get<sol::function>();
// Provide access to the store.
api["records"] = WeatherStore{};
api["getCurrent"] = overloadForActiveCell(
[]() -> const MWWorld::Weather* { return &MWBase::Environment::get().getWorld()->getCurrentWeather(); });
api["getNext"] = overloadForActiveCell(
[]() -> const MWWorld::Weather* { return MWBase::Environment::get().getWorld()->getNextWeather(); });
api["getTransition"] = overloadWeatherGetter(&MWBase::World::getWeatherTransition);
api["getCurrentSunLightDirection"] = overloadForActiveCell(
[]() -> std::optional<osg::Vec4f> {
osg::Vec4f sunPos = MWBase::Environment::get().getWorld()->getSunLightPosition();
// normalize to get the direction towards the sun
sunPos.normalize();
// and invert it to get the direction of the sun light
return -sunPos;
},
false);
api["getCurrentSunVisibility"] = overloadWeatherGetter(&MWBase::World::getSunVisibility);
api["getCurrentSunPercentage"] = overloadWeatherGetter(&MWBase::World::getSunPercentage);
api["getCurrentWindSpeed"] = overloadWeatherGetter(&MWBase::World::getWindSpeed);
api["getCurrentStormDirection"] = overloadWeatherGetter(&MWBase::World::getStormDirection);
return LuaUtil::makeReadOnly(api);
}
}