From be0cbb7277514da74e1ecd27f708c604837158e3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 12 Jun 2025 17:17:28 +0200 Subject: [PATCH 1/6] Use the Color type for Light colours in Lua --- CMakeLists.txt | 2 +- apps/openmw/mwlua/types/light.cpp | 12 ++++++++++-- components/misc/color.cpp | 28 ++++++++++++++++------------ components/misc/color.hpp | 23 +++++++++++++++-------- files/lua_api/openmw/types.lua | 2 +- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e153fce677..0dd4f0ac27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 75) +set(OPENMW_LUA_API_REVISION 76) set(OPENMW_POSTPROCESSING_API_REVISION 2) set(OPENMW_VERSION_COMMITHASH "") diff --git a/apps/openmw/mwlua/types/light.cpp b/apps/openmw/mwlua/types/light.cpp index 41a0bf8d4a..171db177a4 100644 --- a/apps/openmw/mwlua/types/light.cpp +++ b/apps/openmw/mwlua/types/light.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -62,7 +63,13 @@ namespace if (rec["radius"] != sol::nil) light.mData.mRadius = rec["radius"]; if (rec["color"] != sol::nil) - light.mData.mColor = rec["color"]; + { + sol::object color = rec["color"]; + if (color.is()) + light.mData.mColor = color.as().toRGBA(); + else + light.mData.mColor = color.as(); + } setRecordFlag(rec, "isCarriable", ESM::Light::Carry, light); setRecordFlag(rec, "isDynamic", ESM::Light::Dynamic, light); setRecordFlag(rec, "isFire", ESM::Light::Fire, light); @@ -104,7 +111,8 @@ namespace MWLua record["value"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mValue; }); record["duration"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mTime; }); record["radius"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mRadius; }); - record["color"] = sol::readonly_property([](const ESM::Light& rec) -> int { return rec.mData.mColor; }); + record["color"] = sol::readonly_property( + [](const ESM::Light& rec) -> Misc::Color { return Misc::Color::fromRGB(rec.mData.mColor); }); record["isCarriable"] = sol::readonly_property( [](const ESM::Light& rec) -> bool { return rec.mData.mFlags & ESM::Light::Carry; }); record["isDynamic"] = sol::readonly_property( diff --git a/components/misc/color.cpp b/components/misc/color.cpp index b6e7be1fa3..0652e6dbb7 100644 --- a/components/misc/color.cpp +++ b/components/misc/color.cpp @@ -6,13 +6,12 @@ #include #include +#include + namespace Misc { Color::Color(float r, float g, float b, float a) - : mR(std::clamp(r, 0.f, 1.f)) - , mG(std::clamp(g, 0.f, 1.f)) - , mB(std::clamp(b, 0.f, 1.f)) - , mA(std::clamp(a, 0.f, 1.f)) + : mValue(std::clamp(r, 0.f, 1.f), std::clamp(g, 0.f, 1.f), std::clamp(b, 0.f, 1.f), std::clamp(a, 0.f, 1.f)) { } @@ -27,26 +26,31 @@ namespace Misc { if (hex.size() != 6) throw std::logic_error(std::string("Invalid hex color: ") += hex); - std::array rgb; - for (size_t i = 0; i < rgb.size(); i++) + Color col; + col.mValue.a() = 1; + for (size_t i = 0; i < 3; i++) { auto sub = hex.substr(i * 2, 2); int v = 0; auto [_, ec] = std::from_chars(sub.data(), sub.data() + sub.size(), v, 16); if (ec != std::errc()) throw std::logic_error(std::string("Invalid hex color: ") += hex); - rgb[i] = v / 255.0f; + col.mValue[i] = v / 255.0f; } - return Color(rgb[0], rgb[1], rgb[2], 1); + return col; + } + + Color Color::fromRGB(unsigned int value) + { + return Color(SceneUtil::colourFromRGB(value)); } std::string Color::toHex() const { std::string result(6, '0'); - std::array rgb = { mR, mG, mB }; - for (size_t i = 0; i < rgb.size(); i++) + for (size_t i = 0; i < 3; i++) { - int b = static_cast(rgb[i] * 255.0f); + int b = static_cast(mValue[i] * 255.0f); char* start = result.data() + i * 2; if (b < 16) start++; @@ -59,6 +63,6 @@ namespace Misc bool operator==(const Color& l, const Color& r) { - return l.mR == r.mR && l.mG == r.mG && l.mB == r.mB && l.mA == r.mA; + return l.mValue == r.mValue; } } diff --git a/components/misc/color.hpp b/components/misc/color.hpp index f23448c98f..20a24a73ad 100644 --- a/components/misc/color.hpp +++ b/components/misc/color.hpp @@ -3,31 +3,38 @@ #include +#include + namespace Misc { class Color { + explicit Color(osg::Vec4&& value) + : mValue(value) + { + } + public: + Color() = default; Color(float r, float g, float b, float a); - float r() const { return mR; } - float g() const { return mG; } - float b() const { return mB; } - float a() const { return mA; } + float r() const { return mValue.r(); } + float g() const { return mValue.g(); } + float b() const { return mValue.b(); } + float a() const { return mValue.a(); } std::string toString() const; static Color fromHex(std::string_view hex); + static Color fromRGB(unsigned int value); std::string toHex() const; + unsigned int toRGBA() const { return mValue.asRGBA(); } friend bool operator==(const Color& l, const Color& r); private: - float mR; - float mG; - float mB; - float mA; + osg::Vec4 mValue; }; } diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 0012a51c41..5aef5fac94 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1673,7 +1673,7 @@ -- @field #number value -- @field #number duration -- @field #number radius --- @field #number color +-- @field openmw.util#Color color -- @field #boolean isCarriable True if the light can be carried by actors and appears up in their inventory. -- @field #boolean isDynamic If true, the light will apply to actors and other moving objects -- @field #boolean isFire True if the light acts like a fire. From 45a51bceb2f96405d0993efe9c920243483a41ed Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 12 Jun 2025 17:48:11 +0200 Subject: [PATCH 2/6] Implement equal_to for Color --- components/lua/utilpackage.cpp | 1 + scripts/data/integration_tests/test_lua_api/global.lua | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index c135b66092..85492ccf06 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -237,6 +237,7 @@ namespace LuaUtil colorType["asRgba"] = [](const Misc::Color& c) { return Vec4(c.r(), c.g(), c.b(), c.a()); }; colorType["asRgb"] = [](const Misc::Color& c) { return Vec3(c.r(), c.g(), c.b()); }; colorType["asHex"] = [](const Misc::Color& c) { return c.toHex(); }; + colorType[sol::meta_function::equal_to] = [](const Misc::Color& a, const Misc::Color& b) { return a == b; }; sol::table color(lua, sol::create); color["rgba"] = [](float r, float g, float b, float a) { return Misc::Color(r, g, b, a); }; diff --git a/scripts/data/integration_tests/test_lua_api/global.lua b/scripts/data/integration_tests/test_lua_api/global.lua index 31d1b040db..f38c13fa58 100644 --- a/scripts/data/integration_tests/test_lua_api/global.lua +++ b/scripts/data/integration_tests/test_lua_api/global.lua @@ -157,7 +157,7 @@ testing.registerGlobalTest('record creation', function() value = 10, duration = 12, radius = 30, - color = 5, + color = util.color.hex('123456'), name = "TestLight", model = "meshes/marker_door.dae" } From 780a4904bde94043b83b33e231d1b04fda22728a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 13 Jun 2025 14:41:00 +0300 Subject: [PATCH 3/6] Handle implicitly checked files more consistently (#8563) --- .../contentselector/model/contentmodel.cpp | 26 ++++++++++++------- .../contentselector/model/contentmodel.hpp | 1 + .../contentselector/view/contentselector.cpp | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 2604a02885..53d5476fb6 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -129,7 +129,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index { if (depFile->isGameFile() && file->gameFiles().contains(depFile->fileName(), Qt::CaseInsensitive)) { - if (!depFile->builtIn() && !depFile->fromAnotherConfigFile() && !mCheckedFiles.contains(depFile)) + if (!isChecked(depFile)) break; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; @@ -215,8 +215,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int if (file == mGameFile) return QVariant(); - return (file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file)) ? Qt::Checked - : Qt::Unchecked; + return isChecked(file) ? Qt::Checked : Qt::Unchecked; } case Qt::UserRole: @@ -230,7 +229,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int } case Qt::UserRole + 1: - return mCheckedFiles.contains(file); + return isChecked(file); } return QVariant(); } @@ -264,9 +263,9 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const { int checkValue = value.toInt(); if (checkValue == Qt::Checked) - return mCheckedFiles.contains(file) || setCheckState(file, true); + return isChecked(file) || setCheckState(file, true); if (checkValue == Qt::Unchecked) - return !mCheckedFiles.contains(file) || setCheckState(file, false); + return !isChecked(file) || setCheckState(file, false); } } @@ -606,6 +605,14 @@ void ContentSelectorModel::ContentModel::sortFiles() emit layoutChanged(); } +bool ContentSelectorModel::ContentModel::isChecked(const EsmFile* file) const +{ + if (file == nullptr) + return false; + + return file->builtIn() || file->fromAnotherConfigFile() || mCheckedFiles.contains(file); +} + bool ContentSelectorModel::ContentModel::isEnabled(const QModelIndex& index) const { return (flags(index) & Qt::ItemIsEnabled); @@ -691,7 +698,7 @@ QList ContentSelectorModel::ContentModel:: } else { - if (!mCheckedFiles.contains(dependentFile)) + if (!isChecked(dependentFile)) { errors.append(LoadOrderError(LoadOrderError::ErrorCode_InactiveDependency, dependentfileName)); } @@ -706,7 +713,7 @@ QList ContentSelectorModel::ContentModel:: { // Warn the user if Bloodmoon is loaded before Tribunal (Tribunal is not a hard dependency) const EsmFile* tribunalFile = item("Tribunal.esm"); - if (tribunalFile != nullptr && mCheckedFiles.contains(tribunalFile) && row < indexFromItem(tribunalFile).row()) + if (isChecked(tribunalFile) && row < indexFromItem(tribunalFile).row()) errors.append(LoadOrderError(LoadOrderError::ErrorCode_LoadOrder, "Tribunal.esm")); } @@ -770,8 +777,9 @@ bool ContentSelectorModel::ContentModel::setCheckState(const EsmFile* file, bool for (const QString& upstreamName : file->gameFiles()) { const EsmFile* upstreamFile = item(upstreamName); - if (upstreamFile == nullptr || !mCheckedFiles.insert(upstreamFile).second) + if (upstreamFile == nullptr || isChecked(upstreamFile)) continue; + mCheckedFiles.insert(upstreamFile); QModelIndex upstreamIndex = indexFromItem(upstreamFile); emit dataChanged(upstreamIndex, upstreamIndex); } diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index cf182263f9..6260e5f1fe 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -58,6 +58,7 @@ namespace ContentSelectorModel QStringList gameFiles() const; void setCurrentGameFile(const EsmFile* file); + bool isChecked(const EsmFile* file) const; bool isEnabled(const QModelIndex& index) const; bool setCheckState(const EsmFile* file, bool isChecked); bool isNew(const QString& filepath) const; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index f8374b5db5..5fd54ee789 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -157,7 +157,7 @@ void ContentSelectorView::ContentSelector::setGameFile(const QString& filename) index = ui->gameFileView->findText(file->fileName()); // verify that the current index is also checked in the model - if (!mContentModel->setCheckState(file, true)) + if (!mContentModel->isChecked(file) && !mContentModel->setCheckState(file, true)) { // throw error in case file not found? return; From 6d89ae1a751aa729a734d68370e48d45721b08ea Mon Sep 17 00:00:00 2001 From: Sarah Sunday <1644563-ssunday@users.noreply.gitlab.com> Date: Sat, 14 Jun 2025 16:09:07 -0500 Subject: [PATCH 4/6] [CI] Mac - use qt@6 --- CI/before_install.osx.sh | 4 ++-- CI/before_script.osx.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index d73399c102..a8fe3d094a 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -11,8 +11,8 @@ brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake -command -v qmake >/dev/null 2>&1 || brew install qt@5 -export PATH="/opt/homebrew/opt/qt@5/bin:$PATH" +command -v qmake >/dev/null 2>&1 || brew install qt@6 +export PATH="/opt/homebrew/opt/qt@6/bin:$PATH" # Install deps brew install openal-soft icu4c yaml-cpp sqlite diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index 9f7a5bde8f..9be91f1632 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -9,7 +9,7 @@ cd build DEPENDENCIES_ROOT="/tmp/openmw-deps" -QT_PATH=$(brew --prefix qt@5) +QT_PATH=$(brew --prefix qt@6) ICU_PATH=$(brew --prefix icu4c) OPENAL_PATH=$(brew --prefix openal-soft) CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache From f84983a5d4afa0000f9a9ff50d5f99bb67200f02 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 17 Jun 2025 00:52:31 -0700 Subject: [PATCH 5/6] Changed desktop files to appropriately use StartupWMClass. What this does is... --- files/org.openmw.cs.desktop | 1 + files/org.openmw.launcher.desktop | 1 + 2 files changed, 2 insertions(+) diff --git a/files/org.openmw.cs.desktop b/files/org.openmw.cs.desktop index a5e3c27f2a..47e01185b3 100644 --- a/files/org.openmw.cs.desktop +++ b/files/org.openmw.cs.desktop @@ -8,3 +8,4 @@ TryExec=openmw-cs Exec=openmw-cs Icon=openmw-cs Categories=Game;RolePlaying; +StartupWMClass=openmw-cs diff --git a/files/org.openmw.launcher.desktop b/files/org.openmw.launcher.desktop index 36d01b33e9..51ee14c871 100644 --- a/files/org.openmw.launcher.desktop +++ b/files/org.openmw.launcher.desktop @@ -8,3 +8,4 @@ TryExec=openmw-launcher Exec=openmw-launcher Icon=openmw Categories=Game;RolePlaying; +StartupWMClass=openmw-launcher From 4a6d2465b4e68a1ed116a73605861015548b419c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 17 Jun 2025 19:11:39 +0300 Subject: [PATCH 6/6] Remove Qt PATH adjustment (Qt6 is symlinked) --- CI/before_install.osx.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index a8fe3d094a..30822f5d00 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -12,7 +12,6 @@ brew install curl xquartz gd fontconfig freetype harfbuzz brotli s3cmd command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake command -v qmake >/dev/null 2>&1 || brew install qt@6 -export PATH="/opt/homebrew/opt/qt@6/bin:$PATH" # Install deps brew install openal-soft icu4c yaml-cpp sqlite