From e1cd5250af8e3a6003a233ed4b9be130c1b099e4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 7 Oct 2023 00:53:07 +0300 Subject: [PATCH 001/231] Use sun visibility for sunlight scattering (bug #7309) --- CHANGELOG.md | 1 + files/shaders/compatibility/water.frag | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26957e6c8d..c1d3ec0f9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Bug #7284: "Your weapon has no effect." message doesn't always show when the player character attempts to attack Bug #7298: Water ripples from projectiles sometimes are not spawned Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes + Bug #7309: Sunlight scattering is visible in inappropriate situations Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 987dab10a6..09b7090958 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -173,7 +173,7 @@ void main(void) vec3 waterColor = WATER_COLOR * sunFade; vec4 sunSpec = lcalcSpecular(0); - // alpha component is sun visibility; we want to start fading specularity when visibility is low + // alpha component is sun visibility; we want to start fading lighting effects when visibility is low sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); // artificial specularity to make rain ripples more noticeable @@ -202,7 +202,7 @@ void main(void) float sunHeight = lVec.z; vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); vec3 lR = reflect(lVec, lNormal); - float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0); + float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * sunSpec.a * clamp(1.0-exp(-sunHeight), 0.0, 1.0); gl_FragData[0].xyz = mix(mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; gl_FragData[0].w = 1.0; From 335dbffe6eb928da9b134f917e1b1cf5f988f9d2 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 13:09:37 +0100 Subject: [PATCH 002/231] Avoid std::string to QString conversion for label --- apps/opencs/model/prefs/boolsetting.cpp | 4 ++-- apps/opencs/model/prefs/boolsetting.hpp | 2 +- apps/opencs/model/prefs/coloursetting.cpp | 4 ++-- apps/opencs/model/prefs/coloursetting.hpp | 3 +-- apps/opencs/model/prefs/doublesetting.cpp | 4 ++-- apps/opencs/model/prefs/doublesetting.hpp | 3 +-- apps/opencs/model/prefs/enumsetting.cpp | 4 ++-- apps/opencs/model/prefs/enumsetting.hpp | 4 ++-- apps/opencs/model/prefs/intsetting.cpp | 4 ++-- apps/opencs/model/prefs/intsetting.hpp | 2 +- apps/opencs/model/prefs/modifiersetting.cpp | 4 ++-- apps/opencs/model/prefs/modifiersetting.hpp | 2 +- apps/opencs/model/prefs/setting.cpp | 7 +------ apps/opencs/model/prefs/setting.hpp | 6 +++--- apps/opencs/model/prefs/shortcutsetting.cpp | 4 ++-- apps/opencs/model/prefs/shortcutsetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 22 +++++++++------------ apps/opencs/model/prefs/state.hpp | 19 +++++++++--------- apps/opencs/model/prefs/stringsetting.cpp | 2 +- apps/opencs/model/prefs/stringsetting.hpp | 4 ++-- apps/opencs/view/prefs/keybindingpage.cpp | 4 ++-- 21 files changed, 49 insertions(+), 61 deletions(-) diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index c668bc0af4..c2718cd7aa 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -11,7 +11,7 @@ #include "state.hpp" CSMPrefs::BoolSetting::BoolSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, bool default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, bool default_) : Setting(parent, mutex, key, label) , mDefault(default_) , mWidget(nullptr) @@ -26,7 +26,7 @@ CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip(const std::string& tool std::pair CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) { - mWidget = new QCheckBox(QString::fromUtf8(getLabel().c_str()), parent); + mWidget = new QCheckBox(getLabel(), parent); mWidget->setCheckState(mDefault ? Qt::Checked : Qt::Unchecked); if (!mTooltip.empty()) diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp index e75ea1a346..60d51a9888 100644 --- a/apps/opencs/model/prefs/boolsetting.hpp +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -21,7 +21,7 @@ namespace CSMPrefs QCheckBox* mWidget; public: - BoolSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, bool default_); + BoolSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, bool default_); BoolSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index 86f3a5d772..b3f1b70513 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -14,7 +14,7 @@ #include "state.hpp" CSMPrefs::ColourSetting::ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, QColor default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, QColor default_) : Setting(parent, mutex, key, label) , mDefault(std::move(default_)) , mWidget(nullptr) @@ -29,7 +29,7 @@ CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip(const std::string& std::pair CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) { - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); mWidget = new CSVWidget::ColorEditor(mDefault, parent); diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp index 0c22d9cc5d..e36ceb48b5 100644 --- a/apps/opencs/model/prefs/coloursetting.hpp +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -29,8 +29,7 @@ namespace CSMPrefs CSVWidget::ColorEditor* mWidget; public: - ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, QColor default_); + ColourSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, QColor default_); ColourSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 7e3aadb0c3..d88678a8e9 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -15,7 +15,7 @@ #include "state.hpp" CSMPrefs::DoubleSetting::DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, double default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, double default_) : Setting(parent, mutex, key, label) , mPrecision(2) , mMin(0) @@ -58,7 +58,7 @@ CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip(const std::string& std::pair CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) { - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); mWidget = new QDoubleSpinBox(parent); mWidget->setDecimals(mPrecision); diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index c951d2a88c..76135c4cdb 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -21,8 +21,7 @@ namespace CSMPrefs QDoubleSpinBox* mWidget; public: - DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, double default_); + DoubleSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, double default_); DoubleSetting& setPrecision(int precision); diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index a3ac9bce2b..6d925d1b31 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -45,7 +45,7 @@ CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const std::string& value, const } CSMPrefs::EnumSetting::EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, const EnumValue& default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, const EnumValue& default_) : Setting(parent, mutex, key, label) , mDefault(default_) , mWidget(nullptr) @@ -78,7 +78,7 @@ CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const std::string& value, std::pair CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) { - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); mWidget = new QComboBox(parent); diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp index 57bd2115ce..82ce209922 100644 --- a/apps/opencs/model/prefs/enumsetting.hpp +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -44,8 +44,8 @@ namespace CSMPrefs QComboBox* mWidget; public: - EnumSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, - const EnumValue& default_); + EnumSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, const EnumValue& default_); EnumSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 90cc77c788..e8fd4acce6 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -15,7 +15,7 @@ #include "state.hpp" CSMPrefs::IntSetting::IntSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, int default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, int default_) : Setting(parent, mutex, key, label) , mMin(0) , mMax(std::numeric_limits::max()) @@ -51,7 +51,7 @@ CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip(const std::string& toolti std::pair CSMPrefs::IntSetting::makeWidgets(QWidget* parent) { - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); mWidget = new QSpinBox(parent); mWidget->setRange(mMin, mMax); diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp index 8a655178a4..48f24e5b78 100644 --- a/apps/opencs/model/prefs/intsetting.hpp +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -23,7 +23,7 @@ namespace CSMPrefs QSpinBox* mWidget; public: - IntSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, int default_); + IntSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, int default_); // defaults to [0, std::numeric_limits::max()] IntSetting& setRange(int min, int max); diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index 8752a4d51e..d20a427217 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -19,7 +19,7 @@ class QWidget; namespace CSMPrefs { - ModifierSetting::ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) + ModifierSetting::ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) : Setting(parent, mutex, key, label) , mButton(nullptr) , mEditorActive(false) @@ -33,7 +33,7 @@ namespace CSMPrefs QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(modifier).c_str()); - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); QPushButton* widget = new QPushButton(text, parent); widget->setCheckable(true); diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp index ae984243ac..fb186329a9 100644 --- a/apps/opencs/model/prefs/modifiersetting.hpp +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -20,7 +20,7 @@ namespace CSMPrefs Q_OBJECT public: - ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); + ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); std::pair makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp index efe360d1e8..d507236cc5 100644 --- a/apps/opencs/model/prefs/setting.cpp +++ b/apps/opencs/model/prefs/setting.cpp @@ -14,7 +14,7 @@ QMutex* CSMPrefs::Setting::getMutex() return mMutex; } -CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) +CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) : QObject(parent->getState()) , mParent(parent) , mMutex(mutex) @@ -40,11 +40,6 @@ const std::string& CSMPrefs::Setting::getKey() const return mKey; } -const std::string& CSMPrefs::Setting::getLabel() const -{ - return mLabel; -} - int CSMPrefs::Setting::toInt() const { QMutexLocker lock(mMutex); diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index f63271b3f2..01138c6a14 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -21,13 +21,13 @@ namespace CSMPrefs Category* mParent; QMutex* mMutex; std::string mKey; - std::string mLabel; + QString mLabel; protected: QMutex* getMutex(); public: - Setting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); + Setting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); ~Setting() override = default; @@ -46,7 +46,7 @@ namespace CSMPrefs const std::string& getKey() const; - const std::string& getLabel() const; + const QString& getLabel() const { return mLabel; } int toInt() const; diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index d8c71d7008..23c8d9c7a9 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -18,7 +18,7 @@ namespace CSMPrefs { - ShortcutSetting::ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label) + ShortcutSetting::ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) : Setting(parent, mutex, key, label) , mButton(nullptr) , mEditorActive(false) @@ -37,7 +37,7 @@ namespace CSMPrefs QString text = QString::fromUtf8(State::get().getShortcutManager().convertToString(sequence).c_str()); - QLabel* label = new QLabel(QString::fromUtf8(getLabel().c_str()), parent); + QLabel* label = new QLabel(getLabel(), parent); QPushButton* widget = new QPushButton(text, parent); widget->setCheckable(true); diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index bef140dada..7dd2aecd61 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -22,7 +22,7 @@ namespace CSMPrefs Q_OBJECT public: - ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label); + ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); std::pair makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 97f29bc8be..f95ceb896f 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -463,7 +463,7 @@ void CSMPrefs::State::declareCategory(const std::string& key) } } -CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const std::string& label, int default_) +CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const QString& label, int default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -479,8 +479,7 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const return *setting; } -CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble( - const std::string& key, const std::string& label, double default_) +CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, const QString& label, double default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -499,7 +498,7 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble( return *setting; } -CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, const std::string& label, bool default_) +CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, const QString& label, bool default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -516,8 +515,7 @@ CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, cons return *setting; } -CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum( - const std::string& key, const std::string& label, EnumValue default_) +CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(const std::string& key, const QString& label, EnumValue default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -534,8 +532,7 @@ CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum( return *setting; } -CSMPrefs::ColourSetting& CSMPrefs::State::declareColour( - const std::string& key, const std::string& label, QColor default_) +CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(const std::string& key, const QString& label, QColor default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -554,7 +551,7 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour( } CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( - const std::string& key, const std::string& label, const QKeySequence& default_) + const std::string& key, const QString& label, const QKeySequence& default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -576,7 +573,7 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( } CSMPrefs::StringSetting& CSMPrefs::State::declareString( - const std::string& key, const std::string& label, std::string default_) + const std::string& key, const QString& label, std::string default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -593,8 +590,7 @@ CSMPrefs::StringSetting& CSMPrefs::State::declareString( return *setting; } -CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier( - const std::string& key, const std::string& label, int default_) +CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& key, const QString& label, int default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -625,7 +621,7 @@ void CSMPrefs::State::declareSeparator() mCurrentCategory->second.addSetting(setting); } -void CSMPrefs::State::declareSubcategory(const std::string& label) +void CSMPrefs::State::declareSubcategory(const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 354f4552e3..c74f317c94 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -60,25 +60,24 @@ namespace CSMPrefs void declareCategory(const std::string& key); - IntSetting& declareInt(const std::string& key, const std::string& label, int default_); - DoubleSetting& declareDouble(const std::string& key, const std::string& label, double default_); + IntSetting& declareInt(const std::string& key, const QString& label, int default_); + DoubleSetting& declareDouble(const std::string& key, const QString& label, double default_); - BoolSetting& declareBool(const std::string& key, const std::string& label, bool default_); + BoolSetting& declareBool(const std::string& key, const QString& label, bool default_); - EnumSetting& declareEnum(const std::string& key, const std::string& label, EnumValue default_); + EnumSetting& declareEnum(const std::string& key, const QString& label, EnumValue default_); - ColourSetting& declareColour(const std::string& key, const std::string& label, QColor default_); + ColourSetting& declareColour(const std::string& key, const QString& label, QColor default_); - ShortcutSetting& declareShortcut( - const std::string& key, const std::string& label, const QKeySequence& default_); + ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); - StringSetting& declareString(const std::string& key, const std::string& label, std::string default_); + StringSetting& declareString(const std::string& key, const QString& label, std::string default_); - ModifierSetting& declareModifier(const std::string& key, const std::string& label, int modifier_); + ModifierSetting& declareModifier(const std::string& key, const QString& label, int modifier_); void declareSeparator(); - void declareSubcategory(const std::string& label); + void declareSubcategory(const QString& label); void setDefault(const std::string& key, const std::string& default_); diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index 2a8fdd587a..bb7a9b7fe3 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -12,7 +12,7 @@ #include "state.hpp" CSMPrefs::StringSetting::StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const std::string& label, std::string_view default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, std::string_view default_) : Setting(parent, mutex, key, label) , mDefault(default_) , mWidget(nullptr) diff --git a/apps/opencs/model/prefs/stringsetting.hpp b/apps/opencs/model/prefs/stringsetting.hpp index 4b5499ef86..81adc55361 100644 --- a/apps/opencs/model/prefs/stringsetting.hpp +++ b/apps/opencs/model/prefs/stringsetting.hpp @@ -23,8 +23,8 @@ namespace CSMPrefs QLineEdit* mWidget; public: - StringSetting(Category* parent, QMutex* mutex, const std::string& key, const std::string& label, - std::string_view default_); + StringSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, std::string_view default_); StringSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index d3cc1ff889..7b6337a6ab 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -82,7 +82,7 @@ namespace CSVPrefs } else { - if (setting->getLabel().empty()) + if (setting->getLabel().isEmpty()) { // Insert empty space assert(mPageLayout); @@ -99,7 +99,7 @@ namespace CSVPrefs mStackedLayout->addWidget(pageWidget); - mPageSelector->addItem(QString::fromUtf8(setting->getLabel().c_str())); + mPageSelector->addItem(setting->getLabel()); } } } From fb6e429daddc77516ef125a725dd0b9485b26f0f Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 13:17:06 +0100 Subject: [PATCH 003/231] Remove "separators" from cs settings Those are just empty widgets which do not make significant visible difference in the UI. --- apps/opencs/model/prefs/state.cpp | 17 --------------- apps/opencs/model/prefs/state.hpp | 2 -- apps/opencs/view/prefs/keybindingpage.cpp | 25 +++++++---------------- apps/opencs/view/prefs/page.cpp | 4 ---- 4 files changed, 7 insertions(+), 41 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index f95ceb896f..15f8cacef1 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -40,7 +40,6 @@ void CSMPrefs::State::declare() .setTooltip( "If a newly open top level window is showing status bars or not. " " Note that this does not affect existing windows."); - declareSeparator(); declareBool("reuse", "Reuse Subviews", true) .setTooltip( "When a new subview is requested and a matching subview already " @@ -58,7 +57,6 @@ void CSMPrefs::State::declare() declareInt("minimum-width", "Minimum subview width", 325) .setTooltip("Minimum width of subviews.") .setRange(50, 10000); - declareSeparator(); EnumValue scrollbarOnly("Scrollbar Only", "Simple addition of scrollbars, the view window " "does not grow automatically."); @@ -98,7 +96,6 @@ void CSMPrefs::State::declare() declareEnum("double-s", "Shift Double Click", editRecord).addValues(doubleClickValues); declareEnum("double-c", "Control Double Click", view).addValues(doubleClickValues); declareEnum("double-sc", "Shift Control Double Click", editRecordAndClose).addValues(doubleClickValues); - declareSeparator(); EnumValue jumpAndSelect("Jump and Select", "Scroll new record into view and make it the selection"); declareEnum("jump-to-added", "Action on adding or cloning a record", jumpAndSelect) .addValue(jumpAndSelect) @@ -161,7 +158,6 @@ void CSMPrefs::State::declare() declareInt("error-height", "Initial height of the error panel", 100).setRange(100, 10000); declareBool("highlight-occurrences", "Highlight other occurrences of selected names", true); declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); - declareSeparator(); declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta")); declareColour("colour-float", "Highlight Colour: Float Literals", QColor("magenta")); declareColour("colour-name", "Highlight Colour: Names", QColor("grey")); @@ -180,14 +176,12 @@ void CSMPrefs::State::declare() declareDouble("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); declareDouble("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); - declareSeparator(); declareDouble("p-navi-free-sensitivity", "Free Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); declareBool("p-navi-free-invert", "Invert Free Camera Mouse Input", false); declareDouble("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); declareDouble("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); declareDouble("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); - declareSeparator(); declareDouble("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); declareBool("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false); @@ -195,7 +189,6 @@ void CSMPrefs::State::declare() declareDouble("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4) .setRange(0.001, 1000.0); declareBool("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true); - declareSeparator(); declareBool("context-select", "Context Sensitive Selection", false); declareDouble("drag-factor", "Mouse sensitivity during drag operations", 1.0).setRange(0.001, 100.0); @@ -611,16 +604,6 @@ CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& k return *setting; } -void CSMPrefs::State::declareSeparator() -{ - if (mCurrentCategory == mCategories.end()) - throw std::logic_error("no category for setting"); - - CSMPrefs::Setting* setting = new CSMPrefs::Setting(&mCurrentCategory->second, &mMutex, "", ""); - - mCurrentCategory->second.addSetting(setting); -} - void CSMPrefs::State::declareSubcategory(const QString& label) { if (mCurrentCategory == mCategories.end()) diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index c74f317c94..86d2c19da6 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -75,8 +75,6 @@ namespace CSMPrefs ModifierSetting& declareModifier(const std::string& key, const QString& label, int modifier_); - void declareSeparator(); - void declareSubcategory(const QString& label); void setDefault(const std::string& key, const std::string& default_); diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index 7b6337a6ab..60170be283 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -80,27 +80,16 @@ namespace CSVPrefs int next = mPageLayout->rowCount(); mPageLayout->addWidget(widgets.second, next, 0, 1, 2); } - else + else if (!setting->getLabel().isEmpty()) { - if (setting->getLabel().isEmpty()) - { - // Insert empty space - assert(mPageLayout); + // Create new page + QWidget* pageWidget = new QWidget(); + mPageLayout = new QGridLayout(pageWidget); + mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); - int next = mPageLayout->rowCount(); - mPageLayout->addWidget(new QWidget(), next, 0); - } - else - { - // Create new page - QWidget* pageWidget = new QWidget(); - mPageLayout = new QGridLayout(pageWidget); - mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + mStackedLayout->addWidget(pageWidget); - mStackedLayout->addWidget(pageWidget); - - mPageSelector->addItem(setting->getLabel()); - } + mPageSelector->addItem(setting->getLabel()); } } diff --git a/apps/opencs/view/prefs/page.cpp b/apps/opencs/view/prefs/page.cpp index 4f04a39f00..fc70adf482 100644 --- a/apps/opencs/view/prefs/page.cpp +++ b/apps/opencs/view/prefs/page.cpp @@ -37,8 +37,4 @@ void CSVPrefs::Page::addSetting(CSMPrefs::Setting* setting) { mGrid->addWidget(widgets.second, next, 0, 1, 2); } - else - { - mGrid->addWidget(new QWidget(this), next, 0); - } } From e07d8f30662512b5f376bfdaaee2040be83ce112 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 13:28:10 +0100 Subject: [PATCH 004/231] Add separate setting type for subcategory --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/prefs/boolsetting.cpp | 4 ++-- apps/opencs/model/prefs/boolsetting.hpp | 2 +- apps/opencs/model/prefs/coloursetting.cpp | 4 ++-- apps/opencs/model/prefs/coloursetting.hpp | 2 +- apps/opencs/model/prefs/doublesetting.cpp | 4 ++-- apps/opencs/model/prefs/doublesetting.hpp | 2 +- apps/opencs/model/prefs/enumsetting.cpp | 4 ++-- apps/opencs/model/prefs/enumsetting.hpp | 2 +- apps/opencs/model/prefs/intsetting.cpp | 4 ++-- apps/opencs/model/prefs/intsetting.hpp | 2 +- apps/opencs/model/prefs/modifiersetting.cpp | 4 ++-- apps/opencs/model/prefs/modifiersetting.hpp | 2 +- apps/opencs/model/prefs/setting.cpp | 7 ------ apps/opencs/model/prefs/setting.hpp | 17 +++++++++----- apps/opencs/model/prefs/shortcutsetting.cpp | 4 ++-- apps/opencs/model/prefs/shortcutsetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 5 ++-- apps/opencs/model/prefs/stringsetting.cpp | 4 ++-- apps/opencs/model/prefs/stringsetting.hpp | 2 +- apps/opencs/model/prefs/subcategory.cpp | 21 +++++++++++++++++ apps/opencs/model/prefs/subcategory.hpp | 26 +++++++++++++++++++++ apps/opencs/view/prefs/keybindingpage.cpp | 19 ++++++++------- apps/opencs/view/prefs/page.cpp | 13 ++++++----- 24 files changed, 102 insertions(+), 56 deletions(-) create mode 100644 apps/opencs/model/prefs/subcategory.cpp create mode 100644 apps/opencs/model/prefs/subcategory.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 20bd62d145..b040980529 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -116,7 +116,7 @@ opencs_units (view/prefs opencs_units (model/prefs state setting intsetting doublesetting boolsetting enumsetting coloursetting shortcut - shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting + shortcuteventhandler shortcutmanager shortcutsetting modifiersetting stringsetting subcategory ) opencs_units (model/prefs diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index c2718cd7aa..c1eb626969 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -24,7 +24,7 @@ CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip(const std::string& tool return *this; } -std::pair CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) { mWidget = new QCheckBox(getLabel(), parent); mWidget->setCheckState(mDefault ? Qt::Checked : Qt::Unchecked); @@ -37,7 +37,7 @@ std::pair CSMPrefs::BoolSetting::makeWidgets(QWidget* parent connect(mWidget, &QCheckBox::stateChanged, this, &BoolSetting::valueChanged); - return std::make_pair(static_cast(nullptr), mWidget); + return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::BoolSetting::updateWidget() diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp index 60d51a9888..9d53f98e9e 100644 --- a/apps/opencs/model/prefs/boolsetting.hpp +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -26,7 +26,7 @@ namespace CSMPrefs BoolSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index b3f1b70513..e2ece04722 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -27,7 +27,7 @@ CSMPrefs::ColourSetting& CSMPrefs::ColourSetting::setTooltip(const std::string& return *this; } -std::pair CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); @@ -42,7 +42,7 @@ std::pair CSMPrefs::ColourSetting::makeWidgets(QWidget* pare connect(mWidget, &CSVWidget::ColorEditor::pickingFinished, this, &ColourSetting::valueChanged); - return std::make_pair(label, mWidget); + return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::ColourSetting::updateWidget() diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp index e36ceb48b5..5a1a7a2df2 100644 --- a/apps/opencs/model/prefs/coloursetting.hpp +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -34,7 +34,7 @@ namespace CSMPrefs ColourSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index d88678a8e9..153298ce57 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -56,7 +56,7 @@ CSMPrefs::DoubleSetting& CSMPrefs::DoubleSetting::setTooltip(const std::string& return *this; } -std::pair CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); @@ -74,7 +74,7 @@ std::pair CSMPrefs::DoubleSetting::makeWidgets(QWidget* pare connect(mWidget, qOverload(&QDoubleSpinBox::valueChanged), this, &DoubleSetting::valueChanged); - return std::make_pair(label, mWidget); + return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::DoubleSetting::updateWidget() diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index 76135c4cdb..33342d2f5b 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -35,7 +35,7 @@ namespace CSMPrefs DoubleSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index 6d925d1b31..d55e4005a4 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -76,7 +76,7 @@ CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const std::string& value, return *this; } -std::pair CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); @@ -105,7 +105,7 @@ std::pair CSMPrefs::EnumSetting::makeWidgets(QWidget* parent connect(mWidget, qOverload(&QComboBox::currentIndexChanged), this, &EnumSetting::valueChanged); - return std::make_pair(label, mWidget); + return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::EnumSetting::updateWidget() diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp index 82ce209922..f430988aa6 100644 --- a/apps/opencs/model/prefs/enumsetting.hpp +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -56,7 +56,7 @@ namespace CSMPrefs EnumSetting& addValue(const std::string& value, const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index e8fd4acce6..7d0b40a45a 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -49,7 +49,7 @@ CSMPrefs::IntSetting& CSMPrefs::IntSetting::setTooltip(const std::string& toolti return *this; } -std::pair CSMPrefs::IntSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::IntSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); @@ -66,7 +66,7 @@ std::pair CSMPrefs::IntSetting::makeWidgets(QWidget* parent) connect(mWidget, qOverload(&QSpinBox::valueChanged), this, &IntSetting::valueChanged); - return std::make_pair(label, mWidget); + return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::IntSetting::updateWidget() diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp index 48f24e5b78..8fb3bdb1f6 100644 --- a/apps/opencs/model/prefs/intsetting.hpp +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -35,7 +35,7 @@ namespace CSMPrefs IntSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index d20a427217..173092dc2b 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -26,7 +26,7 @@ namespace CSMPrefs { } - std::pair ModifierSetting::makeWidgets(QWidget* parent) + SettingWidgets ModifierSetting::makeWidgets(QWidget* parent) { int modifier = 0; State::get().getShortcutManager().getModifier(getKey(), modifier); @@ -46,7 +46,7 @@ namespace CSMPrefs connect(widget, &QPushButton::toggled, this, &ModifierSetting::buttonToggled); - return std::make_pair(label, widget); + return SettingWidgets{ .mLabel = label, .mInput = widget, .mLayout = nullptr }; } void ModifierSetting::updateWidget() diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp index fb186329a9..9e308875fd 100644 --- a/apps/opencs/model/prefs/modifiersetting.hpp +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -22,7 +22,7 @@ namespace CSMPrefs public: ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp index d507236cc5..666b4ba1b1 100644 --- a/apps/opencs/model/prefs/setting.cpp +++ b/apps/opencs/model/prefs/setting.cpp @@ -23,13 +23,6 @@ CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& k { } -std::pair CSMPrefs::Setting::makeWidgets(QWidget* parent) -{ - return std::pair(0, 0); -} - -void CSMPrefs::Setting::updateWidget() {} - const CSMPrefs::Category* CSMPrefs::Setting::getParent() const { return mParent; diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index 01138c6a14..882add20ca 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -9,11 +9,20 @@ class QWidget; class QColor; class QMutex; +class QGridLayout; +class QLabel; namespace CSMPrefs { class Category; + struct SettingWidgets + { + QLabel* mLabel; + QWidget* mInput; + QGridLayout* mLayout; + }; + class Setting : public QObject { Q_OBJECT @@ -31,16 +40,12 @@ namespace CSMPrefs ~Setting() override = default; - /// Return label, input widget. - /// - /// \note first can be a 0-pointer, which means that the label is part of the input - /// widget. - virtual std::pair makeWidgets(QWidget* parent); + virtual SettingWidgets makeWidgets(QWidget* parent) = 0; /// Updates the widget returned by makeWidgets() to the current setting. /// /// \note If make_widgets() has not been called yet then nothing happens. - virtual void updateWidget(); + virtual void updateWidget() = 0; const Category* getParent() const; diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index 23c8d9c7a9..aae290b3f4 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -30,7 +30,7 @@ namespace CSMPrefs } } - std::pair ShortcutSetting::makeWidgets(QWidget* parent) + SettingWidgets ShortcutSetting::makeWidgets(QWidget* parent) { QKeySequence sequence; State::get().getShortcutManager().getSequence(getKey(), sequence); @@ -50,7 +50,7 @@ namespace CSMPrefs connect(widget, &QPushButton::toggled, this, &ShortcutSetting::buttonToggled); - return std::make_pair(label, widget); + return SettingWidgets{ .mLabel = label, .mInput = widget, .mLayout = nullptr }; } void ShortcutSetting::updateWidget() diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index 7dd2aecd61..74cad995d4 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -24,7 +24,7 @@ namespace CSMPrefs public: ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 15f8cacef1..21965810e5 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -609,9 +610,7 @@ void CSMPrefs::State::declareSubcategory(const QString& label) if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - CSMPrefs::Setting* setting = new CSMPrefs::Setting(&mCurrentCategory->second, &mMutex, "", label); - - mCurrentCategory->second.addSetting(setting); + mCurrentCategory->second.addSetting(new CSMPrefs::Subcategory(&mCurrentCategory->second, &mMutex, label)); } void CSMPrefs::State::setDefault(const std::string& key, const std::string& default_) diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index bb7a9b7fe3..0cba2d047d 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -25,7 +25,7 @@ CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip(const std::string& return *this; } -std::pair CSMPrefs::StringSetting::makeWidgets(QWidget* parent) +CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) { mWidget = new QLineEdit(QString::fromUtf8(mDefault.c_str()), parent); @@ -37,7 +37,7 @@ std::pair CSMPrefs::StringSetting::makeWidgets(QWidget* pare connect(mWidget, &QLineEdit::textChanged, this, &StringSetting::textChanged); - return std::make_pair(static_cast(nullptr), mWidget); + return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget, .mLayout = nullptr }; } void CSMPrefs::StringSetting::updateWidget() diff --git a/apps/opencs/model/prefs/stringsetting.hpp b/apps/opencs/model/prefs/stringsetting.hpp index 81adc55361..5c03a6ea12 100644 --- a/apps/opencs/model/prefs/stringsetting.hpp +++ b/apps/opencs/model/prefs/stringsetting.hpp @@ -29,7 +29,7 @@ namespace CSMPrefs StringSetting& setTooltip(const std::string& tooltip); /// Return label, input widget. - std::pair makeWidgets(QWidget* parent) override; + SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override; diff --git a/apps/opencs/model/prefs/subcategory.cpp b/apps/opencs/model/prefs/subcategory.cpp new file mode 100644 index 0000000000..cca558407c --- /dev/null +++ b/apps/opencs/model/prefs/subcategory.cpp @@ -0,0 +1,21 @@ +#include "subcategory.hpp" + +#include + +namespace CSMPrefs +{ + class Category; + + Subcategory::Subcategory(Category* parent, QMutex* mutex, const QString& label) + : Setting(parent, mutex, "", label) + { + } + + SettingWidgets Subcategory::makeWidgets(QWidget* /*parent*/) + { + QGridLayout* const layout = new QGridLayout(); + layout->setSizeConstraint(QLayout::SetMinAndMaxSize); + + return SettingWidgets{ .mLabel = nullptr, .mInput = nullptr, .mLayout = layout }; + } +} diff --git a/apps/opencs/model/prefs/subcategory.hpp b/apps/opencs/model/prefs/subcategory.hpp new file mode 100644 index 0000000000..4f62c1743c --- /dev/null +++ b/apps/opencs/model/prefs/subcategory.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_SUBCATEGORY_H +#define OPENMW_APPS_OPENCS_MODEL_PREFS_SUBCATEGORY_H + +#include "setting.hpp" + +#include +#include + +namespace CSMPrefs +{ + class Category; + + class Subcategory final : public Setting + { + Q_OBJECT + + public: + explicit Subcategory(Category* parent, QMutex* mutex, const QString& label); + + SettingWidgets makeWidgets(QWidget* parent) override; + + void updateWidget() override {} + }; +} + +#endif diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index 60170be283..b88627aca1 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -61,31 +62,31 @@ namespace CSVPrefs void KeyBindingPage::addSetting(CSMPrefs::Setting* setting) { - std::pair widgets = setting->makeWidgets(this); + const CSMPrefs::SettingWidgets widgets = setting->makeWidgets(this); - if (widgets.first) + if (widgets.mLabel != nullptr && widgets.mInput != nullptr) { // Label, Option widgets assert(mPageLayout); int next = mPageLayout->rowCount(); - mPageLayout->addWidget(widgets.first, next, 0); - mPageLayout->addWidget(widgets.second, next, 1); + mPageLayout->addWidget(widgets.mLabel, next, 0); + mPageLayout->addWidget(widgets.mInput, next, 1); } - else if (widgets.second) + else if (widgets.mInput != nullptr) { // Wide single widget assert(mPageLayout); int next = mPageLayout->rowCount(); - mPageLayout->addWidget(widgets.second, next, 0, 1, 2); + mPageLayout->addWidget(widgets.mInput, next, 0, 1, 2); } - else if (!setting->getLabel().isEmpty()) + else if (widgets.mLayout != nullptr) { // Create new page QWidget* pageWidget = new QWidget(); - mPageLayout = new QGridLayout(pageWidget); - mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); + mPageLayout = widgets.mLayout; + mPageLayout->setParent(pageWidget); mStackedLayout->addWidget(pageWidget); diff --git a/apps/opencs/view/prefs/page.cpp b/apps/opencs/view/prefs/page.cpp index fc70adf482..cc74122782 100644 --- a/apps/opencs/view/prefs/page.cpp +++ b/apps/opencs/view/prefs/page.cpp @@ -6,6 +6,7 @@ #include #include +#include #include "../../model/prefs/category.hpp" #include "../../model/prefs/setting.hpp" @@ -24,17 +25,17 @@ CSVPrefs::Page::Page(CSMPrefs::Category& category, QWidget* parent) void CSVPrefs::Page::addSetting(CSMPrefs::Setting* setting) { - std::pair widgets = setting->makeWidgets(this); + const CSMPrefs::SettingWidgets widgets = setting->makeWidgets(this); int next = mGrid->rowCount(); - if (widgets.first) + if (widgets.mLabel != nullptr && widgets.mInput != nullptr) { - mGrid->addWidget(widgets.first, next, 0); - mGrid->addWidget(widgets.second, next, 1); + mGrid->addWidget(widgets.mLabel, next, 0); + mGrid->addWidget(widgets.mInput, next, 1); } - else if (widgets.second) + else if (widgets.mInput != nullptr) { - mGrid->addWidget(widgets.second, next, 0, 1, 2); + mGrid->addWidget(widgets.mInput, next, 0, 1, 2); } } From 755fef62f73cc9d2aa7570b44fa0f40bf193f812 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 00:42:58 +0100 Subject: [PATCH 005/231] Mark State copy constructor and assignment operators as delete --- apps/opencs/model/prefs/state.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 86d2c19da6..6119fbda1a 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -51,11 +51,6 @@ namespace CSMPrefs Iterator mCurrentCategory; QMutex mMutex; - // not implemented - State(const State&); - State& operator=(const State&); - - private: void declare(); void declareCategory(const std::string& key); @@ -82,8 +77,12 @@ namespace CSMPrefs public: State(const Files::ConfigurationManager& configurationManager); + State(const State&) = delete; + ~State(); + State& operator=(const State&) = delete; + void save(); Iterator begin(); From fb0b95a2dd3891b98a7d880d378690fb601a4d1b Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 11 Nov 2023 00:53:27 +0100 Subject: [PATCH 006/231] Define editor settings as typed struct members --- apps/opencs/model/prefs/state.cpp | 6 + apps/opencs/model/prefs/state.hpp | 8 + apps/opencs/model/prefs/values.hpp | 532 +++++++++++++++++++++++++++ components/settings/settingvalue.hpp | 9 + 4 files changed, 555 insertions(+) create mode 100644 apps/opencs/model/prefs/values.hpp diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 21965810e5..d74044f6d5 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -25,6 +25,7 @@ #include "modifiersetting.hpp" #include "shortcutsetting.hpp" #include "stringsetting.hpp" +#include "values.hpp" CSMPrefs::State* CSMPrefs::State::sThis = nullptr; @@ -628,13 +629,18 @@ CSMPrefs::State::State(const Files::ConfigurationManager& configurationManager) , mDefaultConfigFile("defaults-cs.bin") , mConfigurationManager(configurationManager) , mCurrentCategory(mCategories.end()) + , mIndex(std::make_unique()) { if (sThis) throw std::logic_error("An instance of CSMPRefs::State already exists"); sThis = this; + Values values(*mIndex); + declare(); + + mValues = std::make_unique(std::move(values)); } CSMPrefs::State::~State() diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 6119fbda1a..e398f06e4a 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -17,6 +17,11 @@ class QColor; +namespace Settings +{ + class Index; +} + namespace CSMPrefs { class IntSetting; @@ -27,6 +32,7 @@ namespace CSMPrefs class ModifierSetting; class Setting; class StringSetting; + struct Values; /// \brief User settings state /// @@ -50,6 +56,8 @@ namespace CSMPrefs Collection mCategories; Iterator mCurrentCategory; QMutex mMutex; + std::unique_ptr mIndex; + std::unique_ptr mValues; void declare(); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp new file mode 100644 index 0000000000..247c025e80 --- /dev/null +++ b/apps/opencs/model/prefs/values.hpp @@ -0,0 +1,532 @@ +#ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H +#define OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace CSMPrefs +{ + struct EnumValueView + { + std::string_view mValue; + std::string_view mTooltip; + }; + + class EnumSanitizer final : public Settings::Sanitizer + { + public: + explicit EnumSanitizer(std::span values) + : mValues(values) + { + } + + std::string apply(const std::string& value) const override + { + const auto hasValue = [&](const EnumValueView& v) { return v.mValue == value; }; + if (std::find_if(mValues.begin(), mValues.end(), hasValue) == mValues.end()) + { + std::ostringstream message; + message << "Invalid enum value: " << value; + throw std::runtime_error(message.str()); + } + return value; + } + + private: + std::span mValues; + }; + + inline std::unique_ptr> makeEnumSanitizerString( + std::span values) + { + return std::make_unique(values); + } + + struct WindowsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Windows"; + + static constexpr std::array sMainwindowScrollbarValues{ + EnumValueView{ + "Scrollbar Only", "Simple addition of scrollbars, the view window does not grow automatically." }, + EnumValueView{ "Grow Only", "The view window grows as subviews are added. No scrollbars." }, + EnumValueView{ + "Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further." }, + }; + + Settings::SettingValue mDefaultWidth{ mIndex, sName, "default-width", 800 }; + Settings::SettingValue mDefaultHeight{ mIndex, sName, "default-height", 600 }; + Settings::SettingValue mShowStatusbar{ mIndex, sName, "show-statusbar", true }; + Settings::SettingValue mReuse{ mIndex, sName, "reuse", true }; + Settings::SettingValue mMaxSubviews{ mIndex, sName, "max-subviews", 256 }; + Settings::SettingValue mHideSubview{ mIndex, sName, "hide-subview", false }; + Settings::SettingValue mMinimumWidth{ mIndex, sName, "minimum-width", 325 }; + Settings::SettingValue mMainwindowScrollbar{ mIndex, sName, "mainwindow-scrollbar", + std::string(sMainwindowScrollbarValues[0].mValue), makeEnumSanitizerString(sMainwindowScrollbarValues) }; + Settings::SettingValue mGrowLimit{ mIndex, sName, "grow-limit", false }; + }; + + struct RecordsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Records"; + + static constexpr std::array sRecordValues{ + EnumValueView{ "Icon and Text", "" }, + EnumValueView{ "Icon Only", "" }, + EnumValueView{ "Text Only", "" }, + }; + + Settings::SettingValue mStatusFormat{ mIndex, sName, "status-format", + std::string(sRecordValues[0].mValue), makeEnumSanitizerString(sRecordValues) }; + Settings::SettingValue mTypeFormat{ mIndex, sName, "type-format", + std::string(sRecordValues[0].mValue), makeEnumSanitizerString(sRecordValues) }; + }; + + struct IdTablesCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "ID Tables"; + + static constexpr std::array sDoubleClickValues{ + EnumValueView{ "Edit in Place", "Edit the clicked cell" }, + EnumValueView{ "Edit Record", "Open a dialogue subview for the clicked record" }, + EnumValueView{ "View", "Open a scene subview for the clicked record (not available everywhere)" }, + EnumValueView{ "Revert", "" }, + EnumValueView{ "Delete", "" }, + EnumValueView{ "Edit Record and Close", "" }, + EnumValueView{ + "View and Close", "Open a scene subview for the clicked record and close the table subview" }, + }; + + static constexpr std::array sJumpAndSelectValues{ + EnumValueView{ "Jump and Select", "Scroll new record into view and make it the selection" }, + EnumValueView{ "Jump Only", "Scroll new record into view" }, + EnumValueView{ "No Jump", "No special action" }, + }; + + Settings::SettingValue mDouble{ mIndex, sName, "double", std::string(sDoubleClickValues[0].mValue), + makeEnumSanitizerString(sDoubleClickValues) }; + Settings::SettingValue mDoubleS{ mIndex, sName, "double-s", + std::string(sDoubleClickValues[1].mValue), makeEnumSanitizerString(sDoubleClickValues) }; + Settings::SettingValue mDoubleC{ mIndex, sName, "double-c", + std::string(sDoubleClickValues[2].mValue), makeEnumSanitizerString(sDoubleClickValues) }; + Settings::SettingValue mDoubleSc{ mIndex, sName, "double-sc", + std::string(sDoubleClickValues[5].mValue), makeEnumSanitizerString(sDoubleClickValues) }; + Settings::SettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", + std::string(sJumpAndSelectValues[0].mValue), makeEnumSanitizerString(sJumpAndSelectValues) }; + Settings::SettingValue mExtendedConfig{ mIndex, sName, "extended-config", false }; + Settings::SettingValue mSubviewNewWindow{ mIndex, sName, "subview-new-window", false }; + }; + + struct IdDialoguesCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "ID Dialogues"; + + Settings::SettingValue mToolbar{ mIndex, sName, "toolbar", true }; + }; + + struct ReportsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Reports"; + + static constexpr std::array sReportValues{ + EnumValueView{ "None", "" }, + EnumValueView{ "Edit", "Open a table or dialogue suitable for addressing the listed report" }, + EnumValueView{ "Remove", "Remove the report from the report table" }, + EnumValueView{ "Edit And Remove", + "Open a table or dialogue suitable for addressing the listed report, then remove the report from the " + "report table" }, + }; + + Settings::SettingValue mDouble{ mIndex, sName, "double", std::string(sReportValues[1].mValue), + makeEnumSanitizerString(sReportValues) }; + Settings::SettingValue mDoubleS{ mIndex, sName, "double-s", std::string(sReportValues[2].mValue), + makeEnumSanitizerString(sReportValues) }; + Settings::SettingValue mDoubleC{ mIndex, sName, "double-c", std::string(sReportValues[3].mValue), + makeEnumSanitizerString(sReportValues) }; + Settings::SettingValue mDoubleSc{ mIndex, sName, "double-sc", std::string(sReportValues[0].mValue), + makeEnumSanitizerString(sReportValues) }; + Settings::SettingValue mIgnoreBaseRecords{ mIndex, sName, "ignore-base-records", false }; + }; + + struct SearchAndReplaceCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Search & Replace"; + + Settings::SettingValue mCharBefore{ mIndex, sName, "char-before", 10 }; + Settings::SettingValue mCharAfter{ mIndex, sName, "char-after", 10 }; + Settings::SettingValue mAutoDelete{ mIndex, sName, "auto-delete", true }; + }; + + struct ScriptsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Scripts"; + + static constexpr std::array sWarningValues{ + EnumValueView{ "Ignore", "Do not report warning" }, + EnumValueView{ "Normal", "Report warnings as warning" }, + EnumValueView{ "Strict", "Promote warning to an error" }, + }; + + Settings::SettingValue mShowLinenum{ mIndex, sName, "show-linenum", true }; + Settings::SettingValue mWrapLines{ mIndex, sName, "wrap-lines", false }; + Settings::SettingValue mMonoFont{ mIndex, sName, "mono-font", true }; + Settings::SettingValue mTabWidth{ mIndex, sName, "tab-width", 4 }; + Settings::SettingValue mWarnings{ mIndex, sName, "warnings", std::string(sWarningValues[1].mValue), + makeEnumSanitizerString(sWarningValues) }; + Settings::SettingValue mToolbar{ mIndex, sName, "toolbar", true }; + Settings::SettingValue mCompileDelay{ mIndex, sName, "compile-delay", 100 }; + Settings::SettingValue mErrorHeight{ mIndex, sName, "error-height", 100 }; + Settings::SettingValue mHighlightOccurrences{ mIndex, sName, "highlight-occurrences", true }; + Settings::SettingValue mColourHighlight{ mIndex, sName, "colour-highlight", "lightcyan" }; + Settings::SettingValue mColourInt{ mIndex, sName, "colour-int", "darkmagenta" }; + Settings::SettingValue mColourFloat{ mIndex, sName, "colour-float", "magenta" }; + Settings::SettingValue mColourName{ mIndex, sName, "colour-name", "grey" }; + Settings::SettingValue mColourKeyword{ mIndex, sName, "colour-keyword", "red" }; + Settings::SettingValue mColourSpecial{ mIndex, sName, "colour-special", "darkorange" }; + Settings::SettingValue mColourComment{ mIndex, sName, "colour-comment", "green" }; + Settings::SettingValue mColourId{ mIndex, sName, "colour-id", "blue" }; + }; + + struct GeneralInputCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "General Input"; + + Settings::SettingValue mCycle{ mIndex, sName, "cycle", false }; + }; + + struct SceneInputCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "3D Scene Input"; + + Settings::SettingValue mNaviWheelFactor{ mIndex, sName, "navi-wheel-factor", 8 }; + Settings::SettingValue mSNaviSensitivity{ mIndex, sName, "s-navi-sensitivity", 50 }; + Settings::SettingValue mPNaviFreeSensitivity{ mIndex, sName, "p-navi-free-sensitivity", 1 / 650.0 }; + Settings::SettingValue mPNaviFreeInvert{ mIndex, sName, "p-navi-free-invert", false }; + Settings::SettingValue mNaviFreeLinSpeed{ mIndex, sName, "navi-free-lin-speed", 1000 }; + Settings::SettingValue mNaviFreeRotSpeed{ mIndex, sName, "navi-free-rot-speed", 3.14 / 2 }; + Settings::SettingValue mNaviFreeSpeedMult{ mIndex, sName, "navi-free-speed-mult", 8 }; + Settings::SettingValue mPNaviOrbitSensitivity{ mIndex, sName, "p-navi-orbit-sensitivity", 1 / 650.0 }; + Settings::SettingValue mPNaviOrbitInvert{ mIndex, sName, "p-navi-orbit-invert", false }; + Settings::SettingValue mNaviOrbitRotSpeed{ mIndex, sName, "navi-orbit-rot-speed", 3.14 / 4 }; + Settings::SettingValue mNaviOrbitSpeedMult{ mIndex, sName, "navi-orbit-speed-mult", 4 }; + Settings::SettingValue mNaviOrbitConstRoll{ mIndex, sName, "navi-orbit-const-roll", true }; + Settings::SettingValue mContextSelect{ mIndex, sName, "context-select", false }; + Settings::SettingValue mDragFactor{ mIndex, sName, "drag-factor", 1 }; + Settings::SettingValue mDragWheelFactor{ mIndex, sName, "drag-wheel-factor", 1 }; + Settings::SettingValue mDragShiftFactor{ mIndex, sName, "drag-shift-factor", 4 }; + Settings::SettingValue mRotateFactor{ mIndex, sName, "rotate-factor", 0.007 }; + }; + + struct RenderingCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Rendering"; + + Settings::SettingValue mFramerateLimit{ mIndex, sName, "framerate-limit", 60 }; + Settings::SettingValue mCameraFov{ mIndex, sName, "camera-fov", 90 }; + Settings::SettingValue mCameraOrtho{ mIndex, sName, "camera-ortho", false }; + Settings::SettingValue mCameraOrthoSize{ mIndex, sName, "camera-ortho-size", 100 }; + Settings::SettingValue mObjectMarkerAlpha{ mIndex, sName, "object-marker-alpha", 0.5 }; + Settings::SettingValue mSceneUseGradient{ mIndex, sName, "scene-use-gradient", true }; + Settings::SettingValue mSceneDayBackgroundColour{ mIndex, sName, "scene-day-background-colour", + "#6e7880" }; + Settings::SettingValue mSceneDayGradientColour{ mIndex, sName, "scene-day-gradient-colour", + "#2f3333" }; + Settings::SettingValue mSceneBrightBackgroundColour{ mIndex, sName, + "scene-bright-background-colour", "#4f575c" }; + Settings::SettingValue mSceneBrightGradientColour{ mIndex, sName, "scene-bright-gradient-colour", + "#2f3333" }; + Settings::SettingValue mSceneNightBackgroundColour{ mIndex, sName, "scene-night-background-colour", + "#404d4f" }; + Settings::SettingValue mSceneNightGradientColour{ mIndex, sName, "scene-night-gradient-colour", + "#2f3333" }; + Settings::SettingValue mSceneDayNightSwitchNodes{ mIndex, sName, "scene-day-night-switch-nodes", true }; + }; + + struct TooltipsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Tooltips"; + + Settings::SettingValue mScene{ mIndex, sName, "scene", true }; + Settings::SettingValue mSceneHideBasic{ mIndex, sName, "scene-hide-basic", false }; + Settings::SettingValue mSceneDelay{ mIndex, sName, "scene-delay", 500 }; + }; + + struct SceneEditingCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "3D Scene Editing"; + + static constexpr std::array sInsertOutsideCellValues{ + EnumValueView{ "Create cell and insert", "" }, + EnumValueView{ "Discard", "" }, + EnumValueView{ "Insert anyway", "" }, + }; + + static constexpr std::array sInsertOutsideVisibleCellValues{ + EnumValueView{ "Show cell and insert", "" }, + EnumValueView{ "Discard", "" }, + EnumValueView{ "Insert anyway", "" }, + }; + + static constexpr std::array sLandEditOutsideCellValues{ + EnumValueView{ "Create cell and land, then edit", "" }, + EnumValueView{ "Discard", "" }, + }; + + static constexpr std::array sLandEditOutsideVisibleCellValues{ + EnumValueView{ "Show cell and edit", "" }, + EnumValueView{ "Discard", "" }, + }; + + static constexpr std::array sPrimarySelectAction{ + EnumValueView{ "Select only", "" }, + EnumValueView{ "Add to selection", "" }, + EnumValueView{ "Remove from selection", "" }, + EnumValueView{ "Invert selection", "" }, + }; + + static constexpr std::array sSecondarySelectAction{ + EnumValueView{ "Select only", "" }, + EnumValueView{ "Add to selection", "" }, + EnumValueView{ "Remove from selection", "" }, + EnumValueView{ "Invert selection", "" }, + }; + + Settings::SettingValue mGridsnapMovement{ mIndex, sName, "gridsnap-movement", 16 }; + Settings::SettingValue mGridsnapRotation{ mIndex, sName, "gridsnap-rotation", 15 }; + Settings::SettingValue mGridsnapScale{ mIndex, sName, "gridsnap-scale", 0.25 }; + Settings::SettingValue mDistance{ mIndex, sName, "distance", 50 }; + Settings::SettingValue mOutsideDrop{ mIndex, sName, "outside-drop", + std::string(sInsertOutsideCellValues[0].mValue), makeEnumSanitizerString(sInsertOutsideCellValues) }; + Settings::SettingValue mOutsideVisibleDrop{ mIndex, sName, "outside-visible-drop", + std::string(sInsertOutsideVisibleCellValues[0].mValue), + makeEnumSanitizerString(sInsertOutsideVisibleCellValues) }; + Settings::SettingValue mOutsideLandedit{ mIndex, sName, "outside-landedit", + std::string(sLandEditOutsideCellValues[0].mValue), makeEnumSanitizerString(sLandEditOutsideCellValues) }; + Settings::SettingValue mOutsideVisibleLandedit{ mIndex, sName, "outside-visible-landedit", + std::string(sLandEditOutsideVisibleCellValues[0].mValue), + makeEnumSanitizerString(sLandEditOutsideVisibleCellValues) }; + Settings::SettingValue mTexturebrushMaximumsize{ mIndex, sName, "texturebrush-maximumsize", 50 }; + Settings::SettingValue mShapebrushMaximumsize{ mIndex, sName, "shapebrush-maximumsize", 100 }; + Settings::SettingValue mLandeditPostSmoothpainting{ mIndex, sName, "landedit-post-smoothpainting", + false }; + Settings::SettingValue mLandeditPostSmoothstrength{ mIndex, sName, "landedit-post-smoothstrength", + 0.25 }; + Settings::SettingValue mOpenListView{ mIndex, sName, "open-list-view", false }; + Settings::SettingValue mPrimarySelectAction{ mIndex, sName, "primary-select-action", + std::string(sPrimarySelectAction[0].mValue), makeEnumSanitizerString(sPrimarySelectAction) }; + Settings::SettingValue mSecondarySelectAction{ mIndex, sName, "secondary-select-action", + std::string(sPrimarySelectAction[1].mValue), makeEnumSanitizerString(sPrimarySelectAction) }; + }; + + struct KeyBindingsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Key Bindings"; + + Settings::SettingValue mDocumentFileNewgame{ mIndex, sName, "document-file-newgame", "Ctrl+N" }; + Settings::SettingValue mDocumentFileNewaddon{ mIndex, sName, "document-file-newaddon", "" }; + Settings::SettingValue mDocumentFileOpen{ mIndex, sName, "document-file-open", "Ctrl+O" }; + Settings::SettingValue mDocumentFileSave{ mIndex, sName, "document-file-save", "Ctrl+S" }; + Settings::SettingValue mDocumentHelpHelp{ mIndex, sName, "document-help-help", "F1" }; + Settings::SettingValue mDocumentHelpTutorial{ mIndex, sName, "document-help-tutorial", "" }; + Settings::SettingValue mDocumentFileVerify{ mIndex, sName, "document-file-verify", "" }; + Settings::SettingValue mDocumentFileMerge{ mIndex, sName, "document-file-merge", "" }; + Settings::SettingValue mDocumentFileErrorlog{ mIndex, sName, "document-file-errorlog", "" }; + Settings::SettingValue mDocumentFileMetadata{ mIndex, sName, "document-file-metadata", "" }; + Settings::SettingValue mDocumentFileClose{ mIndex, sName, "document-file-close", "Ctrl+W" }; + Settings::SettingValue mDocumentFileExit{ mIndex, sName, "document-file-exit", "Ctrl+Q" }; + Settings::SettingValue mDocumentEditUndo{ mIndex, sName, "document-edit-undo", "Ctrl+Z" }; + Settings::SettingValue mDocumentEditRedo{ mIndex, sName, "document-edit-redo", "Ctrl+Shift+Z" }; + Settings::SettingValue mDocumentEditPreferences{ mIndex, sName, "document-edit-preferences", "" }; + Settings::SettingValue mDocumentEditSearch{ mIndex, sName, "document-edit-search", "Ctrl+F" }; + Settings::SettingValue mDocumentViewNewview{ mIndex, sName, "document-view-newview", "" }; + Settings::SettingValue mDocumentViewStatusbar{ mIndex, sName, "document-view-statusbar", "" }; + Settings::SettingValue mDocumentViewFilters{ mIndex, sName, "document-view-filters", "" }; + Settings::SettingValue mDocumentWorldRegions{ mIndex, sName, "document-world-regions", "" }; + Settings::SettingValue mDocumentWorldCells{ mIndex, sName, "document-world-cells", "" }; + Settings::SettingValue mDocumentWorldReferencables{ mIndex, sName, "document-world-referencables", + "" }; + Settings::SettingValue mDocumentWorldReferences{ mIndex, sName, "document-world-references", "" }; + Settings::SettingValue mDocumentWorldLands{ mIndex, sName, "document-world-lands", "" }; + Settings::SettingValue mDocumentWorldLandtextures{ mIndex, sName, "document-world-landtextures", + "" }; + Settings::SettingValue mDocumentWorldPathgrid{ mIndex, sName, "document-world-pathgrid", "" }; + Settings::SettingValue mDocumentWorldRegionmap{ mIndex, sName, "document-world-regionmap", "" }; + Settings::SettingValue mDocumentMechanicsGlobals{ mIndex, sName, "document-mechanics-globals", + "" }; + Settings::SettingValue mDocumentMechanicsGamesettings{ mIndex, sName, + "document-mechanics-gamesettings", "" }; + Settings::SettingValue mDocumentMechanicsScripts{ mIndex, sName, "document-mechanics-scripts", + "" }; + Settings::SettingValue mDocumentMechanicsSpells{ mIndex, sName, "document-mechanics-spells", "" }; + Settings::SettingValue mDocumentMechanicsEnchantments{ mIndex, sName, + "document-mechanics-enchantments", "" }; + Settings::SettingValue mDocumentMechanicsMagiceffects{ mIndex, sName, + "document-mechanics-magiceffects", "" }; + Settings::SettingValue mDocumentMechanicsStartscripts{ mIndex, sName, + "document-mechanics-startscripts", "" }; + Settings::SettingValue mDocumentCharacterSkills{ mIndex, sName, "document-character-skills", "" }; + Settings::SettingValue mDocumentCharacterClasses{ mIndex, sName, "document-character-classes", + "" }; + Settings::SettingValue mDocumentCharacterFactions{ mIndex, sName, "document-character-factions", + "" }; + Settings::SettingValue mDocumentCharacterRaces{ mIndex, sName, "document-character-races", "" }; + Settings::SettingValue mDocumentCharacterBirthsigns{ mIndex, sName, + "document-character-birthsigns", "" }; + Settings::SettingValue mDocumentCharacterTopics{ mIndex, sName, "document-character-topics", "" }; + Settings::SettingValue mDocumentCharacterJournals{ mIndex, sName, "document-character-journals", + "" }; + Settings::SettingValue mDocumentCharacterTopicinfos{ mIndex, sName, + "document-character-topicinfos", "" }; + Settings::SettingValue mDocumentCharacterJournalinfos{ mIndex, sName, + "document-character-journalinfos", "" }; + Settings::SettingValue mDocumentCharacterBodyparts{ mIndex, sName, "document-character-bodyparts", + "" }; + Settings::SettingValue mDocumentAssetsReload{ mIndex, sName, "document-assets-reload", "F5" }; + Settings::SettingValue mDocumentAssetsSounds{ mIndex, sName, "document-assets-sounds", "" }; + Settings::SettingValue mDocumentAssetsSoundgens{ mIndex, sName, "document-assets-soundgens", "" }; + Settings::SettingValue mDocumentAssetsMeshes{ mIndex, sName, "document-assets-meshes", "" }; + Settings::SettingValue mDocumentAssetsIcons{ mIndex, sName, "document-assets-icons", "" }; + Settings::SettingValue mDocumentAssetsMusic{ mIndex, sName, "document-assets-music", "" }; + Settings::SettingValue mDocumentAssetsSoundres{ mIndex, sName, "document-assets-soundres", "" }; + Settings::SettingValue mDocumentAssetsTextures{ mIndex, sName, "document-assets-textures", "" }; + Settings::SettingValue mDocumentAssetsVideos{ mIndex, sName, "document-assets-videos", "" }; + Settings::SettingValue mDocumentDebugRun{ mIndex, sName, "document-debug-run", "" }; + Settings::SettingValue mDocumentDebugShutdown{ mIndex, sName, "document-debug-shutdown", "" }; + Settings::SettingValue mDocumentDebugProfiles{ mIndex, sName, "document-debug-profiles", "" }; + Settings::SettingValue mDocumentDebugRunlog{ mIndex, sName, "document-debug-runlog", "" }; + Settings::SettingValue mTableEdit{ mIndex, sName, "table-edit", "" }; + Settings::SettingValue mTableAdd{ mIndex, sName, "table-add", "Shift+A" }; + Settings::SettingValue mTableClone{ mIndex, sName, "table-clone", "Shift+D" }; + Settings::SettingValue mTouchRecord{ mIndex, sName, "touch-record", "" }; + Settings::SettingValue mTableRevert{ mIndex, sName, "table-revert", "" }; + Settings::SettingValue mTableRemove{ mIndex, sName, "table-remove", "Delete" }; + Settings::SettingValue mTableMoveup{ mIndex, sName, "table-moveup", "" }; + Settings::SettingValue mTableMovedown{ mIndex, sName, "table-movedown", "" }; + Settings::SettingValue mTableView{ mIndex, sName, "table-view", "Shift+C" }; + Settings::SettingValue mTablePreview{ mIndex, sName, "table-preview", "Shift+V" }; + Settings::SettingValue mTableExtendeddelete{ mIndex, sName, "table-extendeddelete", "" }; + Settings::SettingValue mTableExtendedrevert{ mIndex, sName, "table-extendedrevert", "" }; + Settings::SettingValue mReporttableShow{ mIndex, sName, "reporttable-show", "" }; + Settings::SettingValue mReporttableRemove{ mIndex, sName, "reporttable-remove", "Delete" }; + Settings::SettingValue mReporttableReplace{ mIndex, sName, "reporttable-replace", "" }; + Settings::SettingValue mReporttableRefresh{ mIndex, sName, "reporttable-refresh", "" }; + Settings::SettingValue mSceneNaviPrimary{ mIndex, sName, "scene-navi-primary", "LMB" }; + Settings::SettingValue mSceneNaviSecondary{ mIndex, sName, "scene-navi-secondary", "Ctrl+LMB" }; + Settings::SettingValue mSceneOpenPrimary{ mIndex, sName, "scene-open-primary", "Shift+LMB" }; + Settings::SettingValue mSceneEditPrimary{ mIndex, sName, "scene-edit-primary", "RMB" }; + Settings::SettingValue mSceneEditSecondary{ mIndex, sName, "scene-edit-secondary", "Ctrl+RMB" }; + Settings::SettingValue mSceneSelectPrimary{ mIndex, sName, "scene-select-primary", "MMB" }; + Settings::SettingValue mSceneSelectSecondary{ mIndex, sName, "scene-select-secondary", + "Ctrl+MMB" }; + Settings::SettingValue mSceneSelectTertiary{ mIndex, sName, "scene-select-tertiary", "Shift+MMB" }; + Settings::SettingValue mSceneSpeedModifier{ mIndex, sName, "scene-speed-modifier", "Shift" }; + Settings::SettingValue mSceneDelete{ mIndex, sName, "scene-delete", "Delete" }; + Settings::SettingValue mSceneInstanceDropTerrain{ mIndex, sName, "scene-instance-drop-terrain", + "G" }; + Settings::SettingValue mSceneInstanceDropCollision{ mIndex, sName, "scene-instance-drop-collision", + "H" }; + Settings::SettingValue mSceneInstanceDropTerrainSeparately{ mIndex, sName, + "scene-instance-drop-terrain-separately", "" }; + Settings::SettingValue mSceneInstanceDropCollisionSeparately{ mIndex, sName, + "scene-instance-drop-collision-separately", "" }; + Settings::SettingValue mSceneLoadCamCell{ mIndex, sName, "scene-load-cam-cell", "Keypad+5" }; + Settings::SettingValue mSceneLoadCamEastcell{ mIndex, sName, "scene-load-cam-eastcell", + "Keypad+6" }; + Settings::SettingValue mSceneLoadCamNorthcell{ mIndex, sName, "scene-load-cam-northcell", + "Keypad+8" }; + Settings::SettingValue mSceneLoadCamWestcell{ mIndex, sName, "scene-load-cam-westcell", + "Keypad+4" }; + Settings::SettingValue mSceneLoadCamSouthcell{ mIndex, sName, "scene-load-cam-southcell", + "Keypad+2" }; + Settings::SettingValue mSceneEditAbort{ mIndex, sName, "scene-edit-abort", "Escape" }; + Settings::SettingValue mSceneFocusToolbar{ mIndex, sName, "scene-focus-toolbar", "T" }; + Settings::SettingValue mSceneRenderStats{ mIndex, sName, "scene-render-stats", "F3" }; + Settings::SettingValue mFreeForward{ mIndex, sName, "free-forward", "W" }; + Settings::SettingValue mFreeBackward{ mIndex, sName, "free-backward", "S" }; + Settings::SettingValue mFreeLeft{ mIndex, sName, "free-left", "A" }; + Settings::SettingValue mFreeRight{ mIndex, sName, "free-right", "D" }; + Settings::SettingValue mFreeRollLeft{ mIndex, sName, "free-roll-left", "Q" }; + Settings::SettingValue mFreeRollRight{ mIndex, sName, "free-roll-right", "E" }; + Settings::SettingValue mFreeSpeedMode{ mIndex, sName, "free-speed-mode", "F" }; + Settings::SettingValue mOrbitUp{ mIndex, sName, "orbit-up", "W" }; + Settings::SettingValue mOrbitDown{ mIndex, sName, "orbit-down", "S" }; + Settings::SettingValue mOrbitLeft{ mIndex, sName, "orbit-left", "A" }; + Settings::SettingValue mOrbitRight{ mIndex, sName, "orbit-right", "D" }; + Settings::SettingValue mOrbitRollLeft{ mIndex, sName, "orbit-roll-left", "Q" }; + Settings::SettingValue mOrbitRollRight{ mIndex, sName, "orbit-roll-right", "E" }; + Settings::SettingValue mOrbitSpeedMode{ mIndex, sName, "orbit-speed-mode", "F" }; + Settings::SettingValue mOrbitCenterSelection{ mIndex, sName, "orbit-center-selection", "C" }; + Settings::SettingValue mScriptEditorComment{ mIndex, sName, "script-editor-comment", "" }; + Settings::SettingValue mScriptEditorUncomment{ mIndex, sName, "script-editor-uncomment", "" }; + }; + + struct ModelsCategory : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + static constexpr std::string_view sName = "Models"; + + Settings::SettingValue mBaseanim{ mIndex, sName, "baseanim", "meshes/base_anim.nif" }; + Settings::SettingValue mBaseanimkna{ mIndex, sName, "baseanimkna", "meshes/base_animkna.nif" }; + Settings::SettingValue mBaseanimfemale{ mIndex, sName, "baseanimfemale", + "meshes/base_anim_female.nif" }; + Settings::SettingValue mWolfskin{ mIndex, sName, "wolfskin", "meshes/wolf/skin.nif" }; + }; + + struct Values : Settings::WithIndex + { + using Settings::WithIndex::WithIndex; + + WindowsCategory mWindows{ mIndex }; + RecordsCategory mRecords{ mIndex }; + IdTablesCategory mIdTables{ mIndex }; + IdDialoguesCategory mIdDialogues{ mIndex }; + ReportsCategory mReports{ mIndex }; + SearchAndReplaceCategory mSearchAndReplace{ mIndex }; + ScriptsCategory mScripts{ mIndex }; + GeneralInputCategory mGeneralInput{ mIndex }; + SceneInputCategory mSceneInput{ mIndex }; + RenderingCategory mRendering{ mIndex }; + TooltipsCategory mTooltips{ mIndex }; + SceneEditingCategory mSceneEditing{ mIndex }; + KeyBindingsCategory mKeyBindings{ mIndex }; + ModelsCategory mModels{ mIndex }; + }; +} + +#endif diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 2f239a4ebd..392e5b646f 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -335,6 +335,15 @@ namespace Settings { } + explicit SettingValue(Index& index, std::string_view category, std::string_view name, T&& defaultValue, + std::unique_ptr>&& sanitizer = nullptr) + : BaseSettingValue(getSettingValueType(), category, name, index) + , mSanitizer(std::move(sanitizer)) + , mDefaultValue(defaultValue) + , mValue(defaultValue) + { + } + SettingValue(SettingValue&& other) : BaseSettingValue(std::move(other)) , mSanitizer(std::move(other.mSanitizer)) From 9ebbdc3a22fafd5850b41d9a28376d0dbc5eccb5 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Mon, 20 Nov 2023 15:59:11 +0800 Subject: [PATCH 007/231] expell->expel --- apps/openmw/mwlua/types/npc.cpp | 2 +- files/lua_api/openmw/types.lua | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 25997b6468..67080304c2 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -265,7 +265,7 @@ namespace MWLua npcStats.setFactionReputation(factionId, existingReputation + value); }; - npc["expell"] = [](Object& actor, std::string_view faction) { + npc["expel"] = [](Object& actor, std::string_view faction) { if (dynamic_cast(&actor) && !dynamic_cast(&actor)) throw std::runtime_error("Local scripts can modify only self"); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 17c2991d87..2a352b58f1 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -837,14 +837,14 @@ -- NPC.modifyFactionReputation(player, "mages guild", 5); --- --- Expell NPC from given faction. +-- Expel NPC from given faction. -- Throws an exception if there is no such faction. -- Note: expelled NPC still keeps his rank and reputation in faction, he just get an additonal flag for given faction. --- @function [parent=#NPC] expell +-- @function [parent=#NPC] expel -- @param openmw.core#GameObject actor NPC object -- @param #string faction Faction ID -- @usage local NPC = require('openmw.types').NPC; --- NPC.expell(player, "mages guild"); +-- NPC.expel(player, "mages guild"); --- -- Clear expelling of NPC from given faction. From 3be0ee824afd300ebddbe1496e459b542a2de824 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 26 Nov 2023 21:57:41 +0300 Subject: [PATCH 008/231] niftest updates Properly read archives within the supplied data directories Don't print quote marks redundantly Reduce code duplication Improve logging --- apps/niftest/niftest.cpp | 141 +++++++++++++++++++++++---------------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 004e45765c..77752d25bd 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -45,9 +45,6 @@ std::unique_ptr makeBsaArchive(const std::filesystem::path& path) { switch (Bsa::BSAFile::detectVersion(path)) { - case Bsa::BSAVER_UNKNOWN: - std::cerr << '"' << path << "\" is unknown BSA archive" << std::endl; - return nullptr; case Bsa::BSAVER_COMPRESSED: return std::make_unique::type>(path); case Bsa::BSAVER_BA2_GNRL: @@ -56,11 +53,11 @@ std::unique_ptr makeBsaArchive(const std::filesystem::path& path) return std::make_unique::type>(path); case Bsa::BSAVER_UNCOMPRESSED: return std::make_unique::type>(path); + case Bsa::BSAVER_UNKNOWN: + default: + std::cerr << "'" << Files::pathToUnicodeString(path) << "' is not a recognized BSA archive" << std::endl; + return nullptr; } - - std::cerr << '"' << path << "\" is unsupported BSA archive" << std::endl; - - return nullptr; } std::unique_ptr makeArchive(const std::filesystem::path& path) @@ -72,58 +69,85 @@ std::unique_ptr makeArchive(const std::filesystem::path& path) return nullptr; } +void readNIF( + const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) +{ + if (!quiet) + { + std::cout << "Reading NIF file '" << Files::pathToUnicodeString(path) << "'"; + if (!source.empty()) + std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; + std::cout << std::endl; + } + std::filesystem::path fullPath = !source.empty() ? source / path : path; + try + { + Nif::NIFFile file(fullPath); + Nif::Reader reader(file); + if (vfs != nullptr) + reader.parse(vfs->get(Files::pathToUnicodeString(path))); + else + reader.parse(Files::openConstrainedFileStream(fullPath)); + } + catch (std::exception& e) + { + std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; + } +} + /// Check all the nif files in a given VFS::Archive /// \note Can not read a bsa file inside of a bsa file. -void readVFS(std::unique_ptr&& anArchive, const std::filesystem::path& archivePath = {}) +void readVFS(std::unique_ptr&& archive, const std::filesystem::path& archivePath, bool quiet) { - if (anArchive == nullptr) + if (archive == nullptr) return; - VFS::Manager myManager; - myManager.addArchive(std::move(anArchive)); - myManager.buildIndex(); + if (!quiet) + std::cout << "Reading data source '" << Files::pathToUnicodeString(archivePath) << "'" << std::endl; - for (const auto& name : myManager.getRecursiveDirectoryIterator("")) + VFS::Manager vfs; + vfs.addArchive(std::move(archive)); + vfs.buildIndex(); + + for (const auto& name : vfs.getRecursiveDirectoryIterator("")) { - try + if (isNIF(name)) { - if (isNIF(name)) - { - // std::cout << "Decoding: " << name << std::endl; - Nif::NIFFile file(archivePath / name); - Nif::Reader reader(file); - reader.parse(myManager.get(name)); - } - else if (isBSA(name)) - { - if (!archivePath.empty() && !isBSA(archivePath)) - { - // std::cout << "Reading BSA File: " << name << std::endl; - readVFS(makeBsaArchive(archivePath / name), archivePath / name); - // std::cout << "Done with BSA File: " << name << std::endl; - } - } + readNIF(archivePath, name, &vfs, quiet); } - catch (std::exception& e) + } + + if (!archivePath.empty() && !isBSA(archivePath)) + { + Files::PathContainer dataDirs = { archivePath }; + const Files::Collections fileCollections = Files::Collections(dataDirs); + const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa"); + const Files::MultiDirCollection& ba2Col = fileCollections.getCollection(".ba2"); + for (auto& file : bsaCol) { - std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + readVFS(makeBsaArchive(file.second), file.second, quiet); + } + for (auto& file : ba2Col) + { + readVFS(makeBsaArchive(file.second), file.second, quiet); } } } -bool parseOptions(int argc, char** argv, std::vector& files, bool& writeDebugLog, - std::vector& archives) +bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives, + bool& writeDebugLog, bool& quiet) { bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF and BSA files Usages: - niftool - Scan the file or directories for nif errors. + niftest + Scan the file or directories for NIF errors. Allowed options)"); auto addOption = desc.add_options(); addOption("help,h", "print help message."); addOption("write-debug-log,v", "write debug log for unsupported nif files"); + addOption("quiet,q", "do not log read archives/files"); addOption("archives", bpo::value(), "path to archive files to provide files"); addOption("input-file", bpo::value(), "input file"); @@ -143,17 +167,18 @@ Allowed options)"); return false; } writeDebugLog = variables.count("write-debug-log") > 0; + quiet = variables.count("quiet") > 0; if (variables.count("input-file")) { - files = variables["input-file"].as(); + files = asPathContainer(variables["input-file"].as()); if (const auto it = variables.find("archives"); it != variables.end()) - archives = it->second.as(); + archives = asPathContainer(it->second.as()); return true; } } catch (std::exception& e) { - std::cout << "ERROR parsing arguments: " << e.what() << "\n\n" << desc << std::endl; + std::cout << "Error parsing arguments: " << e.what() << "\n\n" << desc << std::endl; return false; } @@ -164,64 +189,62 @@ Allowed options)"); int main(int argc, char** argv) { - std::vector files; + Files::PathContainer files, sources; bool writeDebugLog = false; - std::vector archives; - if (!parseOptions(argc, argv, files, writeDebugLog, archives)) + bool quiet = false; + if (!parseOptions(argc, argv, files, sources, writeDebugLog, quiet)) return 1; Nif::Reader::setLoadUnsupportedFiles(true); Nif::Reader::setWriteNifDebugLog(writeDebugLog); std::unique_ptr vfs; - if (!archives.empty()) + if (!sources.empty()) { vfs = std::make_unique(); - for (const std::filesystem::path& path : archives) + for (const std::filesystem::path& path : sources) { + if (!quiet) + std::cout << "Adding data source '" << Files::pathToUnicodeString(path) << "'" << std::endl; + try { if (auto archive = makeArchive(path)) vfs->addArchive(std::move(archive)); else - std::cerr << '"' << path << "\" is unsupported archive" << std::endl; - vfs->buildIndex(); + std::cerr << "Error: '" << Files::pathToUnicodeString(path) << "' is not an archive or directory" + << std::endl; } catch (std::exception& e) { - std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; } } + + vfs->buildIndex(); } - // std::cout << "Reading Files" << std::endl; for (const auto& path : files) { try { if (isNIF(path)) { - // std::cout << "Decoding: " << name << std::endl; - Nif::NIFFile file(path); - Nif::Reader reader(file); - if (vfs != nullptr) - reader.parse(vfs->get(Files::pathToUnicodeString(path))); - else - reader.parse(Files::openConstrainedFileStream(path)); + readNIF({}, path, vfs.get(), quiet); } else if (auto archive = makeArchive(path)) { - readVFS(std::move(archive), path); + readVFS(std::move(archive), path, quiet); } else { - std::cerr << "ERROR: \"" << Files::pathToUnicodeString(path) - << "\" is not a nif file, bsa/ba2 file, or directory!" << std::endl; + std::cerr << "Error: '" << Files::pathToUnicodeString(path) + << "' is not a NIF file, BSA/BA2 archive, or directory" << std::endl; } } catch (std::exception& e) { - std::cerr << "ERROR, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; } } return 0; From 3bf5b150c54012121b852cdd746e412c3d174049 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 27 Nov 2023 00:58:05 +0300 Subject: [PATCH 009/231] bsatool: Support extracting files with forward slash paths --- apps/bsatool/bsatool.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index de755e7d1d..28711df929 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -194,7 +194,8 @@ int extract(std::unique_ptr& bsa, Arguments& info) // Get a stream for the file to extract for (auto it = bsa->getList().rbegin(); it != bsa->getList().rend(); ++it) { - if (Misc::StringUtils::ciEqual(Misc::StringUtils::stringToU8String(it->name()), archivePath)) + auto streamPath = Misc::StringUtils::stringToU8String(it->name()); + if (Misc::StringUtils::ciEqual(streamPath, archivePath) || Misc::StringUtils::ciEqual(streamPath, extractPath)) { stream = bsa->getFile(&*it); break; From 9c94058727880eabc41c5d48b2f853eeb4c3e4a8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 4 Nov 2023 20:02:55 +0300 Subject: [PATCH 010/231] Support Oblivion parallax setup --- components/nifosg/nifloader.cpp | 1 + components/shader/shadervisitor.cpp | 10 +++++++ components/shader/shadervisitor.hpp | 1 + files/shaders/compatibility/objects.frag | 35 ++++++++++++++++-------- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 17c608f2d4..436f2e1d34 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2363,6 +2363,7 @@ namespace NifOsg osg::StateSet* stateset = node->getOrCreateStateSet(); handleTextureProperty( texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags); + node->setUserValue("applyMode", static_cast(texprop->mApplyMode)); break; } case Nif::RC_BSShaderPPLightingProperty: diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index 96e3d42f78..a08652620d 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -181,6 +181,7 @@ namespace Shader , mAlphaBlend(false) , mBlendFuncOverridden(false) , mAdditiveBlending(false) + , mDiffuseHeight(false) , mNormalHeight(false) , mTexStageRequiringTangents(-1) , mSoftParticles(false) @@ -303,6 +304,14 @@ namespace Shader if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) mRequirements.back().mSoftParticles = true; + int applyMode; + // Oblivion parallax + if (node.getUserValue("applyMode", applyMode) && applyMode == 4) + { + mRequirements.back().mShaderRequired = true; + mRequirements.back().mDiffuseHeight = true; + } + // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); @@ -615,6 +624,7 @@ namespace Shader addedState->addUniform("useDiffuseMapForShadowAlpha"); } + defineMap["diffuseParallax"] = reqs.mDiffuseHeight ? "1" : "0"; defineMap["parallax"] = reqs.mNormalHeight ? "1" : "0"; writableStateSet->addUniform(new osg::Uniform("colorMode", reqs.mColorMode)); diff --git a/components/shader/shadervisitor.hpp b/components/shader/shadervisitor.hpp index 66bd8c2a9d..a8e79ec995 100644 --- a/components/shader/shadervisitor.hpp +++ b/components/shader/shadervisitor.hpp @@ -108,6 +108,7 @@ namespace Shader bool mBlendFuncOverridden; bool mAdditiveBlending; + bool mDiffuseHeight; // true if diffuse map has height info in alpha channel bool mNormalHeight; // true if normal map has height info in alpha channel // -1 == no tangents required diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index b86678af87..6de1f6d02f 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -113,10 +113,6 @@ void main() applyOcclusionDiscard(orthoDepthMapCoord, texture2D(orthoDepthMap, orthoDepthMapCoord.xy * 0.5 + 0.5).r); #endif -#if @diffuseMap - vec2 adjustedDiffuseUV = diffuseMapUV; -#endif - vec3 normal = normalize(passNormal); vec3 viewVec = normalize(passViewPos.xyz); @@ -131,11 +127,24 @@ void main() normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); #endif -#if @parallax +#if !@diffuseMap + gl_FragData[0] = vec4(1.0); +#else + vec2 adjustedDiffuseUV = diffuseMapUV; + +#if @normalMap && (@parallax || @diffuseParallax) vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; vec3 eyeDir = normalize(cameraPos - objectPos); - vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, (passTangent.w > 0.0) ? -1.f : 1.f); +#if @parallax + float height = normalTex.a; + float flipY = (passTangent.w > 0.0) ? -1.f : 1.f; +#else + float height = texture2D(diffuseMap, diffuseMapUV).a; + // FIXME: shouldn't be necessary, but in this path false-positives are common + float flipY = -1.f; +#endif + vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, height, flipY); adjustedDiffuseUV += offset; // only offset diffuse for now, other textures are more likely to be using a completely different UV set // TODO: check not working as the same UV buffer is being bound to different targets @@ -149,14 +158,16 @@ void main() #endif -vec3 viewNormal = normalize(gl_NormalMatrix * normal); - -#if @diffuseMap - gl_FragData[0] = texture2D(diffuseMap, adjustedDiffuseUV); - gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); + vec4 diffuseTex = texture2D(diffuseMap, adjustedDiffuseUV); + gl_FragData[0].xyz = diffuseTex.xyz; +#if !@diffuseParallax + gl_FragData[0].a = diffuseTex.a * coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); #else - gl_FragData[0] = vec4(1.0); + gl_FragData[0].a = 1.0; #endif +#endif + + vec3 viewNormal = normalize(gl_NormalMatrix * normal); vec4 diffuseColor = getDiffuseColor(); gl_FragData[0].a *= diffuseColor.a; From 99024d38267f96a2109acde1ee7342996bb07e32 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 27 Nov 2023 01:31:38 +0300 Subject: [PATCH 011/231] Revamp NIF debug logging Disabled by default Extend it to supported files Log more version info Reduce noise --- components/nif/niffile.cpp | 32 ++++++++++++------- .../reference/modding/settings/models.rst | 5 ++- files/settings-default.cfg | 4 +-- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 37e40938d3..d6d063a254 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -512,6 +512,10 @@ namespace Nif void Reader::parse(Files::IStreamPtr&& stream) { + const bool writeDebug = sWriteNifDebugLog; + if (writeDebug) + Log(Debug::Verbose) << "NIF Debug: Reading file: '" << mFilename << "'"; + const std::array fileHash = Files::getHash(mFilename, *stream); mHash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); @@ -538,15 +542,9 @@ namespace Nif }; const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), mVersion) != supportedVers.end(); - const bool writeDebugLog = sWriteNifDebugLog; - if (!supportedVersion) - { - if (!sLoadUnsupportedFiles) - throw Nif::Exception("Unsupported NIF version: " + versionToString(mVersion), mFilename); - if (writeDebugLog) - Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << versionToString(mVersion) - << ". Proceed with caution! File: " << mFilename; - } + + if (!supportedVersion && !sLoadUnsupportedFiles) + throw Nif::Exception("Unsupported NIF version: " + versionToString(mVersion), mFilename); const bool hasEndianness = mVersion >= NIFStream::generateVersion(20, 0, 0, 4); const bool hasUserVersion = mVersion >= NIFStream::generateVersion(10, 0, 1, 8); @@ -603,6 +601,17 @@ namespace Nif } } + if (writeDebug) + { + std::stringstream versionInfo; + versionInfo << "NIF Debug: Version: " << versionToString(mVersion); + if (mUserVersion) + versionInfo << "\nUser version: " << mUserVersion; + if (mBethVersion) + versionInfo << "\nBSStream version: " << mBethVersion; + Log(Debug::Verbose) << versionInfo.str(); + } + if (hasRecTypeListings) { // TODO: 20.3.1.2 uses DJB hashes instead of strings @@ -658,9 +667,8 @@ namespace Nif r = entry->second(); - if (!supportedVersion && writeDebugLog) - Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" - << mFilename << ")"; + if (writeDebug) + Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i; assert(r != nullptr); assert(r->recType != RC_MISSING); diff --git a/docs/source/reference/modding/settings/models.rst b/docs/source/reference/modding/settings/models.rst index 998be9e6ea..4cf8d4c0d6 100644 --- a/docs/source/reference/modding/settings/models.rst +++ b/docs/source/reference/modding/settings/models.rst @@ -269,7 +269,6 @@ write nif debug log :Type: boolean :Range: True/False -:Default: True +:Default: False -If enabled, log the loading process of unsupported NIF files. -:ref:`load unsupported nif files` setting must be enabled for this setting to have any effect. +If enabled, log the loading process of NIF files. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index da1c97519a..4a90a46cc5 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -1136,8 +1136,8 @@ weathersnow = meshes/snow.nif # Blizzard weather effect weatherblizzard = meshes/blizzard.nif -# Enable to write logs when loading unsupported nif file -write nif debug log = true +# Enable to write logs when loading NIF files +write nif debug log = false [Groundcover] From 5e96825e6bd2ceb0e94f60aa5721e4f61fb91606 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Nov 2023 11:14:44 +0400 Subject: [PATCH 012/231] Highlight new items in launcher by text formatting, not by color --- apps/launcher/datafilespage.cpp | 15 ++++++++++----- .../contentselector/model/contentmodel.cpp | 17 ++++++----------- files/ui/datafilespage.ui | 8 ++++---- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 4d3f0cc64f..4ad99c99f1 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -301,12 +301,14 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) auto row = ui.directoryListWidget->count() - 1; auto* item = ui.directoryListWidget->item(row); - // Display new content with green background + // Display new content with custom formatting if (mNewDataDirs.contains(canonicalDirPath)) { tooltip += "Will be added to the current profile\n"; - item->setBackground(Qt::green); - item->setForeground(Qt::black); + QFont font = item->font(); + font.setBold(true); + font.setItalic(true); + item->setFont(font); } // deactivate data-local and global data directory: they are always included @@ -737,8 +739,11 @@ void Launcher::DataFilesPage::addArchive(const QString& name, Qt::CheckState sel ui.archiveListWidget->item(row)->setCheckState(selected); if (mKnownArchives.filter(name).isEmpty()) // XXX why contains doesn't work here ??? { - ui.archiveListWidget->item(row)->setBackground(Qt::green); - ui.archiveListWidget->item(row)->setForeground(Qt::black); + auto item = ui.archiveListWidget->item(row); + QFont font = item->font(); + font.setBold(true); + font.setItalic(true); + item->setFont(font); } } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 7b4f5db158..f8ecc67998 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -164,20 +165,14 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int return isLoadOrderError(file) ? mWarningIcon : QVariant(); } - case Qt::BackgroundRole: + case Qt::FontRole: { if (isNew(file->fileName())) { - return QVariant(QColor(Qt::green)); - } - return QVariant(); - } - - case Qt::ForegroundRole: - { - if (isNew(file->fileName())) - { - return QVariant(QColor(Qt::black)); + auto font = QFont(); + font.setBold(true); + font.setItalic(true); + return font; } return QVariant(); } diff --git a/files/ui/datafilespage.ui b/files/ui/datafilespage.ui index 239df34961..249207123e 100644 --- a/files/ui/datafilespage.ui +++ b/files/ui/datafilespage.ui @@ -6,7 +6,7 @@ 0 0 - 571 + 573 384 @@ -30,7 +30,7 @@ - <html><head/><body><p><span style=" font-style:italic;">note: content files that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + <html><head/><body><p>note: content files that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -57,7 +57,7 @@ - <html><head/><body><p><span style=" font-style:italic;">note: directories that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + <html><head/><body><p>note: directories that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> @@ -210,7 +210,7 @@ - <html><head/><body><p><span style=" font-style:italic;">note: archives that are not part of current Content List are </span><span style=" font-style:italic; background-color:#00ff00;">highlighted</span></p></body></html> + <html><head/><body><p>note: archives that are not part of current Content List are <span style=" font-style:italic;font-weight: bold">highlighted</span></p></body></html> From c5b16d1ba2ab5cdeb7e2b0172f788d5c2cb3b692 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 29 Nov 2023 09:28:40 +0400 Subject: [PATCH 013/231] Do not include formatting spaces to localizable strings --- apps/launcher/importpage.cpp | 6 +-- apps/launcher/maindialog.cpp | 56 ++++++++++++------------ apps/wizard/componentselectionpage.cpp | 8 ++-- apps/wizard/conclusionpage.cpp | 17 +++---- apps/wizard/existinginstallationpage.cpp | 14 +++--- apps/wizard/installationpage.cpp | 24 ++++------ apps/wizard/installationtargetpage.cpp | 19 ++++---- apps/wizard/mainwizard.cpp | 36 +++++++-------- components/process/processinvoker.cpp | 30 ++++++------- 9 files changed, 103 insertions(+), 107 deletions(-) diff --git a/apps/launcher/importpage.cpp b/apps/launcher/importpage.cpp index fa91ad1654..44c5867c0d 100644 --- a/apps/launcher/importpage.cpp +++ b/apps/launcher/importpage.cpp @@ -104,9 +104,9 @@ void Launcher::ImportPage::on_importerButton_clicked() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not open or create %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

") + tr("

Could not open or create %1 for writing

" + "

Please make sure you have the right permissions " + "and try again.

") .arg(file.fileName())); msgBox.exec(); return; diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index bba3bbe5e1..177d4fe88c 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -121,9 +121,9 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() if (!create_directories(userConfigDir)) { cfgError(tr("Error opening OpenMW configuration file"), - tr("
Could not create directory %0

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not create directory %0

" + "Please make sure you have the right permissions " + "and try again.
") .arg(Files::pathToQString(canonical(userConfigDir)))); return FirstRunDialogResultFailure; } @@ -136,10 +136,10 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() msgBox.setIcon(QMessageBox::Question); msgBox.setStandardButtons(QMessageBox::NoButton); msgBox.setText( - tr("

Welcome to OpenMW!

\ -

It is recommended to run the Installation Wizard.

\ -

The Wizard will let you select an existing Morrowind installation, \ - or install Morrowind for OpenMW to use.

")); + tr("

Welcome to OpenMW!

" + "

It is recommended to run the Installation Wizard.

" + "

The Wizard will let you select an existing Morrowind installation, " + "or install Morrowind for OpenMW to use.

")); QAbstractButton* wizardButton = msgBox.addButton(tr("Run &Installation Wizard"), QMessageBox::AcceptRole); // ActionRole doesn't work?! @@ -297,9 +297,9 @@ bool Launcher::MainDialog::setupLauncherSettings() if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { cfgError(tr("Error opening OpenMW configuration file"), - tr("
Could not open %0 for reading:

%1

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not open %0 for reading:

%1

" + "Please make sure you have the right permissions " + "and try again.
") .arg(file.fileName()) .arg(file.errorString())); return false; @@ -327,9 +327,9 @@ bool Launcher::MainDialog::setupGameSettings() if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { cfgError(tr("Error opening OpenMW configuration file"), - tr("
Could not open %0 for reading

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not open %0 for reading

" + "Please make sure you have the right permissions " + "and try again.
") .arg(file.fileName())); return {}; } @@ -388,8 +388,8 @@ bool Launcher::MainDialog::setupGameData() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::NoButton); msgBox.setText( - tr("
Could not find the Data Files location

\ - The directory containing the data files was not found.")); + tr("
Could not find the Data Files location

" + "The directory containing the data files was not found.")); QAbstractButton* wizardButton = msgBox.addButton(tr("Run &Installation Wizard..."), QMessageBox::ActionRole); QAbstractButton* skipButton = msgBox.addButton(tr("Skip"), QMessageBox::RejectRole); @@ -419,8 +419,8 @@ bool Launcher::MainDialog::setupGraphicsSettings() catch (std::exception& e) { cfgError(tr("Error reading OpenMW configuration files"), - tr("
The problem may be due to an incomplete installation of OpenMW.
\ - Reinstalling OpenMW may resolve the problem.
") + tr("
The problem may be due to an incomplete installation of OpenMW.
" + "Reinstalling OpenMW may resolve the problem.
") + e.what()); return false; } @@ -460,9 +460,9 @@ bool Launcher::MainDialog::writeSettings() if (!create_directories(userPath)) { cfgError(tr("Error creating OpenMW configuration directory"), - tr("
Could not create %0

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not create %0

" + "Please make sure you have the right permissions " + "and try again.
") .arg(Files::pathToQString(userPath))); return false; } @@ -479,9 +479,9 @@ bool Launcher::MainDialog::writeSettings() { // File cannot be opened or created cfgError(tr("Error writing OpenMW configuration file"), - tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not open or create %0 for writing

" + "Please make sure you have the right permissions " + "and try again.
") .arg(file.fileName())); return false; } @@ -510,9 +510,9 @@ bool Launcher::MainDialog::writeSettings() { // File cannot be opened or created cfgError(tr("Error writing Launcher configuration file"), - tr("
Could not open or create %0 for writing

\ - Please make sure you have the right permissions \ - and try again.
") + tr("
Could not open or create %0 for writing

" + "Please make sure you have the right permissions " + "and try again.
") .arg(file.fileName())); return false; } @@ -562,8 +562,8 @@ void Launcher::MainDialog::play() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("
You do not have a game file selected.

\ - OpenMW will not start without a game file selected.
")); + tr("
You do not have a game file selected.

" + "OpenMW will not start without a game file selected.
")); msgBox.exec(); return; } diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index e492f4b83a..63f2eff078 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -138,10 +138,10 @@ bool Wizard::ComponentSelectionPage::validatePage() msgBox.setIcon(QMessageBox::Information); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText( - tr("

You are about to install Tribunal

\ -

Bloodmoon is already installed on your computer.

\ -

However, it is recommended that you install Tribunal before Bloodmoon.

\ -

Would you like to re-install Bloodmoon?

")); + tr("

You are about to install Tribunal

" + "

Bloodmoon is already installed on your computer.

" + "

However, it is recommended that you install Tribunal before Bloodmoon.

" + "

Would you like to re-install Bloodmoon?

")); QAbstractButton* reinstallButton = msgBox.addButton(tr("Re-install &Bloodmoon"), QMessageBox::ActionRole); diff --git a/apps/wizard/conclusionpage.cpp b/apps/wizard/conclusionpage.cpp index a184c745ee..4a4a4ef689 100644 --- a/apps/wizard/conclusionpage.cpp +++ b/apps/wizard/conclusionpage.cpp @@ -37,22 +37,23 @@ void Wizard::ConclusionPage::initializePage() if (field(QLatin1String("installation.retailDisc")).toBool() == true) { textLabel->setText( - tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

\ -

Click Finish to close the Wizard.

")); + tr("

The OpenMW Wizard successfully installed Morrowind on your computer.

" + "

Click Finish to close the Wizard.

")); } else { - textLabel->setText(tr( - "

The OpenMW Wizard successfully modified your existing Morrowind installation.

\ -

Click Finish to close the Wizard.

")); + textLabel->setText( + tr("

The OpenMW Wizard successfully modified your existing Morrowind " + "installation.

Click Finish to close the Wizard.

")); } } else { textLabel->setText( - tr("

The OpenMW Wizard failed to install Morrowind on your computer.

\ -

Please report any bugs you might have encountered to our \ - bug tracker.
Make sure to include the installation log.


")); + tr("

The OpenMW Wizard failed to install Morrowind on your computer.

" + "

Please report any bugs you might have encountered to our " + "bug tracker.
Make sure to include the " + "installation log.


")); } } diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 71ae331a61..d5ba009799 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -58,9 +58,9 @@ bool Wizard::ExistingInstallationPage::validatePage() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); msgBox.setText( - QObject::tr("
Could not find Morrowind.ini

\ - The Wizard needs to update settings in this file.

\ - Press \"Browse...\" to specify the location manually.
")); + QObject::tr("
Could not find Morrowind.ini

" + "The Wizard needs to update settings in this file.

" + "Press \"Browse...\" to specify the location manually.
")); QAbstractButton* browseButton2 = msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); @@ -107,8 +107,8 @@ void Wizard::ExistingInstallationPage::on_browseButton_clicked() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - QObject::tr("Morrowind.bsa is missing!
\ - Make sure your Morrowind installation is complete.")); + QObject::tr("Morrowind.bsa is missing!
" + "Make sure your Morrowind installation is complete.")); msgBox.exec(); return; } @@ -187,8 +187,8 @@ bool Wizard::ExistingInstallationPage::versionIsOK(QString directory_name) msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); msgBox.setText( - QObject::tr("
There may be a more recent version of Morrowind available.

\ - Do you wish to continue anyway?
")); + QObject::tr("
There may be a more recent version of Morrowind available.

" + "Do you wish to continue anyway?
")); int ret = msgBox.exec(); if (ret == QMessageBox::Yes) { diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index e06972332a..cf2e3671e1 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -175,8 +175,8 @@ void Wizard::InstallationPage::showFileDialog(Wizard::Component component) if (path.isEmpty()) { logTextEdit->appendHtml( - tr("


\ - Error: The installation was aborted by the user

")); + tr("


" + "Error: The installation was aborted by the user

")); mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); mWizard->mError = true; @@ -205,8 +205,8 @@ void Wizard::InstallationPage::showOldVersionDialog() if (ret == QMessageBox::No) { logTextEdit->appendHtml( - tr("


\ - Error: The installation was aborted by the user

")); + tr("


" + "Error: The installation was aborted by the user

")); mWizard->addLogText(QLatin1String("Error: The installation was aborted by the user")); mWizard->mError = true; @@ -236,14 +236,8 @@ void Wizard::InstallationPage::installationError(const QString& text, const QStr { installProgressLabel->setText(tr("Installation failed!")); - logTextEdit->appendHtml( - tr("


\ - Error: %1

") - .arg(text)); - logTextEdit->appendHtml( - tr("

\ - %1

") - .arg(details)); + logTextEdit->appendHtml(tr("


Error: %1

").arg(text)); + logTextEdit->appendHtml(tr("

%1

").arg(details)); mWizard->addLogText(QLatin1String("Error: ") + text); mWizard->addLogText(details); @@ -254,9 +248,9 @@ void Wizard::InstallationPage::installationError(const QString& text, const QStr msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

The Wizard has encountered an error

\ -

The error reported was:

%1

\ -

Press "Show Details..." for more information.

") + tr("

The Wizard has encountered an error

" + "

The error reported was:

%1

" + "

Press "Show Details..." for more information.

") .arg(text)); msgBox.setDetailedText(details); diff --git a/apps/wizard/installationtargetpage.cpp b/apps/wizard/installationtargetpage.cpp index c32573184d..dc94d2d002 100644 --- a/apps/wizard/installationtargetpage.cpp +++ b/apps/wizard/installationtargetpage.cpp @@ -48,9 +48,9 @@ bool Wizard::InstallationTargetPage::validatePage() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not create the destination directory

\ -

Please make sure you have the right permissions \ - and try again, or specify a different location.

")); + tr("

Could not create the destination directory

" + "

Please make sure you have the right permissions " + "and try again, or specify a different location.

")); msgBox.exec(); return false; } @@ -65,9 +65,9 @@ bool Wizard::InstallationTargetPage::validatePage() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not write to the destination directory

\ -

Please make sure you have the right permissions \ - and try again, or specify a different location.

")); + tr("

Could not write to the destination directory

" + "

Please make sure you have the right permissions " + "and try again, or specify a different location.

")); msgBox.exec(); return false; } @@ -79,9 +79,10 @@ bool Wizard::InstallationTargetPage::validatePage() msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

The destination directory is not empty

\ -

An existing Morrowind installation is present in the specified location.

\ -

Please specify a different location, or go back and select the location as an existing installation.

")); + tr("

The destination directory is not empty

" + "

An existing Morrowind installation is present in the specified location.

" + "

Please specify a different location, or go back and select the location as an existing " + "installation.

")); msgBox.exec(); return false; } diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 9abb61cfd7..e9cce3db5e 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -55,9 +55,9 @@ Wizard::MainWizard::MainWizard(QWidget* parent) &MainWizard::importerFinished); mLogError = tr( - "

Could not open %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

"); + "

Could not open %1 for writing

" + "

Please make sure you have the right permissions " + "and try again.

"); std::filesystem::create_directories(mCfgMgr.getUserConfigPath()); std::filesystem::create_directories(mCfgMgr.getUserDataPath()); @@ -139,9 +139,9 @@ void Wizard::MainWizard::addLogText(const QString& text) void Wizard::MainWizard::setupGameSettings() { QString message( - tr("

Could not open %1 for reading

\ -

Please make sure you have the right permissions \ - and try again.

")); + tr("

Could not open %1 for reading

" + "

Please make sure you have the right permissions " + "and try again.

")); // Load the user config file first, separately // So we can write it properly, uncontaminated @@ -210,9 +210,9 @@ void Wizard::MainWizard::setupLauncherSettings() path.append(QLatin1String(Config::LauncherSettings::sLauncherConfigFileName)); QString message( - tr("

Could not open %1 for reading

\ -

Please make sure you have the right permissions \ - and try again.

")); + tr("

Could not open %1 for reading

" + "

Please make sure you have the right permissions " + "and try again.

")); QFile file(path); @@ -427,9 +427,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not create %1

\ -

Please make sure you have the right permissions \ - and try again.

") + tr("

Could not create %1

" + "

Please make sure you have the right permissions " + "and try again.

") .arg(userPath)); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); @@ -448,9 +448,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not open %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

") + tr("

Could not open %1 for writing

" + "

Please make sure you have the right permissions " + "and try again.

") .arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); @@ -475,9 +475,9 @@ void Wizard::MainWizard::writeSettings() msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not open %1 for writing

\ -

Please make sure you have the right permissions \ - and try again.

") + tr("

Could not open %1 for writing

" + "

Please make sure you have the right permissions " + "and try again.

") .arg(file.fileName())); connect(&msgBox, &QDialog::finished, qApp, &QApplication::quit, Qt::QueuedConnection); msgBox.exec(); diff --git a/components/process/processinvoker.cpp b/components/process/processinvoker.cpp index 73e23eb9f9..9489076acb 100644 --- a/components/process/processinvoker.cpp +++ b/components/process/processinvoker.cpp @@ -76,9 +76,9 @@ bool Process::ProcessInvoker::startProcess(const QString& name, const QStringLis msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not find %1

\ -

The application is not found.

\ -

Please make sure OpenMW is installed correctly and try again.

") + tr("

Could not find %1

" + "

The application is not found.

" + "

Please make sure OpenMW is installed correctly and try again.

") .arg(info.fileName())); msgBox.exec(); return false; @@ -91,9 +91,9 @@ bool Process::ProcessInvoker::startProcess(const QString& name, const QStringLis msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not start %1

\ -

The application is not executable.

\ -

Please make sure you have the right permissions and try again.

") + tr("

Could not start %1

" + "

The application is not executable.

" + "

Please make sure you have the right permissions and try again.

") .arg(info.fileName())); msgBox.exec(); return false; @@ -109,9 +109,9 @@ bool Process::ProcessInvoker::startProcess(const QString& name, const QStringLis msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Could not start %1

\ -

An error occurred while starting %1.

\ -

Press \"Show Details...\" for more information.

") + tr("

Could not start %1

" + "

An error occurred while starting %1.

" + "

Press \"Show Details...\" for more information.

") .arg(info.fileName())); msgBox.setDetailedText(mProcess->errorString()); msgBox.exec(); @@ -168,9 +168,9 @@ void Process::ProcessInvoker::processError(QProcess::ProcessError error) msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Executable %1 returned an error

\ -

An error occurred while running %1.

\ -

Press \"Show Details...\" for more information.

") + tr("

Executable %1 returned an error

" + "

An error occurred while running %1.

" + "

Press \"Show Details...\" for more information.

") .arg(mName)); msgBox.setDetailedText(mProcess->errorString()); msgBox.exec(); @@ -191,9 +191,9 @@ void Process::ProcessInvoker::processFinished(int exitCode, QProcess::ExitStatus msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setText( - tr("

Executable %1 returned an error

\ -

An error occurred while running %1.

\ -

Press \"Show Details...\" for more information.

") + tr("

Executable %1 returned an error

" + "

An error occurred while running %1.

" + "

Press \"Show Details...\" for more information.

") .arg(mName)); msgBox.setDetailedText(error); msgBox.exec(); From b96600a7fb7c760ca073038a6efe081c61aa2913 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 28 Nov 2023 21:50:40 +0300 Subject: [PATCH 014/231] Make niftest exceptions more informative --- apps/niftest/niftest.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index 77752d25bd..29488fb677 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -72,9 +72,10 @@ std::unique_ptr makeArchive(const std::filesystem::path& path) void readNIF( const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet) { + const std::string pathStr = Files::pathToUnicodeString(path); if (!quiet) { - std::cout << "Reading NIF file '" << Files::pathToUnicodeString(path) << "'"; + std::cout << "Reading NIF file '" << pathStr << "'"; if (!source.empty()) std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'"; std::cout << std::endl; @@ -85,13 +86,13 @@ void readNIF( Nif::NIFFile file(fullPath); Nif::Reader reader(file); if (vfs != nullptr) - reader.parse(vfs->get(Files::pathToUnicodeString(path))); + reader.parse(vfs->get(pathStr)); else reader.parse(Files::openConstrainedFileStream(fullPath)); } catch (std::exception& e) { - std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Failed to read '" << pathStr << "':" << std::endl << e.what() << std::endl; } } @@ -204,20 +205,20 @@ int main(int argc, char** argv) vfs = std::make_unique(); for (const std::filesystem::path& path : sources) { + const std::string pathStr = Files::pathToUnicodeString(path); if (!quiet) - std::cout << "Adding data source '" << Files::pathToUnicodeString(path) << "'" << std::endl; + std::cout << "Adding data source '" << pathStr << "'" << std::endl; try { if (auto archive = makeArchive(path)) vfs->addArchive(std::move(archive)); else - std::cerr << "Error: '" << Files::pathToUnicodeString(path) << "' is not an archive or directory" - << std::endl; + std::cerr << "Error: '" << pathStr << "' is not an archive or directory" << std::endl; } catch (std::exception& e) { - std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Failed to add data source '" << pathStr << "': " << e.what() << std::endl; } } @@ -226,6 +227,7 @@ int main(int argc, char** argv) for (const auto& path : files) { + const std::string pathStr = Files::pathToUnicodeString(path); try { if (isNIF(path)) @@ -238,13 +240,12 @@ int main(int argc, char** argv) } else { - std::cerr << "Error: '" << Files::pathToUnicodeString(path) - << "' is not a NIF file, BSA/BA2 archive, or directory" << std::endl; + std::cerr << "Error: '" << pathStr << "' is not a NIF file, BSA/BA2 archive, or directory" << std::endl; } } catch (std::exception& e) { - std::cerr << "Error, an exception has occurred: " << e.what() << std::endl; + std::cerr << "Failed to read '" << pathStr << "': " << e.what() << std::endl; } } return 0; From b7a4cb0c8385ab613bc131e53e33aaa0e92a693b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 18 Nov 2023 17:42:12 +0100 Subject: [PATCH 015/231] The anim queue should still update when underwater. CharState_SpecialIdle should be retained until the animation queue is done. --- apps/openmw/mwmechanics/character.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index dd7b97b6a5..df04e3cfaa 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2306,6 +2306,7 @@ namespace MWMechanics jumpstate = JumpState_None; } + updateAnimQueue(); if (mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle)) { if (inwater) @@ -2315,8 +2316,8 @@ namespace MWMechanics else idlestate = CharState_Idle; } - else - updateAnimQueue(); + else if (!mAnimQueue.empty()) + idlestate = CharState_SpecialIdle; if (!mSkipAnim) { From f4cc16e469988fae2901970acb639164a6482130 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 28 Nov 2023 21:29:05 +0100 Subject: [PATCH 016/231] feedback --- apps/openmw/mwmechanics/character.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index df04e3cfaa..d9166aa683 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2307,17 +2307,15 @@ namespace MWMechanics } updateAnimQueue(); - if (mAnimQueue.empty() || inwater || (sneak && mIdleState != CharState_SpecialIdle)) - { - if (inwater) - idlestate = CharState_IdleSwim; - else if (sneak && !mInJump) - idlestate = CharState_IdleSneak; - else - idlestate = CharState_Idle; - } - else if (!mAnimQueue.empty()) + if (!mAnimQueue.empty()) idlestate = CharState_SpecialIdle; + else if (sneak && !mInJump) + idlestate = CharState_IdleSneak; + else + idlestate = CharState_Idle; + + if (inwater) + idlestate = CharState_IdleSwim; if (!mSkipAnim) { From 23aacbd9147a8de57823f4974a36385dddf90a80 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 30 Nov 2023 19:20:38 +0100 Subject: [PATCH 017/231] Introduce a minimum supported save game format --- CHANGELOG.md | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 29 +++++++++++++++++-------- components/esm3/formatversion.hpp | 4 ++++ files/data/l10n/OMWEngine/de.yaml | 3 +++ files/data/l10n/OMWEngine/en.yaml | 3 +++ files/data/l10n/OMWEngine/fr.yaml | 3 +++ files/data/l10n/OMWEngine/ru.yaml | 3 +++ files/data/l10n/OMWEngine/sv.yaml | 3 +++ 8 files changed, 40 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ad2bae6c..eb61affa40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,7 @@ Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds + Feature #6624: Drop support for old saves Feature #6726: Lua API for creating new objects Feature #6864: Lua file access API Feature #6922: Improve launcher appearance diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 826c0dbba6..c040dca8dd 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -411,10 +411,25 @@ void MWState::StateManager::loadGame(const Character* character, const std::file ESM::ESMReader reader; reader.open(filepath); - if (reader.getFormatVersion() > ESM::CurrentSaveGameFormatVersion) - throw VersionMismatchError( - "This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade " - "to the newest OpenMW version to load this file."); + ESM::FormatVersion version = reader.getFormatVersion(); + if (version > ESM::CurrentSaveGameFormatVersion) + throw VersionMismatchError("#{OMWEngine:LoadingRequiresNewVersionError}"); + else if (version < ESM::MinSupportedSaveGameFormatVersion) + { + const char* release; + // Report the last version still capable of reading this save + if (version <= ESM::OpenMW0_48SaveGameFormatVersion) + release = "OpenMW 0.48.0"; + else + { + // Insert additional else if statements above to cover future releases + static_assert(ESM::MinSupportedSaveGameFormatVersion <= ESM::OpenMW0_49SaveGameFormatVersion); + release = "OpenMW 0.49.0"; + } + auto l10n = MWBase::Environment::get().getL10nManager()->getContext("OMWEngine"); + std::string message = l10n->formatMessage("LoadingRequiresOldVersionError", { "version" }, { release }); + throw VersionMismatchError(message); + } std::map contentFileMap = buildContentFileIndexMap(reader); reader.setContentFileMapping(&contentFileMap); @@ -607,11 +622,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file std::vector buttons; buttons.emplace_back("#{Interface:OK}"); - std::string error; - if (typeid(e) == typeid(VersionMismatchError)) - error = "#{OMWEngine:LoadingFailed}: #{OMWEngine:LoadingRequiresNewVersionError}"; - else - error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what()); + std::string error = "#{OMWEngine:LoadingFailed}: " + std::string(e.what()); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error, buttons); } diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 12a73fc12b..1b4bee0bc5 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -26,6 +26,10 @@ namespace ESM inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; + + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 0; + inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; + inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } #endif diff --git a/files/data/l10n/OMWEngine/de.yaml b/files/data/l10n/OMWEngine/de.yaml index 26838bd93c..0f729d0077 100644 --- a/files/data/l10n/OMWEngine/de.yaml +++ b/files/data/l10n/OMWEngine/de.yaml @@ -25,6 +25,9 @@ BuildingNavigationMesh: "Baue Navigationsgitter" #LoadingRequiresNewVersionError: |- # This save file was created using a newer version of OpenMW and is thus not supported. # Please upgrade to the newest OpenMW version to load this file. +# LoadingRequiresOldVersionError: |- +# This save file was created using an older version of OpenMW in a format that is no longer supported. +# Load and save this file using {version} to upgrade it. #NewGameConfirmation: "Do you want to start a new game and lose the current one?" #SaveGameDenied: "The game cannot be saved right now." #SavingInProgress: "Saving..." diff --git a/files/data/l10n/OMWEngine/en.yaml b/files/data/l10n/OMWEngine/en.yaml index ee2a33ee71..09db2b496d 100644 --- a/files/data/l10n/OMWEngine/en.yaml +++ b/files/data/l10n/OMWEngine/en.yaml @@ -22,6 +22,9 @@ LoadingInProgress: "Loading Save Game" LoadingRequiresNewVersionError: |- This save file was created using a newer version of OpenMW and is thus not supported. Please upgrade to the newest OpenMW version to load this file. +LoadingRequiresOldVersionError: |- + This save file was created using an older version of OpenMW in a format that is no longer supported. + Load and save this file using {version} to upgrade it. NewGameConfirmation: "Do you want to start a new game and lose the current one?" SaveGameDenied: "The game cannot be saved right now." SavingInProgress: "Saving..." diff --git a/files/data/l10n/OMWEngine/fr.yaml b/files/data/l10n/OMWEngine/fr.yaml index 689ccc59a5..f2772b017e 100644 --- a/files/data/l10n/OMWEngine/fr.yaml +++ b/files/data/l10n/OMWEngine/fr.yaml @@ -22,6 +22,9 @@ LoadingInProgress: "Chargement de la sauvegarde" LoadingRequiresNewVersionError: |- Ce fichier de sauvegarde provient d'une version plus récente d'OpenMW, il n'est par consequent pas supporté. Mettez à jour votre version d'OpenMW afin de pouvoir charger cette sauvegarde. +# LoadingRequiresOldVersionError: |- +# This save file was created using an older version of OpenMW in a format that is no longer supported. +# Load and save this file using {version} to upgrade it. NewGameConfirmation: "Voulez-vous démarrer une nouvelle partie ? Toute progression non sauvegardée sera perdue." SaveGameDenied: "Sauvegarde impossible" SavingInProgress: "Sauvegarde en cours..." diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index b645b681b1..cecd6fc37c 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -22,6 +22,9 @@ LoadingInProgress: "Загрузка сохранения" LoadingRequiresNewVersionError: |- Это сохранение создано более новой версией OpenMW и поэтому не может быть загружено. Обновите OpenMW до последней версии, чтобы загрузить этот файл. +# LoadingRequiresOldVersionError: |- +# This save file was created using an older version of OpenMW in a format that is no longer supported. +# Load and save this file using {version} to upgrade it. NewGameConfirmation: "Вы хотите начать новую игру? Текущая игра будет потеряна." SaveGameDenied: "В данный момент игру нельзя сохранить." SavingInProgress: "Сохранение..." diff --git a/files/data/l10n/OMWEngine/sv.yaml b/files/data/l10n/OMWEngine/sv.yaml index f4c9db031a..dc65726fdd 100644 --- a/files/data/l10n/OMWEngine/sv.yaml +++ b/files/data/l10n/OMWEngine/sv.yaml @@ -22,6 +22,9 @@ LoadingInProgress: "Laddar sparat spel" LoadingRequiresNewVersionError: |- Denna sparfil skapades i en nyare version av OpenMW och stöds därför inte. Uppgradera till den senaste versionen av OpenMW för att ladda filen. +# LoadingRequiresOldVersionError: |- +# This save file was created using an older version of OpenMW in a format that is no longer supported. +# Load and save this file using {version} to upgrade it. NewGameConfirmation: "Vill du starta ett nytt spel och förlora det pågående spelet?" SaveGameDenied: "Spelet kan inte sparas just nu." SavingInProgress: "Sparar..." From 194bcb0187c4c12ddfb2681889fa957ccf50b89c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 30 Nov 2023 22:08:30 +0100 Subject: [PATCH 018/231] Drop support for save game format 0 (pre 0.37) --- apps/openmw/mwclass/creature.cpp | 32 +++++------ apps/openmw/mwclass/npc.cpp | 24 +++----- apps/openmw/mwdialogue/journalimp.cpp | 2 +- apps/openmw/mwstate/statemanagerimp.cpp | 1 - apps/openmw/mwworld/cellstore.cpp | 6 +- apps/openmw/mwworld/player.cpp | 7 +-- apps/openmw_test_suite/mwworld/test_store.cpp | 4 +- components/esm/defs.hpp | 2 - components/esm3/creaturestats.cpp | 11 +--- components/esm3/dialoguestate.cpp | 8 --- components/esm3/fogstate.cpp | 2 +- components/esm3/formatversion.hpp | 7 ++- components/esm3/inventorystate.cpp | 8 --- components/esm3/npcstats.cpp | 57 ------------------- components/esm3/npcstats.hpp | 2 - components/esm3/objectstate.cpp | 4 -- components/esm3/quickkeys.cpp | 6 -- components/esm3/statstate.cpp | 4 -- 18 files changed, 36 insertions(+), 151 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 2628cd3905..ed601a9255 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -779,32 +779,26 @@ namespace MWClass const ESM::CreatureState& creatureState = state.asCreatureState(); - if (state.mVersion > 0) + if (!ptr.getRefData().getCustomData()) { - if (!ptr.getRefData().getCustomData()) + if (creatureState.mCreatureStats.mMissingACDT) + ensureCustomData(ptr); + else { - if (creatureState.mCreatureStats.mMissingACDT) - ensureCustomData(ptr); + // Create a CustomData, but don't fill it from ESM records (not needed) + auto data = std::make_unique(); + + if (hasInventoryStore(ptr)) + data->mContainerStore = std::make_unique(); else - { - // Create a CustomData, but don't fill it from ESM records (not needed) - auto data = std::make_unique(); + data->mContainerStore = std::make_unique(); - if (hasInventoryStore(ptr)) - data->mContainerStore = std::make_unique(); - else - data->mContainerStore = std::make_unique(); + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + data->mContainerStore->setPtr(ptr); - MWBase::Environment::get().getWorldModel()->registerPtr(ptr); - data->mContainerStore->setPtr(ptr); - - ptr.getRefData().setCustomData(std::move(data)); - } + ptr.getRefData().setCustomData(std::move(data)); } } - else - ensureCustomData( - ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index b9e8bc8dfb..95a3b713fa 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1333,25 +1333,19 @@ namespace MWClass const ESM::NpcState& npcState = state.asNpcState(); - if (state.mVersion > 0) + if (!ptr.getRefData().getCustomData()) { - if (!ptr.getRefData().getCustomData()) + if (npcState.mCreatureStats.mMissingACDT) + ensureCustomData(ptr); + else { - if (npcState.mCreatureStats.mMissingACDT) - ensureCustomData(ptr); - else - { - // Create a CustomData, but don't fill it from ESM records (not needed) - auto data = std::make_unique(); - MWBase::Environment::get().getWorldModel()->registerPtr(ptr); - data->mInventoryStore.setPtr(ptr); - ptr.getRefData().setCustomData(std::move(data)); - } + // Create a CustomData, but don't fill it from ESM records (not needed) + auto data = std::make_unique(); + MWBase::Environment::get().getWorldModel()->registerPtr(ptr); + data->mInventoryStore.setPtr(ptr); + ptr.getRefData().setCustomData(std::move(data)); } } - else - ensureCustomData( - ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless. NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index e4d9453c83..28a2e16699 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -250,7 +250,7 @@ namespace MWDialogue void Journal::readRecord(ESM::ESMReader& reader, uint32_t type) { - if (type == ESM::REC_JOUR || type == ESM::REC_JOUR_LEGACY) + if (type == ESM::REC_JOUR) { ESM::JournalEntry record; record.load(reader); diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index c040dca8dd..89b63c35e4 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -472,7 +472,6 @@ void MWState::StateManager::loadGame(const Character* character, const std::file break; case ESM::REC_JOUR: - case ESM::REC_JOUR_LEGACY: case ESM::REC_QUES: MWBase::Environment::get().getJournal()->readRecord(reader, n.toInt()); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 340def5859..930f26c1cc 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -252,16 +252,16 @@ namespace if (!record) return; - if (state.mVersion < 15) + if (state.mVersion <= ESM::MaxOldRestockingFormatVersion) fixRestocking(record, state); - if (state.mVersion < 17) + if (state.mVersion <= ESM::MaxClearModifiersFormatVersion) { if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory); else if constexpr (std::is_same_v) MWWorld::convertMagicEffects(state.mCreatureStats, state.mInventory, &state.mNpcStats); } - else if (state.mVersion < 20) + else if (state.mVersion <= ESM::MaxOldCreatureStatsFormatVersion) { if constexpr (std::is_same_v || std::is_same_v) MWWorld::convertStats(state.mCreatureStats); diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index 0d7afb559f..b498bb488b 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -334,12 +334,7 @@ namespace MWWorld if (player.mObject.mNpcStats.mIsWerewolf) { - if (player.mObject.mNpcStats.mWerewolfDeprecatedData) - { - saveStats(); - setWerewolfStats(); - } - else if (reader.getFormatVersion() <= ESM::MaxOldSkillsAndAttributesFormatVersion) + if (reader.getFormatVersion() <= ESM::MaxOldSkillsAndAttributesFormatVersion) { setWerewolfStats(); if (player.mSetWerewolfAcrobatics) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index d8890bc5ab..f80c12917c 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -272,8 +272,8 @@ namespace ESM::CurrentContentFormatVersion, ESM::MaxOldWeatherFormatVersion, ESM::MaxOldDeathAnimationFormatVersion, - ESM::MaxOldForOfWarFormatVersion, - ESM::MaxWerewolfDeprecatedDataFormatVersion, + ESM::MaxOldFogOfWarFormatVersion, + ESM::MaxUnoptimizedCharacterDataFormatVersion, ESM::MaxOldTimeLeftFormatVersion, ESM::MaxIntFallbackFormatVersion, ESM::MaxClearModifiersFormatVersion, diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 96d70f6fea..55404ee768 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -146,8 +146,6 @@ namespace ESM // format 0 - saved games REC_SAVE = esm3Recname("SAVE"), - REC_JOUR_LEGACY = esm3Recname("\xa4UOR"), // "\xa4UOR", rather than "JOUR", little oversight when magic numbers - // were calculated by hand, needs to be supported for older files now REC_JOUR = esm3Recname("JOUR"), REC_QUES = esm3Recname("QUES"), REC_GSCR = esm3Recname("GSCR"), diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index d8fb0d6969..8281916e30 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -38,7 +38,7 @@ namespace ESM mHitRecovery = false; mBlock = false; mRecalcDynamicStats = false; - if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion) + if (esm.getFormatVersion() <= MaxUnoptimizedCharacterDataFormatVersion) { esm.getHNOT(mDead, "DEAD"); esm.getHNOT(mDeathAnimationFinished, "DFNT"); @@ -46,13 +46,9 @@ namespace ESM mDeathAnimationFinished = true; esm.getHNOT(mDied, "DIED"); esm.getHNOT(mMurdered, "MURD"); - if (esm.isNextSub("FRHT")) - esm.skipHSub(); // Friendly hits, no longer used esm.getHNOT(mTalkedTo, "TALK"); esm.getHNOT(mAlarmed, "ALRM"); esm.getHNOT(mAttacked, "ATKD"); - if (esm.isNextSub("HOST")) - esm.skipHSub(); // Hostile, no longer used if (esm.isNextSub("ATCK")) esm.skipHSub(); // attackingOrSpell, no longer used esm.getHNOT(mKnockdown, "KNCK"); @@ -82,9 +78,6 @@ namespace ESM mMovementFlags = 0; esm.getHNOT(mMovementFlags, "MOVE"); - if (esm.isNextSub("ASTR")) - esm.skipHSub(); // attackStrength, no longer used - mFallHeight = 0; esm.getHNOT(mFallHeight, "FALL"); @@ -92,7 +85,7 @@ namespace ESM mLastHitAttemptObject = esm.getHNORefId("LHAT"); - if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion) + if (esm.getFormatVersion() <= MaxUnoptimizedCharacterDataFormatVersion) esm.getHNOT(mRecalcDynamicStats, "CALC"); mDrawState = 0; diff --git a/components/esm3/dialoguestate.cpp b/components/esm3/dialoguestate.cpp index 88fbe9659a..7095e096cb 100644 --- a/components/esm3/dialoguestate.cpp +++ b/components/esm3/dialoguestate.cpp @@ -22,14 +22,6 @@ namespace ESM esm.getHNT(reaction, "INTV"); mChangedFactionReaction[faction][faction2] = reaction; } - - // no longer used - while (esm.isNextSub("REAC")) - { - esm.skipHSub(); - esm.getSubName(); - esm.skipHSub(); - } } } diff --git a/components/esm3/fogstate.cpp b/components/esm3/fogstate.cpp index 3ee4600c90..2c07438070 100644 --- a/components/esm3/fogstate.cpp +++ b/components/esm3/fogstate.cpp @@ -74,7 +74,7 @@ namespace ESM tex.mImageData.resize(imageSize); esm.getExact(tex.mImageData.data(), imageSize); - if (dataFormat <= MaxOldForOfWarFormatVersion) + if (dataFormat <= MaxOldFogOfWarFormatVersion) convertFogOfWar(tex.mImageData); mFogTextures.push_back(tex); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 1b4bee0bc5..e02e0176a9 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -11,10 +11,11 @@ namespace ESM inline constexpr FormatVersion CurrentContentFormatVersion = 1; inline constexpr FormatVersion MaxOldWeatherFormatVersion = 1; inline constexpr FormatVersion MaxOldDeathAnimationFormatVersion = 2; - inline constexpr FormatVersion MaxOldForOfWarFormatVersion = 6; - inline constexpr FormatVersion MaxWerewolfDeprecatedDataFormatVersion = 7; + inline constexpr FormatVersion MaxOldFogOfWarFormatVersion = 6; + inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7; inline constexpr FormatVersion MaxOldTimeLeftFormatVersion = 8; inline constexpr FormatVersion MaxIntFallbackFormatVersion = 10; + inline constexpr FormatVersion MaxOldRestockingFormatVersion = 14; inline constexpr FormatVersion MaxClearModifiersFormatVersion = 16; inline constexpr FormatVersion MaxOldAiPackageFormatVersion = 17; inline constexpr FormatVersion MaxOldSkillsAndAttributesFormatVersion = 18; @@ -27,7 +28,7 @@ namespace ESM inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 0; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 1; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index 84a52ff518..a6130af473 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -22,14 +22,6 @@ namespace ESM ObjectState state; - // obsolete - if (esm.isNextSub("SLOT")) - { - int32_t slot; - esm.getHT(slot); - mEquipmentSlots[index] = slot; - } - state.mRef.loadId(esm, true); state.load(esm); diff --git a/components/esm3/npcstats.cpp b/components/esm3/npcstats.cpp index a21ba807e4..dc221a5b43 100644 --- a/components/esm3/npcstats.cpp +++ b/components/esm3/npcstats.cpp @@ -41,46 +41,6 @@ namespace ESM for (auto& skill : mSkills) skill.load(esm, intFallback); - mWerewolfDeprecatedData = false; - if (esm.getFormatVersion() <= MaxWerewolfDeprecatedDataFormatVersion && esm.peekNextSub("STBA")) - { - // we have deprecated werewolf skills, stored interleaved - // Load into one big vector, then remove every 2nd value - mWerewolfDeprecatedData = true; - std::vector> skills(mSkills.begin(), mSkills.end()); - - for (size_t i = 0; i < std::size(mSkills); ++i) - { - StatState skill; - skill.load(esm, intFallback); - skills.push_back(skill); - } - - int i = 0; - for (std::vector>::iterator it = skills.begin(); it != skills.end(); ++i) - { - if (i % 2 == 1) - it = skills.erase(it); - else - ++it; - } - if (skills.size() != std::size(mSkills)) - throw std::runtime_error( - "Invalid number of skill for werewolf deprecated data: " + std::to_string(skills.size())); - std::copy(skills.begin(), skills.end(), mSkills.begin()); - } - - // No longer used - bool hasWerewolfAttributes = false; - esm.getHNOT(hasWerewolfAttributes, "HWAT"); - if (hasWerewolfAttributes) - { - StatState dummy; - for (int i = 0; i < ESM::Attribute::Length; ++i) - dummy.load(esm, intFallback); - mWerewolfDeprecatedData = true; - } - mIsWerewolf = false; esm.getHNOT(mIsWerewolf, "WOLF"); @@ -93,14 +53,6 @@ namespace ESM mWerewolfKills = 0; esm.getHNOT(mWerewolfKills, "WKIL"); - // No longer used - if (esm.isNextSub("PROF")) - esm.skipHSub(); // int profit - - // No longer used - if (esm.isNextSub("ASTR")) - esm.skipHSub(); // attackStrength - mLevelProgress = 0; esm.getHNOT(mLevelProgress, "LPRO"); @@ -116,14 +68,6 @@ namespace ESM mTimeToStartDrowning = 0; esm.getHNOT(mTimeToStartDrowning, "DRTI"); - // No longer used - float lastDrowningHit = 0; - esm.getHNOT(lastDrowningHit, "DRLH"); - - // No longer used - float levelHealthBonus = 0; - esm.getHNOT(levelHealthBonus, "LVLH"); - mCrimeId = -1; esm.getHNOT(mCrimeId, "CRID"); } @@ -195,7 +139,6 @@ namespace ESM void NpcStats::blank() { - mWerewolfDeprecatedData = false; mIsWerewolf = false; mDisposition = 0; mBounty = 0; diff --git a/components/esm3/npcstats.hpp b/components/esm3/npcstats.hpp index ccb58a12ad..425a62162b 100644 --- a/components/esm3/npcstats.hpp +++ b/components/esm3/npcstats.hpp @@ -31,8 +31,6 @@ namespace ESM bool mIsWerewolf; - bool mWerewolfDeprecatedData; - std::map mFactions; int32_t mDisposition; std::array, ESM::Skill::Length> mSkills; diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index a7fe41d66c..fca4c64f5f 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -48,10 +48,6 @@ namespace ESM mFlags = 0; esm.getHNOT(mFlags, "FLAG"); - // obsolete - int32_t unused; - esm.getHNOT(unused, "LTIM"); - mAnimationState.load(esm); // FIXME: assuming "false" as default would make more sense, but also break compatibility with older save files diff --git a/components/esm3/quickkeys.cpp b/components/esm3/quickkeys.cpp index ababa535b7..7477fd24fa 100644 --- a/components/esm3/quickkeys.cpp +++ b/components/esm3/quickkeys.cpp @@ -8,9 +8,6 @@ namespace ESM void QuickKeys::load(ESMReader& esm) { - if (esm.isNextSub("KEY_")) - esm.getSubHeader(); // no longer used, because sub-record hierachies do not work properly in esmreader - while (esm.isNextSub("TYPE")) { QuickKey key; @@ -18,9 +15,6 @@ namespace ESM key.mId = esm.getHNRefId("ID__"); mKeys.push_back(key); - - if (esm.isNextSub("KEY_")) - esm.getSubHeader(); // no longer used, because sub-record hierachies do not work properly in esmreader } } diff --git a/components/esm3/statstate.cpp b/components/esm3/statstate.cpp index 7477d83e2d..b46c2e34fd 100644 --- a/components/esm3/statstate.cpp +++ b/components/esm3/statstate.cpp @@ -32,10 +32,6 @@ namespace ESM int32_t current = 0; esm.getHNOT(current, "STCU"); mCurrent = static_cast(current); - - int32_t oldDamage = 0; - esm.getHNOT(oldDamage, "STDA"); - mDamage = static_cast(oldDamage); } else { From 659d7fefa1eca36ac7f9ebe6a540c2a34cf8551a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 1 Dec 2023 16:38:38 +0100 Subject: [PATCH 019/231] Add Russian localization --- files/data/l10n/OMWEngine/ru.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/files/data/l10n/OMWEngine/ru.yaml b/files/data/l10n/OMWEngine/ru.yaml index cecd6fc37c..2bcb76a442 100644 --- a/files/data/l10n/OMWEngine/ru.yaml +++ b/files/data/l10n/OMWEngine/ru.yaml @@ -22,9 +22,9 @@ LoadingInProgress: "Загрузка сохранения" LoadingRequiresNewVersionError: |- Это сохранение создано более новой версией OpenMW и поэтому не может быть загружено. Обновите OpenMW до последней версии, чтобы загрузить этот файл. -# LoadingRequiresOldVersionError: |- -# This save file was created using an older version of OpenMW in a format that is no longer supported. -# Load and save this file using {version} to upgrade it. +LoadingRequiresOldVersionError: |- + Это сохранение создано старой версией OpenMW и использует формат, который больше не поддерживается. + Загрузите и сохраните этот файл в {version}, чтобы обновить его. NewGameConfirmation: "Вы хотите начать новую игру? Текущая игра будет потеряна." SaveGameDenied: "В данный момент игру нельзя сохранить." SavingInProgress: "Сохранение..." From 88a6ecabae77854092a13e5cff070c62e6afaedf Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 08:11:49 +0100 Subject: [PATCH 020/231] Add lookup index to editor settings category Prevent adding duplicate settings there. --- apps/opencs/model/prefs/category.cpp | 12 ++++++++---- apps/opencs/model/prefs/category.hpp | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/opencs/model/prefs/category.cpp b/apps/opencs/model/prefs/category.cpp index 5a82be08fc..9c8923f042 100644 --- a/apps/opencs/model/prefs/category.cpp +++ b/apps/opencs/model/prefs/category.cpp @@ -24,6 +24,9 @@ CSMPrefs::State* CSMPrefs::Category::getState() const void CSMPrefs::Category::addSetting(Setting* setting) { + if (!mIndex.emplace(setting->getKey(), setting).second) + throw std::logic_error("Category " + mKey + " already has setting: " + setting->getKey()); + mSettings.push_back(setting); } @@ -39,11 +42,12 @@ CSMPrefs::Category::Iterator CSMPrefs::Category::end() CSMPrefs::Setting& CSMPrefs::Category::operator[](const std::string& key) { - for (Iterator iter = mSettings.begin(); iter != mSettings.end(); ++iter) - if ((*iter)->getKey() == key) - return **iter; + const auto it = mIndex.find(key); - throw std::logic_error("Invalid user setting: " + key); + if (it != mIndex.end()) + return *it->second; + + throw std::logic_error("Invalid user setting in " + mKey + " category: " + key); } void CSMPrefs::Category::update() diff --git a/apps/opencs/model/prefs/category.hpp b/apps/opencs/model/prefs/category.hpp index 5c75f99067..19b559b384 100644 --- a/apps/opencs/model/prefs/category.hpp +++ b/apps/opencs/model/prefs/category.hpp @@ -3,6 +3,7 @@ #include #include +#include #include namespace CSMPrefs @@ -20,6 +21,7 @@ namespace CSMPrefs State* mParent; std::string mKey; Container mSettings; + std::unordered_map mIndex; public: Category(State* parent, const std::string& key); From 4c13ecea236bfe1ee7c0b5a554a198fff0d9831c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 00:52:09 +0100 Subject: [PATCH 021/231] Use settings values for editor --- apps/opencs/model/prefs/boolsetting.cpp | 16 ++---- apps/opencs/model/prefs/boolsetting.hpp | 6 +- apps/opencs/model/prefs/category.cpp | 6 ++ apps/opencs/model/prefs/category.hpp | 3 + apps/opencs/model/prefs/coloursetting.cpp | 17 ++---- apps/opencs/model/prefs/coloursetting.hpp | 7 ++- apps/opencs/model/prefs/doublesetting.cpp | 17 ++---- apps/opencs/model/prefs/doublesetting.hpp | 6 +- apps/opencs/model/prefs/enumsetting.cpp | 24 +++----- apps/opencs/model/prefs/enumsetting.hpp | 7 +-- apps/opencs/model/prefs/intsetting.cpp | 17 ++---- apps/opencs/model/prefs/intsetting.hpp | 6 +- apps/opencs/model/prefs/modifiersetting.cpp | 17 ++---- apps/opencs/model/prefs/modifiersetting.hpp | 6 +- apps/opencs/model/prefs/setting.cpp | 29 ++-------- apps/opencs/model/prefs/setting.hpp | 59 ++++++++++++++++--- apps/opencs/model/prefs/shortcutsetting.cpp | 17 ++---- apps/opencs/model/prefs/shortcutsetting.hpp | 6 +- apps/opencs/model/prefs/state.cpp | 64 ++++++--------------- apps/opencs/model/prefs/state.hpp | 2 +- apps/opencs/model/prefs/stringsetting.cpp | 17 ++---- apps/opencs/model/prefs/stringsetting.hpp | 8 +-- apps/opencs/model/prefs/subcategory.cpp | 4 +- apps/opencs/model/prefs/subcategory.hpp | 4 +- apps/opencs/model/world/actoradapter.cpp | 7 ++- 25 files changed, 164 insertions(+), 208 deletions(-) diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index c1eb626969..2ca8315245 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -11,9 +11,8 @@ #include "state.hpp" CSMPrefs::BoolSetting::BoolSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, bool default_) - : Setting(parent, mutex, key, label) - , mDefault(default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { } @@ -27,7 +26,7 @@ CSMPrefs::BoolSetting& CSMPrefs::BoolSetting::setTooltip(const std::string& tool CSMPrefs::SettingWidgets CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) { mWidget = new QCheckBox(getLabel(), parent); - mWidget->setCheckState(mDefault ? Qt::Checked : Qt::Unchecked); + mWidget->setCheckState(getValue() ? Qt::Checked : Qt::Unchecked); if (!mTooltip.empty()) { @@ -44,17 +43,12 @@ void CSMPrefs::BoolSetting::updateWidget() { if (mWidget) { - mWidget->setCheckState( - Settings::Manager::getBool(getKey(), getParent()->getKey()) ? Qt::Checked : Qt::Unchecked); + mWidget->setCheckState(getValue() ? Qt::Checked : Qt::Unchecked); } } void CSMPrefs::BoolSetting::valueChanged(int value) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setBool(getKey(), getParent()->getKey(), value); - } - + setValue(value != Qt::Unchecked); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp index 9d53f98e9e..fd67019a78 100644 --- a/apps/opencs/model/prefs/boolsetting.hpp +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -12,16 +12,16 @@ namespace CSMPrefs { class Category; - class BoolSetting : public Setting + class BoolSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - bool mDefault; QCheckBox* mWidget; public: - BoolSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, bool default_); + explicit BoolSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); BoolSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/category.cpp b/apps/opencs/model/prefs/category.cpp index 9c8923f042..3ae4826953 100644 --- a/apps/opencs/model/prefs/category.cpp +++ b/apps/opencs/model/prefs/category.cpp @@ -5,6 +5,7 @@ #include "setting.hpp" #include "state.hpp" +#include "subcategory.hpp" CSMPrefs::Category::Category(State* parent, const std::string& key) : mParent(parent) @@ -30,6 +31,11 @@ void CSMPrefs::Category::addSetting(Setting* setting) mSettings.push_back(setting); } +void CSMPrefs::Category::addSubcategory(Subcategory* setting) +{ + mSettings.push_back(setting); +} + CSMPrefs::Category::Iterator CSMPrefs::Category::begin() { return mSettings.begin(); diff --git a/apps/opencs/model/prefs/category.hpp b/apps/opencs/model/prefs/category.hpp index 19b559b384..ef67c82138 100644 --- a/apps/opencs/model/prefs/category.hpp +++ b/apps/opencs/model/prefs/category.hpp @@ -10,6 +10,7 @@ namespace CSMPrefs { class State; class Setting; + class Subcategory; class Category { @@ -32,6 +33,8 @@ namespace CSMPrefs void addSetting(Setting* setting); + void addSubcategory(Subcategory* setting); + Iterator begin(); Iterator end(); diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index e2ece04722..61aa92063e 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -14,9 +14,8 @@ #include "state.hpp" CSMPrefs::ColourSetting::ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, QColor default_) - : Setting(parent, mutex, key, label) - , mDefault(std::move(default_)) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { } @@ -31,7 +30,7 @@ CSMPrefs::SettingWidgets CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); - mWidget = new CSVWidget::ColorEditor(mDefault, parent); + mWidget = new CSVWidget::ColorEditor(toColor(), parent); if (!mTooltip.empty()) { @@ -48,18 +47,12 @@ CSMPrefs::SettingWidgets CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) void CSMPrefs::ColourSetting::updateWidget() { if (mWidget) - { - mWidget->setColor(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); - } + mWidget->setColor(toColor()); } void CSMPrefs::ColourSetting::valueChanged() { CSVWidget::ColorEditor& widget = dynamic_cast(*sender()); - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), widget.color().name().toUtf8().data()); - } - + setValue(widget.color().name().toStdString()); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp index 5a1a7a2df2..f5af627491 100644 --- a/apps/opencs/model/prefs/coloursetting.hpp +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -20,16 +20,17 @@ namespace CSVWidget namespace CSMPrefs { class Category; - class ColourSetting : public Setting + + class ColourSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - QColor mDefault; CSVWidget::ColorEditor* mWidget; public: - ColourSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, QColor default_); + explicit ColourSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); ColourSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 153298ce57..b275c2e9b7 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -15,12 +15,11 @@ #include "state.hpp" CSMPrefs::DoubleSetting::DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, double default_) - : Setting(parent, mutex, key, label) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mPrecision(2) , mMin(0) , mMax(std::numeric_limits::max()) - , mDefault(default_) , mWidget(nullptr) { } @@ -63,7 +62,7 @@ CSMPrefs::SettingWidgets CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) mWidget = new QDoubleSpinBox(parent); mWidget->setDecimals(mPrecision); mWidget->setRange(mMin, mMax); - mWidget->setValue(mDefault); + mWidget->setValue(getValue()); if (!mTooltip.empty()) { @@ -80,17 +79,11 @@ CSMPrefs::SettingWidgets CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) void CSMPrefs::DoubleSetting::updateWidget() { if (mWidget) - { - mWidget->setValue(Settings::Manager::getFloat(getKey(), getParent()->getKey())); - } + mWidget->setValue(getValue()); } void CSMPrefs::DoubleSetting::valueChanged(double value) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setFloat(getKey(), getParent()->getKey(), value); - } - + setValue(value); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index 33342d2f5b..add85cb9b3 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -9,7 +9,7 @@ namespace CSMPrefs { class Category; - class DoubleSetting : public Setting + class DoubleSetting final : public TypedSetting { Q_OBJECT @@ -17,11 +17,11 @@ namespace CSMPrefs double mMin; double mMax; std::string mTooltip; - double mDefault; QDoubleSpinBox* mWidget; public: - DoubleSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, double default_); + explicit DoubleSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); DoubleSetting& setPrecision(int precision); diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index d55e4005a4..1521c3cc13 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -45,9 +45,8 @@ CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const std::string& value, const } CSMPrefs::EnumSetting::EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, const EnumValue& default_) - : Setting(parent, mutex, key, label) - , mDefault(default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { } @@ -83,16 +82,18 @@ CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) mWidget = new QComboBox(parent); size_t index = 0; + const std::string value = getValue(); for (size_t i = 0; i < mValues.mValues.size(); ++i) { - if (mDefault.mValue == mValues.mValues[i].mValue) + if (value == mValues.mValues[i].mValue) index = i; mWidget->addItem(QString::fromUtf8(mValues.mValues[i].mValue.c_str())); if (!mValues.mValues[i].mTooltip.empty()) - mWidget->setItemData(i, QString::fromUtf8(mValues.mValues[i].mTooltip.c_str()), Qt::ToolTipRole); + mWidget->setItemData( + static_cast(i), QString::fromUtf8(mValues.mValues[i].mTooltip.c_str()), Qt::ToolTipRole); } mWidget->setCurrentIndex(static_cast(index)); @@ -111,20 +112,11 @@ CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) void CSMPrefs::EnumSetting::updateWidget() { if (mWidget) - { - int index - = mWidget->findText(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); - - mWidget->setCurrentIndex(index); - } + mWidget->setCurrentIndex(mWidget->findText(QString::fromStdString(getValue()))); } void CSMPrefs::EnumSetting::valueChanged(int value) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), mValues.mValues.at(value).mValue); - } - + setValue(mValues.mValues.at(value).mValue); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp index f430988aa6..51241d593f 100644 --- a/apps/opencs/model/prefs/enumsetting.hpp +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -34,18 +34,17 @@ namespace CSMPrefs EnumValues& add(const std::string& value, const std::string& tooltip); }; - class EnumSetting : public Setting + class EnumSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - EnumValue mDefault; EnumValues mValues; QComboBox* mWidget; public: - EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, const EnumValue& default_); + explicit EnumSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); EnumSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 7d0b40a45a..023339b9c1 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -15,11 +15,10 @@ #include "state.hpp" CSMPrefs::IntSetting::IntSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, int default_) - : Setting(parent, mutex, key, label) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mMin(0) , mMax(std::numeric_limits::max()) - , mDefault(default_) , mWidget(nullptr) { } @@ -55,7 +54,7 @@ CSMPrefs::SettingWidgets CSMPrefs::IntSetting::makeWidgets(QWidget* parent) mWidget = new QSpinBox(parent); mWidget->setRange(mMin, mMax); - mWidget->setValue(mDefault); + mWidget->setValue(getValue()); if (!mTooltip.empty()) { @@ -72,17 +71,11 @@ CSMPrefs::SettingWidgets CSMPrefs::IntSetting::makeWidgets(QWidget* parent) void CSMPrefs::IntSetting::updateWidget() { if (mWidget) - { - mWidget->setValue(Settings::Manager::getInt(getKey(), getParent()->getKey())); - } + mWidget->setValue(getValue()); } void CSMPrefs::IntSetting::valueChanged(int value) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setInt(getKey(), getParent()->getKey(), value); - } - + setValue(value); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp index 8fb3bdb1f6..a1ed19ffd6 100644 --- a/apps/opencs/model/prefs/intsetting.hpp +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -12,18 +12,18 @@ namespace CSMPrefs { class Category; - class IntSetting : public Setting + class IntSetting final : public TypedSetting { Q_OBJECT int mMin; int mMax; std::string mTooltip; - int mDefault; QSpinBox* mWidget; public: - IntSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label, int default_); + explicit IntSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); // defaults to [0, std::numeric_limits::max()] IntSetting& setRange(int min, int max); diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index 173092dc2b..53db4c8521 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -19,8 +19,9 @@ class QWidget; namespace CSMPrefs { - ModifierSetting::ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) - : Setting(parent, mutex, key, label) + ModifierSetting::ModifierSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mButton(nullptr) , mEditorActive(false) { @@ -53,7 +54,7 @@ namespace CSMPrefs { if (mButton) { - const std::string& shortcut = Settings::Manager::getString(getKey(), getParent()->getKey()); + const std::string& shortcut = getValue(); int modifier; State::get().getShortcutManager().convertFromString(shortcut, modifier); @@ -131,15 +132,7 @@ namespace CSMPrefs void ModifierSetting::storeValue(int modifier) { State::get().getShortcutManager().setModifier(getKey(), modifier); - - // Convert to string and assign - std::string value = State::get().getShortcutManager().convertToString(modifier); - - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), value); - } - + setValue(State::get().getShortcutManager().convertToString(modifier)); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp index 9e308875fd..cf9ce8f149 100644 --- a/apps/opencs/model/prefs/modifiersetting.hpp +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -15,12 +15,14 @@ class QPushButton; namespace CSMPrefs { class Category; - class ModifierSetting : public Setting + + class ModifierSetting final : public TypedSetting { Q_OBJECT public: - ModifierSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); + explicit ModifierSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp index 666b4ba1b1..cc5077dc83 100644 --- a/apps/opencs/model/prefs/setting.cpp +++ b/apps/opencs/model/prefs/setting.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "category.hpp" #include "state.hpp" @@ -14,12 +15,14 @@ QMutex* CSMPrefs::Setting::getMutex() return mMutex; } -CSMPrefs::Setting::Setting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) +CSMPrefs::Setting::Setting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) : QObject(parent->getState()) , mParent(parent) , mMutex(mutex) , mKey(key) , mLabel(label) + , mIndex(index) { } @@ -33,30 +36,6 @@ const std::string& CSMPrefs::Setting::getKey() const return mKey; } -int CSMPrefs::Setting::toInt() const -{ - QMutexLocker lock(mMutex); - return Settings::Manager::getInt(mKey, mParent->getKey()); -} - -double CSMPrefs::Setting::toDouble() const -{ - QMutexLocker lock(mMutex); - return Settings::Manager::getFloat(mKey, mParent->getKey()); -} - -std::string CSMPrefs::Setting::toString() const -{ - QMutexLocker lock(mMutex); - return Settings::Manager::getString(mKey, mParent->getKey()); -} - -bool CSMPrefs::Setting::isTrue() const -{ - QMutexLocker lock(mMutex); - return Settings::Manager::getBool(mKey, mParent->getKey()); -} - QColor CSMPrefs::Setting::toColor() const { // toString() handles lock diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index 882add20ca..f5d9e83ef1 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -4,8 +4,13 @@ #include #include +#include #include +#include + +#include "category.hpp" + class QWidget; class QColor; class QMutex; @@ -14,8 +19,6 @@ class QLabel; namespace CSMPrefs { - class Category; - struct SettingWidgets { QLabel* mLabel; @@ -31,12 +34,35 @@ namespace CSMPrefs QMutex* mMutex; std::string mKey; QString mLabel; + Settings::Index& mIndex; protected: QMutex* getMutex(); + template + void resetValueImpl() + { + QMutexLocker lock(mMutex); + return mIndex.get(mParent->getKey(), mKey).reset(); + } + + template + T getValueImpl() const + { + QMutexLocker lock(mMutex); + return mIndex.get(mParent->getKey(), mKey).get(); + } + + template + void setValueImpl(const T& value) + { + QMutexLocker lock(mMutex); + return mIndex.get(mParent->getKey(), mKey).set(value); + } + public: - Setting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); + explicit Setting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); ~Setting() override = default; @@ -47,23 +73,42 @@ namespace CSMPrefs /// \note If make_widgets() has not been called yet then nothing happens. virtual void updateWidget() = 0; + virtual void reset() = 0; + const Category* getParent() const; const std::string& getKey() const; const QString& getLabel() const { return mLabel; } - int toInt() const; + int toInt() const { return getValueImpl(); } - double toDouble() const; + double toDouble() const { return getValueImpl(); } - std::string toString() const; + std::string toString() const { return getValueImpl(); } - bool isTrue() const; + bool isTrue() const { return getValueImpl(); } QColor toColor() const; }; + template + class TypedSetting : public Setting + { + public: + using Setting::Setting; + + void reset() final + { + resetValueImpl(); + updateWidget(); + } + + T getValue() const { return getValueImpl(); } + + void setValue(const T& value) { return setValueImpl(value); } + }; + // note: fullKeys have the format categoryKey/settingKey bool operator==(const Setting& setting, const std::string& fullKey); bool operator==(const std::string& fullKey, const Setting& setting); diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index aae290b3f4..42902f02cd 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -18,8 +18,9 @@ namespace CSMPrefs { - ShortcutSetting::ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label) - : Setting(parent, mutex, key, label) + ShortcutSetting::ShortcutSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mButton(nullptr) , mEditorActive(false) , mEditorPos(0) @@ -57,7 +58,7 @@ namespace CSMPrefs { if (mButton) { - const std::string& shortcut = Settings::Manager::getString(getKey(), getParent()->getKey()); + const std::string shortcut = getValue(); QKeySequence sequence; State::get().getShortcutManager().convertFromString(shortcut, sequence); @@ -170,15 +171,7 @@ namespace CSMPrefs void ShortcutSetting::storeValue(const QKeySequence& sequence) { State::get().getShortcutManager().setSequence(getKey(), sequence); - - // Convert to string and assign - std::string value = State::get().getShortcutManager().convertToString(sequence); - - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), value); - } - + setValue(State::get().getShortcutManager().convertToString(sequence)); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index 74cad995d4..84857a9bc7 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -17,12 +17,14 @@ class QWidget; namespace CSMPrefs { class Category; - class ShortcutSetting : public Setting + + class ShortcutSetting final : public TypedSetting { Q_OBJECT public: - ShortcutSetting(Category* parent, QMutex* mutex, const std::string& key, const QString& label); + explicit ShortcutSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index d74044f6d5..d55913fb3c 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -465,9 +465,7 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const setDefault(key, std::to_string(default_)); - default_ = Settings::Manager::getInt(key, mCurrentCategory->second.getKey()); - - CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -483,10 +481,8 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, stream << default_; setDefault(key, stream.str()); - default_ = Settings::Manager::getFloat(key, mCurrentCategory->second.getKey()); - CSMPrefs::DoubleSetting* setting - = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -500,10 +496,7 @@ CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, cons setDefault(key, default_ ? "true" : "false"); - default_ = Settings::Manager::getBool(key, mCurrentCategory->second.getKey()); - - CSMPrefs::BoolSetting* setting - = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + CSMPrefs::BoolSetting* setting = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -517,10 +510,7 @@ CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(const std::string& key, cons setDefault(key, default_.mValue); - default_.mValue = Settings::Manager::getString(key, mCurrentCategory->second.getKey()); - - CSMPrefs::EnumSetting* setting - = new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + CSMPrefs::EnumSetting* setting = new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -534,11 +524,8 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(const std::string& key, setDefault(key, default_.name().toUtf8().data()); - default_.setNamedColor( - QString::fromUtf8(Settings::Manager::getString(key, mCurrentCategory->second.getKey()).c_str())); - CSMPrefs::ColourSetting* setting - = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -557,28 +544,26 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( // Setup with actual data QKeySequence sequence; - getShortcutManager().convertFromString( - Settings::Manager::getString(key, mCurrentCategory->second.getKey()), sequence); + getShortcutManager().convertFromString(mIndex->get(mCurrentCategory->second.getKey(), key), sequence); getShortcutManager().setSequence(key, sequence); - CSMPrefs::ShortcutSetting* setting = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, key, label); + CSMPrefs::ShortcutSetting* setting + = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); return *setting; } CSMPrefs::StringSetting& CSMPrefs::State::declareString( - const std::string& key, const QString& label, std::string default_) + const std::string& key, const QString& label, const std::string& default_) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); setDefault(key, default_); - default_ = Settings::Manager::getString(key, mCurrentCategory->second.getKey()); - CSMPrefs::StringSetting* setting - = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, default_); + = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -596,11 +581,11 @@ CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& k // Setup with actual data int modifier; - getShortcutManager().convertFromString( - Settings::Manager::getString(key, mCurrentCategory->second.getKey()), modifier); + getShortcutManager().convertFromString(mIndex->get(mCurrentCategory->second.getKey(), key), modifier); getShortcutManager().setModifier(key, modifier); - CSMPrefs::ModifierSetting* setting = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, key, label); + CSMPrefs::ModifierSetting* setting + = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); return *setting; @@ -611,7 +596,8 @@ void CSMPrefs::State::declareSubcategory(const QString& label) if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - mCurrentCategory->second.addSetting(new CSMPrefs::Subcategory(&mCurrentCategory->second, &mMutex, label)); + mCurrentCategory->second.addSubcategory( + new CSMPrefs::Subcategory(&mCurrentCategory->second, &mMutex, label, *mIndex)); } void CSMPrefs::State::setDefault(const std::string& key, const std::string& default_) @@ -693,27 +679,13 @@ CSMPrefs::State& CSMPrefs::State::get() void CSMPrefs::State::resetCategory(const std::string& category) { - for (Settings::CategorySettingValueMap::iterator i = Settings::Manager::mUserSettings.begin(); - i != Settings::Manager::mUserSettings.end(); ++i) - { - // if the category matches - if (i->first.first == category) - { - // mark the setting as changed - Settings::Manager::mChangedSettings.insert(std::make_pair(i->first.first, i->first.second)); - // reset the value to the default - i->second = Settings::Manager::mDefaultSettings[i->first]; - } - } - Collection::iterator container = mCategories.find(category); if (container != mCategories.end()) { - Category settings = container->second; - for (Category::Iterator i = settings.begin(); i != settings.end(); ++i) + for (Setting* setting : container->second) { - (*i)->updateWidget(); - update(**i); + setting->reset(); + update(*setting); } } } diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index e398f06e4a..97d657ddaf 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -74,7 +74,7 @@ namespace CSMPrefs ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); - StringSetting& declareString(const std::string& key, const QString& label, std::string default_); + StringSetting& declareString(const std::string& key, const QString& label, const std::string& default_); ModifierSetting& declareModifier(const std::string& key, const QString& label, int modifier_); diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index 0cba2d047d..d70324ba56 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -12,9 +12,8 @@ #include "state.hpp" CSMPrefs::StringSetting::StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, std::string_view default_) - : Setting(parent, mutex, key, label) - , mDefault(default_) + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { } @@ -27,7 +26,7 @@ CSMPrefs::StringSetting& CSMPrefs::StringSetting::setTooltip(const std::string& CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) { - mWidget = new QLineEdit(QString::fromUtf8(mDefault.c_str()), parent); + mWidget = new QLineEdit(QString::fromStdString(getValue()), parent); if (!mTooltip.empty()) { @@ -43,17 +42,11 @@ CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) void CSMPrefs::StringSetting::updateWidget() { if (mWidget) - { - mWidget->setText(QString::fromStdString(Settings::Manager::getString(getKey(), getParent()->getKey()))); - } + mWidget->setText(QString::fromStdString(getValue())); } void CSMPrefs::StringSetting::textChanged(const QString& text) { - { - QMutexLocker lock(getMutex()); - Settings::Manager::setString(getKey(), getParent()->getKey(), text.toStdString()); - } - + setValue(text.toStdString()); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/stringsetting.hpp b/apps/opencs/model/prefs/stringsetting.hpp index 5c03a6ea12..25e1a362ff 100644 --- a/apps/opencs/model/prefs/stringsetting.hpp +++ b/apps/opencs/model/prefs/stringsetting.hpp @@ -14,17 +14,17 @@ class QWidget; namespace CSMPrefs { class Category; - class StringSetting : public Setting + + class StringSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - std::string mDefault; QLineEdit* mWidget; public: - StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, std::string_view default_); + explicit StringSetting( + Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); StringSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/subcategory.cpp b/apps/opencs/model/prefs/subcategory.cpp index cca558407c..ac5b78ee2c 100644 --- a/apps/opencs/model/prefs/subcategory.cpp +++ b/apps/opencs/model/prefs/subcategory.cpp @@ -6,8 +6,8 @@ namespace CSMPrefs { class Category; - Subcategory::Subcategory(Category* parent, QMutex* mutex, const QString& label) - : Setting(parent, mutex, "", label) + Subcategory::Subcategory(Category* parent, QMutex* mutex, const QString& label, Settings::Index& index) + : Setting(parent, mutex, "", label, index) { } diff --git a/apps/opencs/model/prefs/subcategory.hpp b/apps/opencs/model/prefs/subcategory.hpp index 4f62c1743c..4c661ad0fa 100644 --- a/apps/opencs/model/prefs/subcategory.hpp +++ b/apps/opencs/model/prefs/subcategory.hpp @@ -15,11 +15,13 @@ namespace CSMPrefs Q_OBJECT public: - explicit Subcategory(Category* parent, QMutex* mutex, const QString& label); + explicit Subcategory(Category* parent, QMutex* mutex, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; void updateWidget() override {} + + void reset() override {} }; } diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index e68d3e833f..0e3725bbb7 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -132,11 +133,11 @@ namespace CSMWorld bool beast = mRaceData ? mRaceData->isBeast() : false; if (beast) - return Settings::Manager::getString("baseanimkna", "Models"); + return CSMPrefs::get()["Models"]["baseanimkna"].toString(); else if (mFemale) - return Settings::Manager::getString("baseanimfemale", "Models"); + return CSMPrefs::get()["Models"]["baseanimfemale"].toString(); else - return Settings::Manager::getString("baseanim", "Models"); + return CSMPrefs::get()["Models"]["baseanim"].toString(); } ESM::RefId ActorAdapter::ActorData::getPart(ESM::PartReferenceType index) const From e1a68d8cf598e25c5348a7b91b05dbe7ea854b69 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 08:14:16 +0100 Subject: [PATCH 022/231] Ignore absent default setting value --- apps/opencs/model/prefs/state.cpp | 35 +------------------ apps/opencs/model/prefs/state.hpp | 2 -- .../openmw_test_suite/settings/testvalues.cpp | 34 ++++++++++++++++++ components/settings/settings.hpp | 9 +++++ components/settings/settingvalue.hpp | 4 +-- 5 files changed, 46 insertions(+), 38 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index d55913fb3c..364f7b3320 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -463,8 +463,6 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, std::to_string(default_)); - CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -477,10 +475,6 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - std::ostringstream stream; - stream << default_; - setDefault(key, stream.str()); - CSMPrefs::DoubleSetting* setting = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); @@ -494,8 +488,6 @@ CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, cons if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, default_ ? "true" : "false"); - CSMPrefs::BoolSetting* setting = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -508,8 +500,6 @@ CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(const std::string& key, cons if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, default_.mValue); - CSMPrefs::EnumSetting* setting = new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); mCurrentCategory->second.addSetting(setting); @@ -522,8 +512,6 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(const std::string& key, if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, default_.name().toUtf8().data()); - CSMPrefs::ColourSetting* setting = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); @@ -538,9 +526,6 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - std::string seqStr = getShortcutManager().convertToString(default_); - setDefault(key, seqStr); - // Setup with actual data QKeySequence sequence; @@ -560,8 +545,6 @@ CSMPrefs::StringSetting& CSMPrefs::State::declareString( if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - setDefault(key, default_); - CSMPrefs::StringSetting* setting = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); @@ -575,9 +558,6 @@ CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& k if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - std::string modStr = getShortcutManager().convertToString(default_); - setDefault(key, modStr); - // Setup with actual data int modifier; @@ -600,33 +580,20 @@ void CSMPrefs::State::declareSubcategory(const QString& label) new CSMPrefs::Subcategory(&mCurrentCategory->second, &mMutex, label, *mIndex)); } -void CSMPrefs::State::setDefault(const std::string& key, const std::string& default_) -{ - Settings::CategorySetting fullKey(mCurrentCategory->second.getKey(), key); - - Settings::CategorySettingValueMap::iterator iter = Settings::Manager::mDefaultSettings.find(fullKey); - - if (iter == Settings::Manager::mDefaultSettings.end()) - Settings::Manager::mDefaultSettings.insert(std::make_pair(fullKey, default_)); -} - CSMPrefs::State::State(const Files::ConfigurationManager& configurationManager) : mConfigFile("openmw-cs.cfg") , mDefaultConfigFile("defaults-cs.bin") , mConfigurationManager(configurationManager) , mCurrentCategory(mCategories.end()) , mIndex(std::make_unique()) + , mValues(std::make_unique(*mIndex)) { if (sThis) throw std::logic_error("An instance of CSMPRefs::State already exists"); sThis = this; - Values values(*mIndex); - declare(); - - mValues = std::make_unique(std::move(values)); } CSMPrefs::State::~State() diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 97d657ddaf..018a81a8d9 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -80,8 +80,6 @@ namespace CSMPrefs void declareSubcategory(const QString& label); - void setDefault(const std::string& key, const std::string& default_); - public: State(const Files::ConfigurationManager& configurationManager); diff --git a/apps/openmw_test_suite/settings/testvalues.cpp b/apps/openmw_test_suite/settings/testvalues.cpp index 81af308795..236417b559 100644 --- a/apps/openmw_test_suite/settings/testvalues.cpp +++ b/apps/openmw_test_suite/settings/testvalues.cpp @@ -59,6 +59,33 @@ namespace Settings EXPECT_EQ(values.mCamera.mFieldOfView.get(), 1); } + TEST_F(SettingsValuesTest, constructorWithDefaultShouldDoLookup) + { + Manager::mUserSettings[std::make_pair("category", "value")] = "13"; + Index index; + SettingValue value{ index, "category", "value", 42 }; + EXPECT_EQ(value.get(), 13); + value.reset(); + EXPECT_EQ(value.get(), 42); + } + + TEST_F(SettingsValuesTest, constructorWithDefaultShouldSanitize) + { + Manager::mUserSettings[std::make_pair("category", "value")] = "2"; + Index index; + SettingValue value{ index, "category", "value", -1, Settings::makeClampSanitizerInt(0, 1) }; + EXPECT_EQ(value.get(), 1); + value.reset(); + EXPECT_EQ(value.get(), 0); + } + + TEST_F(SettingsValuesTest, constructorWithDefaultShouldFallbackToDefault) + { + Index index; + const SettingValue value{ index, "category", "value", 42 }; + EXPECT_EQ(value.get(), 42); + } + TEST_F(SettingsValuesTest, moveConstructorShouldSetDefaults) { Index index; @@ -79,6 +106,13 @@ namespace Settings EXPECT_EQ(values.mCamera.mFieldOfView.get(), 1); } + TEST_F(SettingsValuesTest, moveConstructorShouldThrowOnMissingSetting) + { + Index index; + SettingValue defaultValue{ index, "category", "value", 42 }; + EXPECT_THROW([&] { SettingValue value(std::move(defaultValue)); }(), std::runtime_error); + } + TEST_F(SettingsValuesTest, findShouldThrowExceptionOnTypeMismatch) { Index index; diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index c061755bc1..bcdcbb16d1 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -77,6 +77,15 @@ namespace Settings static osg::Vec2f getVector2(std::string_view setting, std::string_view category); static osg::Vec3f getVector3(std::string_view setting, std::string_view category); + template + static T getOrDefault(std::string_view setting, std::string_view category, const T& defaultValue) + { + const auto key = std::make_pair(category, setting); + if (!mUserSettings.contains(key) && !mDefaultSettings.contains(key)) + return defaultValue; + return get(setting, category); + } + template static T get(std::string_view setting, std::string_view category) { diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 392e5b646f..8183e8c1ac 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -339,8 +339,8 @@ namespace Settings std::unique_ptr>&& sanitizer = nullptr) : BaseSettingValue(getSettingValueType(), category, name, index) , mSanitizer(std::move(sanitizer)) - , mDefaultValue(defaultValue) - , mValue(defaultValue) + , mDefaultValue(sanitize(defaultValue)) + , mValue(sanitize(Settings::Manager::getOrDefault(mName, mCategory, mDefaultValue))) { } From 6e7661ca879661bbabc6333fd376e4b1cb49f621 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Dec 2023 17:44:49 +0300 Subject: [PATCH 023/231] BulletNifLoader: Handle only the first child of NiSwitchNode and NiFltAnimationNode To prevent duplicated collisions in general cases when the node states are similar or only one child is ever active. For NiLODNode this is definitely not going to work --- components/nifbullet/bulletnifloader.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 96dff80004..2c7dd1b1c4 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -250,11 +250,16 @@ namespace NifBullet const Nif::Parent currentParent{ *ninode, parent }; for (const auto& child : ninode->mChildren) { - if (child.empty()) - continue; - - assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end()); - handleNode(child.get(), ¤tParent, args); + if (!child.empty()) + { + assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end()); + handleNode(child.get(), ¤tParent, args); + } + // For NiSwitchNodes and NiFltAnimationNodes, only use the first child + // TODO: must synchronize with the rendering scene graph somehow + // Doing this for NiLODNodes is unsafe (the first level might not be the closest) + if (node.recType == Nif::RC_NiSwitchNode || node.recType == Nif::RC_NiFltAnimationNode) + break; } } } From b93291840ee3c0996576ffabef29ff550a056a87 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 3 Dec 2023 18:20:05 +0300 Subject: [PATCH 024/231] BulletNifLoader: Handle NiSkinPartition Add NiSkinPartition recovery helper method --- apps/openmw_test_suite/nif/node.hpp | 1 + components/nif/data.cpp | 11 +++ components/nif/data.hpp | 2 + components/nif/node.cpp | 104 +++++++++++++++++------ components/nifbullet/bulletnifloader.cpp | 1 - components/nifosg/nifloader.cpp | 12 +-- 6 files changed, 91 insertions(+), 40 deletions(-) diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index 76cc6ac687..4e21698501 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -53,6 +53,7 @@ namespace Nif::Testing { value.mData = NiSkinDataPtr(nullptr); value.mRoot = NiAVObjectPtr(nullptr); + value.mPartitions = NiSkinPartitionPtr(nullptr); } inline void init(NiTimeController& value) diff --git a/components/nif/data.cpp b/components/nif/data.cpp index e653959bbc..fa66435aee 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -290,6 +290,17 @@ namespace Nif } } + const Nif::NiSkinPartition* NiSkinInstance::getPartitions() const + { + const Nif::NiSkinPartition* partitions = nullptr; + if (!mPartitions.empty()) + partitions = mPartitions.getPtr(); + else if (!mData.empty() && !mData->mPartitions.empty()) + partitions = mData->mPartitions.getPtr(); + + return partitions; + } + void BSDismemberSkinInstance::read(NIFStream* nif) { NiSkinInstance::read(nif); diff --git a/components/nif/data.hpp b/components/nif/data.hpp index 75c18d657a..c9daeef4d4 100644 --- a/components/nif/data.hpp +++ b/components/nif/data.hpp @@ -208,6 +208,8 @@ namespace Nif void read(NIFStream* nif) override; void post(Reader& nif) override; + + const Nif::NiSkinPartition* getPartitions() const; }; struct BSDismemberSkinInstance : public NiSkinInstance diff --git a/components/nif/node.cpp b/components/nif/node.cpp index 01c0e1597d..b91f143d00 100644 --- a/components/nif/node.cpp +++ b/components/nif/node.cpp @@ -27,6 +27,37 @@ namespace mesh.preallocateIndices(static_cast(data.mNumTriangles) * 3); } + void trianglesToBtTriangleMesh(btTriangleMesh& mesh, const std::vector& triangles) + { + for (std::size_t i = 0; i < triangles.size(); i += 3) + mesh.addTriangleIndices(triangles[i + 0], triangles[i + 1], triangles[i + 2]); + } + + void stripsToBtTriangleMesh(btTriangleMesh& mesh, const std::vector>& strips) + { + for (const auto& strip : strips) + { + if (strip.size() < 3) + continue; + + unsigned short a; + unsigned short b = strip[0]; + unsigned short c = strip[1]; + for (size_t i = 2; i < strip.size(); i++) + { + a = b; + b = c; + c = strip[i]; + if (a == b || b == c || a == c) + continue; + if (i % 2 == 0) + mesh.addTriangleIndices(a, b, c); + else + mesh.addTriangleIndices(a, c, b); + } + } + } + } namespace Nif @@ -243,15 +274,33 @@ namespace Nif if (mData.empty() || mData->mVertices.empty()) return nullptr; + std::vector*> triangleLists; + std::vector>*> stripsLists; auto data = static_cast(mData.getPtr()); - if (data->mNumTriangles == 0 || data->mTriangles.empty()) - return nullptr; + const Nif::NiSkinPartition* partitions = nullptr; + if (!mSkin.empty()) + partitions = mSkin->getPartitions(); + if (partitions) + { + triangleLists.reserve(partitions->mPartitions.size()); + stripsLists.reserve(partitions->mPartitions.size()); + for (auto& partition : partitions->mPartitions) + { + triangleLists.push_back(&partition.mTrueTriangles); + stripsLists.push_back(&partition.mTrueStrips); + } + } + else if (data->mNumTriangles != 0) + triangleLists.push_back(&data->mTriangles); + + // This makes a perhaps dangerous assumption that NiSkinPartition will never have more than 65536 triangles. auto mesh = std::make_unique(); triBasedGeomToBtTriangleMesh(*mesh, *data); - const std::vector& triangles = data->mTriangles; - for (std::size_t i = 0; i < triangles.size(); i += 3) - mesh->addTriangleIndices(triangles[i + 0], triangles[i + 1], triangles[i + 2]); + for (const auto triangles : triangleLists) + trianglesToBtTriangleMesh(*mesh, *triangles); + for (const auto strips : stripsLists) + stripsToBtTriangleMesh(*mesh, *strips); if (mesh->getNumTriangles() == 0) return nullptr; @@ -267,33 +316,32 @@ namespace Nif if (mData.empty() || mData->mVertices.empty()) return nullptr; + std::vector*> triangleLists; + std::vector>*> stripsLists; auto data = static_cast(mData.getPtr()); - if (data->mNumTriangles == 0 || data->mStrips.empty()) - return nullptr; + const Nif::NiSkinPartition* partitions = nullptr; + if (!mSkin.empty()) + partitions = mSkin->getPartitions(); + + if (partitions) + { + triangleLists.reserve(partitions->mPartitions.size()); + stripsLists.reserve(partitions->mPartitions.size()); + for (auto& partition : partitions->mPartitions) + { + triangleLists.push_back(&partition.mTrueTriangles); + stripsLists.push_back(&partition.mTrueStrips); + } + } + else if (data->mNumTriangles != 0) + stripsLists.push_back(&data->mStrips); auto mesh = std::make_unique(); triBasedGeomToBtTriangleMesh(*mesh, *data); - for (const std::vector& strip : data->mStrips) - { - if (strip.size() < 3) - continue; - - unsigned short a; - unsigned short b = strip[0]; - unsigned short c = strip[1]; - for (size_t i = 2; i < strip.size(); i++) - { - a = b; - b = c; - c = strip[i]; - if (a == b || b == c || a == c) - continue; - if (i % 2 == 0) - mesh->addTriangleIndices(a, b, c); - else - mesh->addTriangleIndices(a, c, b); - } - } + for (const auto triangles : triangleLists) + trianglesToBtTriangleMesh(*mesh, *triangles); + for (const auto strips : stripsLists) + stripsToBtTriangleMesh(*mesh, *strips); if (mesh->getNumTriangles() == 0) return nullptr; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 2c7dd1b1c4..0bba9ee1a2 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -277,7 +277,6 @@ namespace NifBullet if (!niGeometry.mSkin.empty()) args.mAnimated = false; - // TODO: handle NiSkinPartition std::unique_ptr childShape = niGeometry.getCollisionShape(); if (childShape == nullptr) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 436f2e1d34..fba831d2a2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1381,17 +1381,7 @@ namespace NifOsg if (!niGeometry->mSkin.empty()) { const Nif::NiSkinInstance* skin = niGeometry->mSkin.getPtr(); - const Nif::NiSkinData* data = nullptr; - const Nif::NiSkinPartition* partitions = nullptr; - if (!skin->mData.empty()) - { - data = skin->mData.getPtr(); - if (!data->mPartitions.empty()) - partitions = data->mPartitions.getPtr(); - } - if (!partitions && !skin->mPartitions.empty()) - partitions = skin->mPartitions.getPtr(); - + const Nif::NiSkinPartition* partitions = skin->getPartitions(); hasPartitions = partitions != nullptr; if (hasPartitions) { From 8cf99822edd15654e77236e5081725cbf55fd107 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 2 Nov 2023 17:31:13 +0100 Subject: [PATCH 025/231] Add a death event to the Lua API --- apps/openmw/mwbase/luamanager.hpp | 1 + apps/openmw/mwlua/engineevents.cpp | 10 ++++++++++ apps/openmw/mwlua/engineevents.hpp | 7 ++++++- apps/openmw/mwlua/localscripts.cpp | 2 +- apps/openmw/mwlua/localscripts.hpp | 2 ++ apps/openmw/mwlua/luamanagerimp.hpp | 4 ++++ apps/openmw/mwmechanics/actors.cpp | 2 ++ .../source/reference/lua-scripting/engine_handlers.rst | 2 ++ 8 files changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index 06a68efe4a..e4b16ff725 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -53,6 +53,7 @@ namespace MWBase virtual void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; virtual void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) = 0; virtual void exteriorCreated(MWWorld::CellStore& cell) = 0; + virtual void actorDied(const MWWorld::Ptr& actor) = 0; virtual void questUpdated(const ESM::RefId& questId, int stage) = 0; // `arg` is either forwarded from MWGui::pushGuiMode or empty diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 0fbb13f1cf..7250acc857 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -86,6 +86,16 @@ namespace MWLua void operator()(const OnNewExterior& event) const { mGlobalScripts.onNewExterior(GCell{ &event.mCell }); } + void operator()(const OnDeath& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + if (!actor.isEmpty()) + { + if (auto* scripts = getLocalScripts(actor)) + scripts->onDeath(); + } + } + private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index 7c706edcd0..04a5ab4ebd 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -51,7 +51,12 @@ namespace MWLua { MWWorld::CellStore& mCell; }; - using Event = std::variant; + struct OnDeath + { + ESM::RefNum mActor; + }; + using Event = std::variant; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 8cf383e985..8dc8358aa8 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -170,7 +170,7 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers }); + &mOnTeleportedHandlers, &mOnDeathHandlers }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index b87b628a89..05b34cbb96 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -71,6 +71,7 @@ namespace MWLua void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); } void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); } void onTeleported() { callEngineHandlers(mOnTeleportedHandlers); } + void onDeath() { callEngineHandlers(mOnDeathHandlers); } void applyStatsCache(); @@ -83,6 +84,7 @@ namespace MWLua EngineHandlerList mOnConsumeHandlers{ "onConsume" }; EngineHandlerList mOnActivatedHandlers{ "onActivated" }; EngineHandlerList mOnTeleportedHandlers{ "onTeleported" }; + EngineHandlerList mOnDeathHandlers{ "onDeath" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 404820cc6b..ed0b4e9257 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -82,6 +82,10 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); } + void actorDied(const MWWorld::Ptr& actor) override + { + mEngineEvents.addToQueue(EngineEvents::OnDeath{ getId(actor) }); + } void objectTeleported(const MWWorld::Ptr& ptr) override; void questUpdated(const ESM::RefId& questId, int stage) override; void uiModeChanged(const MWWorld::Ptr& arg) override; diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 3e7b075e62..73bd331de2 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1730,6 +1730,8 @@ namespace MWMechanics actor.getClass().getCreatureStats(actor).notifyDied(); ++mDeathCount[actor.getCellRef().getRefId()]; + + MWBase::Environment::get().getLuaManager()->actorDied(actor); } void Actors::resurrect(const MWWorld::Ptr& ptr) const diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 1ffa1820f3..b6e90a6bda 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -79,6 +79,8 @@ Engine handler is a function defined by a script, that can be called by the engi - | Called on an actor when they consume an item (e.g. a potion). | Similarly to onActivated, the item has already been removed | from the actor's inventory, and the count was set to zero. + * - onDeath() + - Called when the actor dies. Note that actors that start out dead are ignored. **Only for local scripts attached to a player** From ad68b7e18bf2f5a9b8f880fc9ecc658c498577d8 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 4 Dec 2023 17:32:38 +0100 Subject: [PATCH 026/231] Turn onDeath the engine handler into OnDeath the regular event --- apps/openmw/mwlua/engineevents.cpp | 10 ---------- apps/openmw/mwlua/engineevents.hpp | 7 +------ apps/openmw/mwlua/localscripts.cpp | 2 +- apps/openmw/mwlua/localscripts.hpp | 2 -- apps/openmw/mwlua/luamanagerimp.cpp | 7 +++++++ apps/openmw/mwlua/luamanagerimp.hpp | 5 +---- .../reference/lua-scripting/engine_handlers.rst | 2 -- docs/source/reference/lua-scripting/events.rst | 12 ++++++++++++ 8 files changed, 22 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp index 7250acc857..0fbb13f1cf 100644 --- a/apps/openmw/mwlua/engineevents.cpp +++ b/apps/openmw/mwlua/engineevents.cpp @@ -86,16 +86,6 @@ namespace MWLua void operator()(const OnNewExterior& event) const { mGlobalScripts.onNewExterior(GCell{ &event.mCell }); } - void operator()(const OnDeath& event) const - { - MWWorld::Ptr actor = getPtr(event.mActor); - if (!actor.isEmpty()) - { - if (auto* scripts = getLocalScripts(actor)) - scripts->onDeath(); - } - } - private: MWWorld::Ptr getPtr(ESM::RefNum id) const { diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp index 04a5ab4ebd..7c706edcd0 100644 --- a/apps/openmw/mwlua/engineevents.hpp +++ b/apps/openmw/mwlua/engineevents.hpp @@ -51,12 +51,7 @@ namespace MWLua { MWWorld::CellStore& mCell; }; - struct OnDeath - { - ESM::RefNum mActor; - }; - using Event = std::variant; + using Event = std::variant; void clear() { mQueue.clear(); } void addToQueue(Event e) { mQueue.push_back(std::move(e)); } diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 8dc8358aa8..8cf383e985 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -170,7 +170,7 @@ namespace MWLua { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); registerEngineHandlers({ &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers, - &mOnTeleportedHandlers, &mOnDeathHandlers }); + &mOnTeleportedHandlers }); } void LocalScripts::setActive(bool active) diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index 05b34cbb96..b87b628a89 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -71,7 +71,6 @@ namespace MWLua void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); } void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); } void onTeleported() { callEngineHandlers(mOnTeleportedHandlers); } - void onDeath() { callEngineHandlers(mOnDeathHandlers); } void applyStatsCache(); @@ -84,7 +83,6 @@ namespace MWLua EngineHandlerList mOnConsumeHandlers{ "onConsume" }; EngineHandlerList mOnActivatedHandlers{ "onActivated" }; EngineHandlerList mOnTeleportedHandlers{ "onTeleported" }; - EngineHandlerList mOnDeathHandlers{ "onDeath" }; }; } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 48b0c15381..66e8ff7538 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -344,6 +344,13 @@ namespace MWLua playerScripts->uiModeChanged(argId, false); } + void LuaManager::actorDied(const MWWorld::Ptr& actor) + { + if (actor.isEmpty()) + return; + mLuaEvents.addLocalEvent({ getId(actor), "OnDeath", {} }); + } + void LuaManager::useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) { MWBase::Environment::get().getWorldModel()->registerPtr(object); diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index ed0b4e9257..ae16689562 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -82,13 +82,10 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); } - void actorDied(const MWWorld::Ptr& actor) override - { - mEngineEvents.addToQueue(EngineEvents::OnDeath{ getId(actor) }); - } void objectTeleported(const MWWorld::Ptr& ptr) override; void questUpdated(const ESM::RefId& questId, int stage) override; void uiModeChanged(const MWWorld::Ptr& arg) override; + void actorDied(const MWWorld::Ptr& actor) override; MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index b6e90a6bda..1ffa1820f3 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -79,8 +79,6 @@ Engine handler is a function defined by a script, that can be called by the engi - | Called on an actor when they consume an item (e.g. a potion). | Similarly to onActivated, the item has already been removed | from the actor's inventory, and the count was set to zero. - * - onDeath() - - Called when the actor dies. Note that actors that start out dead are ignored. **Only for local scripts attached to a player** diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 7f0a764b86..7a08ef4d6e 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -6,6 +6,18 @@ Built-in events Actor events ------------ +**OnDeath** + +This event is sent to an actor's local script when that actor dies. + +.. code-block:: Lua + + eventHandlers = { + OnDeath = function() + print('Alas, ye hardly knew me!') + end + } + **StartAIPackage, RemoveAIPackages** Any script can send to any actor (except player, for player will be ignored) events ``StartAIPackage`` and ``RemoveAIPackages``. From efb819b9d2c3de689a6e183b2697286552c2e0fa Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 4 Dec 2023 17:50:18 +0100 Subject: [PATCH 027/231] Rename to Died --- apps/openmw/mwlua/luamanagerimp.cpp | 2 +- docs/source/reference/lua-scripting/events.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 66e8ff7538..d34518d558 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -348,7 +348,7 @@ namespace MWLua { if (actor.isEmpty()) return; - mLuaEvents.addLocalEvent({ getId(actor), "OnDeath", {} }); + mLuaEvents.addLocalEvent({ getId(actor), "Died", {} }); } void LuaManager::useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) diff --git a/docs/source/reference/lua-scripting/events.rst b/docs/source/reference/lua-scripting/events.rst index 7a08ef4d6e..282e3d1173 100644 --- a/docs/source/reference/lua-scripting/events.rst +++ b/docs/source/reference/lua-scripting/events.rst @@ -6,14 +6,14 @@ Built-in events Actor events ------------ -**OnDeath** +**Died** This event is sent to an actor's local script when that actor dies. .. code-block:: Lua eventHandlers = { - OnDeath = function() + Died = function() print('Alas, ye hardly knew me!') end } From e043ed935eae8cb65addd01b6ce6094df1b05369 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Tue, 5 Dec 2023 12:03:34 +0000 Subject: [PATCH 028/231] Add some missing changelog entries --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ad2bae6c..98be6c60ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,8 @@ Bug #7298: Water ripples from projectiles sometimes are not spawned Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives + Bug #7354: Disabling post processing in-game causes a crash + Bug #7364: Post processing is not reflected in savegame previews Bug #7380: NiZBufferProperty issue Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies @@ -127,12 +129,14 @@ Feature #7477: NegativeLight Magic Effect flag Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field Feature #7546: Start the game on Fredas + Feature #7554: Controller binding for tab for menu navigation Feature #7568: Uninterruptable scripted music Feature #7608: Make the missing dependencies warning when loading a savegame more helpful Feature #7618: Show the player character's health in the save details Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb Feature #7652: Sort inactive post processing shaders list properly + Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char From f6a6c278dd8c61af8e4f437de20c1cb9b4620571 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 5 Dec 2023 14:13:35 +0000 Subject: [PATCH 029/231] More cleanup of scripted animations --- CHANGELOG.md | 3 + apps/openmw/mwmechanics/character.cpp | 166 +++++++++++++------ apps/openmw/mwmechanics/character.hpp | 5 + apps/openmw/mwmechanics/weapontype.cpp | 16 ++ apps/openmw/mwmechanics/weapontype.hpp | 5 + apps/openmw/mwrender/animation.cpp | 34 +--- apps/openmw/mwrender/animation.hpp | 10 +- apps/openmw/mwrender/npcanimation.cpp | 17 +- apps/openmw/mwscript/animationextensions.cpp | 2 +- components/esm3/loadweap.hpp | 4 +- 10 files changed, 175 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ad2bae6c..fca7982e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward + Bug #4743: PlayGroup doesn't play non-looping animations correctly Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation + Bug #5066: Quirks with starting and stopping scripted animations Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X @@ -91,6 +93,7 @@ Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation Bug #7637: Actors can sometimes move while playing scripted animations Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat + Bug #7641: loopgroup loops the animation one time too many for actors Bug #7642: Items in repair and recharge menus aren't sorted alphabetically Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index d9166aa683..77bc51423e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -20,6 +20,7 @@ #include "character.hpp" #include +#include #include #include @@ -1189,7 +1190,7 @@ namespace MWMechanics if (!animPlaying) { int mask = MWRender::Animation::BlendMask_Torso | MWRender::Animation::BlendMask_RightArm; - mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul); + mAnimation->play("idlestorm", Priority_Storm, mask, true, 1.0f, "start", "stop", 0.0f, ~0ul, true); } else { @@ -1246,8 +1247,47 @@ namespace MWMechanics } } + bool CharacterController::isLoopingAnimation(std::string_view group) const + { + // In Morrowind, a some animation groups are always considered looping, regardless + // of loop start/stop keys. + // To be match vanilla behavior we probably only need to check this list, but we don't + // want to prevent modded animations with custom group names from looping either. + static const std::unordered_set loopingAnimations = { "walkforward", "walkback", "walkleft", + "walkright", "swimwalkforward", "swimwalkback", "swimwalkleft", "swimwalkright", "runforward", "runback", + "runleft", "runright", "swimrunforward", "swimrunback", "swimrunleft", "swimrunright", "sneakforward", + "sneakback", "sneakleft", "sneakright", "turnleft", "turnright", "swimturnleft", "swimturnright", + "spellturnleft", "spellturnright", "torch", "idle", "idle2", "idle3", "idle4", "idle5", "idle6", "idle7", + "idle8", "idle9", "idlesneak", "idlestorm", "idleswim", "jump", "inventoryhandtohand", + "inventoryweapononehand", "inventoryweapontwohand", "inventoryweapontwowide" }; + static const std::vector shortGroups = getAllWeaponTypeShortGroups(); + + if (mAnimation && mAnimation->getTextKeyTime(std::string(group) + ": loop start") >= 0) + return true; + + // Most looping animations have variants for each weapon type shortgroup. + // Just remove the shortgroup instead of enumerating all of the possible animation groupnames. + // Make sure we pick the longest shortgroup so e.g. "bow" doesn't get picked over "crossbow" + // when the shortgroup is crossbow. + std::size_t suffixLength = 0; + for (std::string_view suffix : shortGroups) + { + if (suffix.length() > suffixLength && group.ends_with(suffix)) + { + suffixLength = suffix.length(); + } + } + group.remove_suffix(suffixLength); + + return loopingAnimations.count(group) > 0; + } + bool CharacterController::updateWeaponState() { + // If the current animation is scripted, we can't do anything here. + if (isScriptedAnimPlaying()) + return false; + const auto world = MWBase::Environment::get().getWorld(); auto& prng = world->getPrng(); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); @@ -1481,10 +1521,6 @@ namespace MWMechanics sndMgr->stopSound3D(mPtr, wolfRun); } - // Combat for actors with scripted animations obviously will be buggy - if (isScriptedAnimPlaying()) - return forcestateupdate; - float complete = 0.f; bool animPlaying = false; ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass; @@ -1857,33 +1893,58 @@ namespace MWMechanics if (!mAnimation->isPlaying(mAnimQueue.front().mGroup)) { - // Remove the finished animation, unless it's a scripted animation that was interrupted by e.g. a rebuild of - // the animation object. - if (mAnimQueue.size() > 1 || !mAnimQueue.front().mScripted || mAnimQueue.front().mLoopCount == 0) + // Playing animations through mwscript is weird. If an animation is + // a looping animation (idle or other cyclical animations), then they + // will end as expected. However, if they are non-looping animations, they + // will stick around forever or until another animation appears in the queue. + bool shouldPlayOrRestart = mAnimQueue.size() > 1; + if (shouldPlayOrRestart || !mAnimQueue.front().mScripted + || (mAnimQueue.front().mLoopCount == 0 && mAnimQueue.front().mLooping)) { + mAnimation->setPlayScriptedOnly(false); mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); + shouldPlayOrRestart = true; } + else + // A non-looping animation will stick around forever, so only restart if the animation + // actually was removed for some reason. + shouldPlayOrRestart = !mAnimation->getInfo(mAnimQueue.front().mGroup) + && mAnimation->hasAnimation(mAnimQueue.front().mGroup); - if (!mAnimQueue.empty()) + if (shouldPlayOrRestart) { // Move on to the remaining items of the queue - bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, - mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default, - MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, - mAnimQueue.front().mLoopCount, loopfallback); + playAnimQueue(); } } else { - mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup); + float complete; + size_t loopcount; + mAnimation->getInfo(mAnimQueue.front().mGroup, &complete, nullptr, &loopcount); + mAnimQueue.front().mLoopCount = loopcount; + mAnimQueue.front().mTime = complete; } if (!mAnimQueue.empty()) mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1); } + void CharacterController::playAnimQueue(bool loopStart) + { + if (!mAnimQueue.empty()) + { + clearStateAnimation(mCurrentIdle); + mIdleState = CharState_SpecialIdle; + auto priority = mAnimQueue.front().mScripted ? Priority_Scripted : Priority_Default; + mAnimation->setPlayScriptedOnly(mAnimQueue.front().mScripted); + mAnimation->play(mAnimQueue.front().mGroup, priority, MWRender::Animation::BlendMask_All, false, 1.0f, + (loopStart ? "loop start" : "start"), "stop", mAnimQueue.front().mTime, mAnimQueue.front().mLoopCount, + mAnimQueue.front().mLooping); + } + } + void CharacterController::update(float duration) { MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -2455,10 +2516,11 @@ namespace MWMechanics if (iter == mAnimQueue.begin()) { - anim.mLoopCount = mAnimation->getCurrentLoopCount(anim.mGroup); float complete; - mAnimation->getInfo(anim.mGroup, &complete, nullptr); + size_t loopcount; + mAnimation->getInfo(anim.mGroup, &complete, nullptr, &loopcount); anim.mTime = complete; + anim.mLoopCount = loopcount; } else { @@ -2484,26 +2546,20 @@ namespace MWMechanics entry.mGroup = iter->mGroup; entry.mLoopCount = iter->mLoopCount; entry.mScripted = true; + entry.mLooping = isLoopingAnimation(entry.mGroup); + entry.mTime = iter->mTime; + if (iter->mAbsolute) + { + float start = mAnimation->getTextKeyTime(iter->mGroup + ": start"); + float stop = mAnimation->getTextKeyTime(iter->mGroup + ": stop"); + float time = std::clamp(iter->mTime, start, stop); + entry.mTime = (time - start) / (stop - start); + } mAnimQueue.push_back(entry); } - const ESM::AnimationState::ScriptedAnimation& anim = state.mScriptedAnims.front(); - float complete = anim.mTime; - if (anim.mAbsolute) - { - float start = mAnimation->getTextKeyTime(anim.mGroup + ": start"); - float stop = mAnimation->getTextKeyTime(anim.mGroup + ": stop"); - float time = std::clamp(anim.mTime, start, stop); - complete = (time - start) / (stop - start); - } - - clearStateAnimation(mCurrentIdle); - mIdleState = CharState_SpecialIdle; - - bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(anim.mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", - "stop", complete, anim.mLoopCount, loopfallback); + playAnimQueue(); } } @@ -2516,13 +2572,14 @@ namespace MWMechanics if (isScriptedAnimPlaying() && !scripted) return true; - // If this animation is a looped animation (has a "loop start" key) that is already playing + bool looping = isLoopingAnimation(groupname); + + // If this animation is a looped animation that is already playing // and has not yet reached the end of the loop, allow it to continue animating with its existing loop count // and remove any other animations that were queued. // This emulates observed behavior from the original allows the script "OutsideBanner" to animate banners // correctly. - if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname - && mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop start") >= 0 + if (!mAnimQueue.empty() && mAnimQueue.front().mGroup == groupname && looping && mAnimation->isPlaying(groupname)) { float endOfLoop = mAnimation->getTextKeyTime(mAnimQueue.front().mGroup + ": loop stop"); @@ -2537,36 +2594,43 @@ namespace MWMechanics } } - count = std::max(count, 1); + // The loop count in vanilla is weird. + // if played with a count of 0, all objects play exactly once from start to stop. + // But if the count is x > 0, actors and non-actors behave differently. actors will loop + // exactly x times, while non-actors will loop x+1 instead. + if (mPtr.getClass().isActor()) + count--; + count = std::max(count, 0); AnimationQueueEntry entry; entry.mGroup = groupname; - entry.mLoopCount = count - 1; + entry.mLoopCount = count; + entry.mTime = 0.f; entry.mScripted = scripted; + entry.mLooping = looping; + + bool playImmediately = false; if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { clearAnimQueue(scripted); - clearStateAnimation(mCurrentIdle); - - mIdleState = CharState_SpecialIdle; - bool loopfallback = entry.mGroup.starts_with("idle"); - mAnimation->play(groupname, scripted && groupname != "idle" ? Priority_Scripted : Priority_Default, - MWRender::Animation::BlendMask_All, false, 1.0f, ((mode == 2) ? "loop start" : "start"), "stop", 0.0f, - count - 1, loopfallback); + playImmediately = true; } else { mAnimQueue.resize(1); } - // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing + // "PlayGroup idle" is a special case, used to stop and remove scripted animations playing if (groupname == "idle") entry.mScripted = false; mAnimQueue.push_back(entry); + if (playImmediately) + playAnimQueue(mode == 2); + return true; } @@ -2577,11 +2641,10 @@ namespace MWMechanics bool CharacterController::isScriptedAnimPlaying() const { + // If the front of the anim queue is scripted, morrowind treats it as if it's + // still playing even if it's actually done. if (!mAnimQueue.empty()) - { - const AnimationQueueEntry& first = mAnimQueue.front(); - return first.mScripted && isAnimPlaying(first.mGroup); - } + return mAnimQueue.front().mScripted; return false; } @@ -2611,6 +2674,7 @@ namespace MWMechanics if (clearScriptedAnims) { + mAnimation->setPlayScriptedOnly(false); mAnimQueue.clear(); return; } @@ -2645,6 +2709,8 @@ namespace MWMechanics playRandomDeath(); } + updateAnimQueue(); + mAnimation->runAnimation(0.f); } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 63491ec776..ee26b61a25 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -135,6 +135,8 @@ namespace MWMechanics { std::string mGroup; size_t mLoopCount; + float mTime; + bool mLooping; bool mScripted; }; typedef std::deque AnimationQueue; @@ -219,6 +221,7 @@ namespace MWMechanics bool isMovementAnimationControlled() const; void updateAnimQueue(); + void playAnimQueue(bool useLoopStart = false); void updateHeadTracking(float duration); @@ -245,6 +248,8 @@ namespace MWMechanics void prepareHit(); + bool isLoopingAnimation(std::string_view group) const; + public: CharacterController(const MWWorld::Ptr& ptr, MWRender::Animation* anim); virtual ~CharacterController(); diff --git a/apps/openmw/mwmechanics/weapontype.cpp b/apps/openmw/mwmechanics/weapontype.cpp index 9dd5842f58..8c51629803 100644 --- a/apps/openmw/mwmechanics/weapontype.cpp +++ b/apps/openmw/mwmechanics/weapontype.cpp @@ -8,6 +8,8 @@ #include +#include + namespace MWMechanics { template @@ -416,4 +418,18 @@ namespace MWMechanics return &Weapon::getValue(); } + + std::vector getAllWeaponTypeShortGroups() + { + // Go via a set to eliminate duplicates. + std::set shortGroupSet; + for (int type = ESM::Weapon::Type::First; type <= ESM::Weapon::Type::Last; type++) + { + std::string_view shortGroup = getWeaponType(type)->mShortGroup; + if (!shortGroup.empty()) + shortGroupSet.insert(shortGroup); + } + + return std::vector(shortGroupSet.begin(), shortGroupSet.end()); + } } diff --git a/apps/openmw/mwmechanics/weapontype.hpp b/apps/openmw/mwmechanics/weapontype.hpp index db7b3013f6..efe404d327 100644 --- a/apps/openmw/mwmechanics/weapontype.hpp +++ b/apps/openmw/mwmechanics/weapontype.hpp @@ -1,6 +1,9 @@ #ifndef GAME_MWMECHANICS_WEAPONTYPE_H #define GAME_MWMECHANICS_WEAPONTYPE_H +#include +#include + namespace ESM { struct WeaponType; @@ -21,6 +24,8 @@ namespace MWMechanics MWWorld::ContainerStoreIterator getActiveWeapon(const MWWorld::Ptr& actor, int* weaptype); const ESM::WeaponType* getWeaponType(const int weaponType); + + std::vector getAllWeaponTypeShortGroups(); } #endif diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index bac9dbb56c..5b0e1f82bd 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -529,6 +529,7 @@ namespace MWRender , mBodyPitchRadians(0.f) , mHasMagicEffects(false) , mAlpha(1.f) + , mPlayScriptedOnly(false) { for (size_t i = 0; i < sNumBlendMasks; i++) mAnimationTimePtr[i] = std::make_shared(); @@ -1020,7 +1021,7 @@ namespace MWRender return false; } - bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const + bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult, size_t* loopcount) const { AnimStateMap::const_iterator iter = mStates.find(groupname); if (iter == mStates.end()) @@ -1029,6 +1030,8 @@ namespace MWRender *complete = 0.0f; if (speedmult) *speedmult = 0.0f; + if (loopcount) + *loopcount = 0; return false; } @@ -1042,6 +1045,9 @@ namespace MWRender } if (speedmult) *speedmult = iter->second.mSpeedMult; + + if (loopcount) + *loopcount = iter->second.mLoopCount; return true; } @@ -1054,15 +1060,6 @@ namespace MWRender return iter->second.getTime(); } - size_t Animation::getCurrentLoopCount(const std::string& groupname) const - { - AnimStateMap::const_iterator iter = mStates.find(groupname); - if (iter == mStates.end()) - return 0; - - return iter->second.mLoopCount; - } - void Animation::disable(std::string_view groupname) { AnimStateMap::iterator iter = mStates.find(groupname); @@ -1141,23 +1138,12 @@ namespace MWRender osg::Vec3f Animation::runAnimation(float duration) { - // If we have scripted animations, play only them - bool hasScriptedAnims = false; - for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) - { - if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying) - { - hasScriptedAnims = true; - break; - } - } - osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); while (stateiter != mStates.end()) { AnimState& state = stateiter->second; - if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Scripted))) + if (mPlayScriptedOnly && !state.mPriority.contains(MWMechanics::Priority_Scripted)) { ++stateiter; continue; @@ -1263,10 +1249,6 @@ namespace MWRender osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1))); } - // Scripted animations should not cause movement - if (hasScriptedAnims) - return osg::Vec3f(0, 0, 0); - return movement; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 8615811cc3..24366889c4 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -292,6 +292,8 @@ namespace MWRender osg::ref_ptr mLightListCallback; + bool mPlayScriptedOnly; + const NodeMap& getNodeMap() const; /* Sets the appropriate animations on the bone groups based on priority. @@ -441,7 +443,8 @@ namespace MWRender * \param speedmult Stores the animation speed multiplier * \return True if the animation is active, false otherwise. */ - bool getInfo(std::string_view groupname, float* complete = nullptr, float* speedmult = nullptr) const; + bool getInfo(std::string_view groupname, float* complete = nullptr, float* speedmult = nullptr, + size_t* loopcount = nullptr) const; /// Get the absolute position in the animation track of the first text key with the given group. float getStartTime(const std::string& groupname) const; @@ -453,8 +456,6 @@ namespace MWRender /// the given group. float getCurrentTime(const std::string& groupname) const; - size_t getCurrentLoopCount(const std::string& groupname) const; - /** Disables the specified animation group; * \param groupname Animation group to disable. */ @@ -477,6 +478,9 @@ namespace MWRender MWWorld::MovementDirectionFlags getSupportedMovementDirections( std::span prefixes) const; + bool getPlayScriptedOnly() const { return mPlayScriptedOnly; } + void setPlayScriptedOnly(bool playScriptedOnly) { mPlayScriptedOnly = playScriptedOnly; } + virtual bool useShieldAnimations() const { return false; } virtual bool getWeaponsShown() const { return false; } virtual void showWeapons(bool showWeapon) {} diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 669a6fae45..469978e6eb 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -923,13 +923,18 @@ namespace MWRender if (mViewMode == VM_FirstPerson) { - NodeMap::iterator found = mNodeMap.find("bip01 neck"); - if (found != mNodeMap.end()) + // If there is no active animation, then the bip01 neck node will not be updated each frame, and the + // RotateController will accumulate rotations. + if (mStates.size() > 0) { - osg::MatrixTransform* node = found->second.get(); - mFirstPersonNeckController = new RotateController(mObjectRoot.get()); - node->addUpdateCallback(mFirstPersonNeckController); - mActiveControllers.emplace_back(node, mFirstPersonNeckController); + NodeMap::iterator found = mNodeMap.find("bip01 neck"); + if (found != mNodeMap.end()) + { + osg::MatrixTransform* node = found->second.get(); + mFirstPersonNeckController = new RotateController(mObjectRoot.get()); + node->addUpdateCallback(mFirstPersonNeckController); + mActiveControllers.emplace_back(node, mFirstPersonNeckController); + } } } else if (mViewMode == VM_Normal) diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 32d7e46527..8d439ec82b 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -91,7 +91,7 @@ namespace MWScript throw std::runtime_error("animation mode out of range"); } - MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(ptr, group, mode, loops + 1, true); + MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(ptr, group, mode, loops, true); } }; diff --git a/components/esm3/loadweap.hpp b/components/esm3/loadweap.hpp index e8355d0f55..ba1599b1df 100644 --- a/components/esm3/loadweap.hpp +++ b/components/esm3/loadweap.hpp @@ -24,6 +24,7 @@ namespace ESM enum Type { + First = -4, PickProbe = -4, HandToHand = -3, Spell = -2, @@ -41,7 +42,8 @@ namespace ESM MarksmanCrossbow = 10, MarksmanThrown = 11, Arrow = 12, - Bolt = 13 + Bolt = 13, + Last = 13 }; enum AttackType From 754c5a8e2a3bcd5b66e1a48350ddcd558fabaf94 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 4 Dec 2023 23:43:35 +0300 Subject: [PATCH 030/231] Restore animated collision shape rescaling --- apps/openmw/mwphysics/object.cpp | 12 +- .../nifloader/testbulletnifloader.cpp | 126 +++++++++++++----- components/nifbullet/bulletnifloader.cpp | 15 ++- components/resource/bulletshape.cpp | 6 +- components/resource/bulletshape.hpp | 19 ++- 5 files changed, 132 insertions(+), 46 deletions(-) diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 6e0e2cdc7f..53529ec729 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -137,6 +137,7 @@ namespace MWPhysics osg::NodePath& nodePath = nodePathFound->second; osg::Matrixf matrix = osg::computeLocalToWorld(nodePath); + btVector3 scale = Misc::Convert::toBullet(matrix.getScale()); matrix.orthoNormalize(matrix); btTransform transform; @@ -145,8 +146,15 @@ namespace MWPhysics for (int j = 0; j < 3; ++j) transform.getBasis()[i][j] = matrix(j, i); // NB column/row major difference - // Note: we can not apply scaling here for now since we treat scaled shapes - // as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now + btCollisionShape* childShape = compound->getChildShape(shapeIndex); + btVector3 newScale = compound->getLocalScaling() * scale; + + if (childShape->getLocalScaling() != newScale) + { + childShape->setLocalScaling(newScale); + result = true; + } + if (!(transform == compound->getChildTransform(shapeIndex))) { compound->updateChildTransform(shapeIndex, transform); diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 7dce21bac6..d44561a68b 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -144,6 +144,12 @@ namespace Resource return stream << "}}"; } + static std::ostream& operator<<(std::ostream& stream, const ScaledTriangleMeshShape& value) + { + return stream << "Resource::ScaledTriangleMeshShape {" << value.getLocalScaling() << ", " + << value.getChildShape() << "}"; + } + static bool operator==(const CollisionBox& l, const CollisionBox& r) { const auto tie = [](const CollisionBox& v) { return std::tie(v.mExtents, v.mCenter); }; @@ -169,6 +175,10 @@ static std::ostream& operator<<(std::ostream& stream, const btCollisionShape& va if (const auto casted = dynamic_cast(&value)) return stream << *casted; break; + case SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto casted = dynamic_cast(&value)) + return stream << *casted; + break; } return stream << "btCollisionShape {" << value.getShapeType() << "}"; } @@ -249,6 +259,12 @@ static bool operator==(const btBvhTriangleMeshShape& lhs, const btBvhTriangleMes && lhs.getOwnsBvh() == rhs.getOwnsBvh() && isNear(getTriangles(lhs), getTriangles(rhs)); } +static bool operator==(const btScaledBvhTriangleMeshShape& lhs, const btScaledBvhTriangleMeshShape& rhs) +{ + return isNear(lhs.getLocalScaling(), rhs.getLocalScaling()) + && compareObjects(lhs.getChildShape(), rhs.getChildShape()); +} + static bool operator==(const btCollisionShape& lhs, const btCollisionShape& rhs) { if (lhs.getShapeType() != rhs.getShapeType()) @@ -264,6 +280,11 @@ static bool operator==(const btCollisionShape& lhs, const btCollisionShape& rhs) if (const auto rhsCasted = dynamic_cast(&rhs)) return *lhsCasted == *rhsCasted; return false; + case SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto lhsCasted = dynamic_cast(&lhs)) + if (const auto rhsCasted = dynamic_cast(&rhs)) + return *lhsCasted == *rhsCasted; + return false; } return false; } @@ -572,7 +593,9 @@ namespace triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -596,7 +619,9 @@ namespace triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -619,7 +644,9 @@ namespace triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -644,7 +671,9 @@ namespace triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -669,9 +698,13 @@ namespace std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + auto triShape2 = std::make_unique(triangles2.release(), true); + compound->addChildShape( - btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles2.release(), true)); + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape2.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -695,7 +728,9 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + auto triShape = std::make_unique(triangles.release(), true); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(triShape.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -717,9 +752,8 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(3, 3, 3)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransform, mesh.release()); + shape->addChildShape(mTransform, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(3, 3, 3))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; @@ -744,9 +778,9 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransformScale4, mesh.release()); + shape->addChildShape( + mTransformScale4, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(12, 12, 12))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; @@ -776,16 +810,14 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(3, 3, 3)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); - mesh2->setLocalScaling(btVector3(3, 3, 3)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransform, mesh.release()); - shape->addChildShape(mTransform, mesh2.release()); + shape->addChildShape(mTransform, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(3, 3, 3))); + shape->addChildShape(mTransform, new Resource::ScaledTriangleMeshShape(mesh2.release(), btVector3(3, 3, 3))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; @@ -813,9 +845,9 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransformScale4, mesh.release()); + shape->addChildShape( + mTransformScale4, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(12, 12, 12))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; @@ -849,16 +881,16 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); - mesh->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); - mesh2->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransformScale4, mesh.release()); - shape->addChildShape(mTransformScale4, mesh2.release()); + shape->addChildShape( + mTransformScale4, new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(12, 12, 12))); + shape->addChildShape( + mTransformScale4, new Resource::ScaledTriangleMeshShape(mesh2.release(), btVector3(12, 12, 12))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 1 } }; @@ -880,14 +912,17 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); compound->addChildShape( - btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles2.release(), true)); + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh2.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -911,9 +946,11 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mAvoidCollisionShape.reset(compound.release()); @@ -973,8 +1010,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1002,8 +1041,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1029,8 +1070,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1057,8 +1100,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1083,8 +1128,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1116,8 +1163,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1178,8 +1227,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); triangles->addTriangle(btVector3(1, 0, 0), btVector3(0, 1, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1267,9 +1318,10 @@ namespace std::unique_ptr triangles(new btTriangleMesh(false)); triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); - + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape( + btTransform::getIdentity(), new Resource::ScaledTriangleMeshShape(mesh.release(), btVector3(1, 1, 1))); Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); @@ -1298,14 +1350,14 @@ namespace std::unique_ptr triangles1(new btTriangleMesh(false)); triangles1->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh1(new Resource::TriangleMeshShape(triangles1.release(), true)); - mesh1->setLocalScaling(btVector3(8, 8, 8)); std::unique_ptr triangles2(new btTriangleMesh(false)); triangles2->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); - mesh2->setLocalScaling(btVector3(12, 12, 12)); std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(mTransformScale2, mesh1.release()); - shape->addChildShape(mTransformScale3, mesh2.release()); + shape->addChildShape( + mTransformScale2, new Resource::ScaledTriangleMeshShape(mesh1.release(), btVector3(8, 8, 8))); + shape->addChildShape( + mTransformScale3, new Resource::ScaledTriangleMeshShape(mesh2.release(), btVector3(12, 12, 12))); Resource::BulletShape expected; expected.mCollisionShape.reset(shape.release()); expected.mAnimatedShapes = { { -1, 0 } }; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 96dff80004..c2cd915a8c 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -281,7 +281,20 @@ namespace NifBullet osg::Matrixf transform = niGeometry.mTransform.toMatrix(); for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) transform *= parent->mNiNode.mTransform.toMatrix(); - childShape->setLocalScaling(Misc::Convert::toBullet(transform.getScale())); + + if (childShape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) + { + auto scaledShape = std::make_unique( + static_cast(childShape.get()), Misc::Convert::toBullet(transform.getScale())); + std::ignore = childShape.release(); + + childShape = std::move(scaledShape); + } + else + { + childShape->setLocalScaling(Misc::Convert::toBullet(transform.getScale())); + } + transform.orthoNormalize(transform); btTransform trans; diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index cc0a63359b..360b92ffc0 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -32,11 +32,11 @@ namespace Resource return newShape; } - if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE) + if (shape->getShapeType() == SCALED_TRIANGLE_MESH_SHAPE_PROXYTYPE) { - const btBvhTriangleMeshShape* trishape = static_cast(shape); + const btScaledBvhTriangleMeshShape* trishape = static_cast(shape); return CollisionShapePtr(new btScaledBvhTriangleMeshShape( - const_cast(trishape), btVector3(1.f, 1.f, 1.f))); + const_cast(trishape->getChildShape()), trishape->getLocalScaling())); } if (shape->getShapeType() == BOX_SHAPE_PROXYTYPE) diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 9610fde49f..0a1b98bf7c 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -10,6 +10,7 @@ #include #include +#include class btCollisionShape; @@ -49,8 +50,8 @@ namespace Resource // collision box for creatures. For now, use one file <-> one resource for simplicity. CollisionBox mCollisionBox; - // Stores animated collision shapes. If any collision nodes in the NIF are animated, then mCollisionShape - // will be a btCompoundShape (which consists of one or more child shapes). + // Stores animated collision shapes. + // mCollisionShape is a btCompoundShape (which consists of one or more child shapes). // In this map, for each animated collision shape, // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; @@ -61,6 +62,7 @@ namespace Resource VisualCollisionType mVisualCollisionType = VisualCollisionType::None; BulletShape() = default; + // Note this is always a shallow copy and the copy will not autodelete underlying vertex data BulletShape(const BulletShape& other, const osg::CopyOp& copyOp = osg::CopyOp()); META_Object(Resource, BulletShape) @@ -70,7 +72,7 @@ namespace Resource bool isAnimated() const { return !mAnimatedShapes.empty(); } }; - // An instance of a BulletShape that may have its own unique scaling set on the mCollisionShape. + // An instance of a BulletShape that may have its own unique scaling set on collision shapes. // Vertex data is shallow-copied where possible. A ref_ptr to the original shape is held to keep vertex pointers // intact. class BulletShapeInstance : public BulletShape @@ -102,6 +104,17 @@ namespace Resource } }; + // btScaledBvhTriangleMeshShape that auto-deletes the child shape + struct ScaledTriangleMeshShape : public btScaledBvhTriangleMeshShape + { + ScaledTriangleMeshShape(btBvhTriangleMeshShape* childShape, const btVector3& localScaling) + : btScaledBvhTriangleMeshShape(childShape, localScaling) + { + } + + ~ScaledTriangleMeshShape() override { delete getChildShape(); } + }; + } #endif From 6f68df9ac221ad62a36441efb23fc27c69156ba2 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 6 Dec 2023 23:45:25 +0400 Subject: [PATCH 031/231] Avoid redundant copies --- apps/bulletobjecttool/main.cpp | 8 ++++---- apps/esmtool/esmtool.cpp | 2 +- apps/essimporter/main.cpp | 4 ++-- apps/navmeshtool/main.cpp | 8 ++++---- apps/opencs/view/world/dragrecordtable.cpp | 2 +- apps/openmw/mwlua/magicbindings.cpp | 2 +- apps/openmw/mwworld/scene.cpp | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index c522d69d70..7d87899f4a 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -145,12 +145,12 @@ namespace config.filterOutNonExistingPaths(dataDirs); - const auto resDir = variables["resources"].as(); + const auto& resDir = variables["resources"].as(); Log(Debug::Info) << Version::getOpenmwVersionDescription(); dataDirs.insert(dataDirs.begin(), resDir / "vfs"); - const auto fileCollections = Files::Collections(dataDirs); - const auto archives = variables["fallback-archive"].as(); - const auto contentFiles = variables["content"].as(); + const Files::Collections fileCollections(dataDirs); + const auto& archives = variables["fallback-archive"].as(); + const auto& contentFiles = variables["content"].as(); Fallback::Map::init(variables["fallback"].as().mMap); diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 092be66e97..88342a2a5b 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -156,7 +156,7 @@ Allowed options)"); return false; }*/ - const auto inputFiles = variables["input-file"].as(); + const auto& inputFiles = variables["input-file"].as(); info.filename = inputFiles[0].u8string(); // This call to u8string is redundant, but required to build on // MSVC 14.26 due to implementation bugs. if (inputFiles.size() > 1) diff --git a/apps/essimporter/main.cpp b/apps/essimporter/main.cpp index 7d3ad10bb1..f0833e9d81 100644 --- a/apps/essimporter/main.cpp +++ b/apps/essimporter/main.cpp @@ -42,8 +42,8 @@ Allowed options)"); Files::ConfigurationManager cfgManager(true); cfgManager.readConfiguration(variables, desc); - const auto essFile = variables["mwsave"].as(); - const auto outputFile = variables["output"].as(); + const auto& essFile = variables["mwsave"].as(); + const auto& outputFile = variables["output"].as(); std::string encoding = variables["encoding"].as(); ESSImport::Importer importer(essFile, outputFile, encoding); diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 793a08ba2c..8604bcdfb0 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -164,12 +164,12 @@ namespace NavMeshTool config.filterOutNonExistingPaths(dataDirs); - const auto resDir = variables["resources"].as(); + const auto& resDir = variables["resources"].as(); Log(Debug::Info) << Version::getOpenmwVersionDescription(); dataDirs.insert(dataDirs.begin(), resDir / "vfs"); - const auto fileCollections = Files::Collections(dataDirs); - const auto archives = variables["fallback-archive"].as(); - const auto contentFiles = variables["content"].as(); + const Files::Collections fileCollections(dataDirs); + const auto& archives = variables["fallback-archive"].as(); + const auto& contentFiles = variables["content"].as(); const std::size_t threadsNumber = variables["threads"].as(); if (threadsNumber < 1) diff --git a/apps/opencs/view/world/dragrecordtable.cpp b/apps/opencs/view/world/dragrecordtable.cpp index ae8c1cb708..11a0c9a540 100644 --- a/apps/opencs/view/world/dragrecordtable.cpp +++ b/apps/opencs/view/world/dragrecordtable.cpp @@ -92,7 +92,7 @@ void CSVWorld::DragRecordTable::dropEvent(QDropEvent* event) if (CSVWorld::DragDropUtils::isTopicOrJournal(*event, display)) { const CSMWorld::TableMimeData* tableMimeData = CSVWorld::DragDropUtils::getTableMimeData(*event); - for (auto universalId : tableMimeData->getData()) + for (const auto& universalId : tableMimeData->getData()) { emit createNewInfoRecord(universalId.getId()); } diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index fb714c81c6..0a34c008a7 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -839,7 +839,7 @@ namespace MWLua // Note that, although this is member method of ActorActiveEffects and we are removing an effect (not a // spell), we still need to use the active spells store to purge this effect from active spells. - auto ptr = effects.mActor.ptr(); + const auto& ptr = effects.mActor.ptr(); auto& activeSpells = ptr.getClass().getCreatureStats(ptr).getActiveSpells(); activeSpells.purgeEffect(ptr, key.mId, key.mArg); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 3d96de6749..036287d3a9 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -69,13 +69,13 @@ namespace osg::Quat makeInverseNodeRotation(const MWWorld::Ptr& ptr) { - const auto pos = ptr.getRefData().getPosition(); + const auto& pos = ptr.getRefData().getPosition(); return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : makeInversedOrderObjectOsgQuat(pos); } osg::Quat makeDirectNodeRotation(const MWWorld::Ptr& ptr) { - const auto pos = ptr.getRefData().getPosition(); + const auto& pos = ptr.getRefData().getPosition(); return ptr.getClass().isActor() ? makeActorOsgQuat(pos) : Misc::Convert::makeOsgQuat(pos); } From 81617719692dd410de135dd756d0c93502d65a59 Mon Sep 17 00:00:00 2001 From: Devin Alexander Torres Date: Thu, 7 Dec 2023 04:57:52 -0600 Subject: [PATCH 032/231] Add sol::lib::jit to actually enable JIT --- components/lua/luastate.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 95b56fb020..0a350a2d9f 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -179,6 +179,10 @@ namespace LuaUtil mSol.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string, sol::lib::table, sol::lib::os, sol::lib::debug); +#ifndef NO_LUAJIT + mSol.open_libraries(sol::lib::jit); +#endif // NO_LUAJIT + mSol["math"]["randomseed"](static_cast(std::time(nullptr))); mSol["math"]["randomseed"] = [] {}; From 1a39ef07c86ca0c4bb6d1999becb8b478ee3ee24 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 8 Dec 2023 00:00:54 +0100 Subject: [PATCH 033/231] Fix build with Lua 5.4.6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp: In member function ‘void MWLua::LuaManager::update()’: /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp:127:19: error: use of deleted function ‘Settings::SettingValue::SettingValue(const Settings::SettingValue&) [with T = int]’ 127 | lua_gc(mLua.sol(), LUA_GCSTEP, Settings::lua().mGcStepsPerFrame); | ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In file included from /home/elsid/dev/openmw/components/settings/categories/camera.hpp:5, from /home/elsid/dev/openmw/components/settings/values.hpp:4, from /home/elsid/dev/openmw/apps/openmw/mwlua/luamanagerimp.cpp:16: /home/elsid/dev/openmw/components/settings/settingvalue.hpp:355:9: note: declared here 355 | SettingValue(const SettingValue& other) = delete; | ^~~~~~~~~~~~ --- apps/openmw/mwlua/luamanagerimp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index d34518d558..c324360287 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -123,8 +123,8 @@ namespace MWLua void LuaManager::update() { - if (Settings::lua().mGcStepsPerFrame > 0) - lua_gc(mLua.sol(), LUA_GCSTEP, Settings::lua().mGcStepsPerFrame); + if (const int steps = Settings::lua().mGcStepsPerFrame; steps > 0) + lua_gc(mLua.sol(), LUA_GCSTEP, steps); if (mPlayer.isEmpty()) return; // The game is not started yet. From c1088e5f70e8ce1ca7fe5099e3f04bfdb4ddc2ab Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 8 Dec 2023 00:19:14 +0300 Subject: [PATCH 034/231] Streamline passing influence data to skinning --- components/nifosg/nifloader.cpp | 58 ++++++++++++++-------------- components/sceneutil/riggeometry.cpp | 37 +++++++++++++----- components/sceneutil/riggeometry.hpp | 32 +++++++-------- 3 files changed, 70 insertions(+), 57 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 436f2e1d34..a08231809b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1515,24 +1515,24 @@ namespace NifOsg osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geom); - // Assign bone weights - osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); - const Nif::NiSkinInstance* skin = niGeometry->mSkin.getPtr(); const Nif::NiSkinData* data = skin->mData.getPtr(); const Nif::NiAVObjectList& bones = skin->mBones; + + // Assign bone weights + std::vector boneInfo; + std::vector influences; + boneInfo.resize(bones.size()); + influences.resize(bones.size()); for (std::size_t i = 0; i < bones.size(); ++i) { - std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); - - SceneUtil::RigGeometry::BoneInfluence influence; - influence.mWeights = data->mBones[i].mWeights; - influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); - influence.mBoundSphere = data->mBones[i].mBoundSphere; - - map->mData.emplace_back(boneName, influence); + boneInfo[i].mName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); + boneInfo[i].mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); + boneInfo[i].mBoundSphere = data->mBones[i].mBoundSphere; + influences[i] = data->mBones[i].mWeights; } - rig->setInfluenceMap(map); + rig->setBoneInfo(std::move(boneInfo)); + rig->setInfluences(influences); drawable = rig; } @@ -1671,29 +1671,29 @@ namespace NifOsg osg::ref_ptr rig(new SceneUtil::RigGeometry); rig->setSourceGeometry(geometry); - osg::ref_ptr map(new SceneUtil::RigGeometry::InfluenceMap); - - auto skin = static_cast(bsTriShape->mSkin.getPtr()); + const Nif::BSSkinInstance* skin = static_cast(bsTriShape->mSkin.getPtr()); const Nif::BSSkinBoneData* data = skin->mData.getPtr(); const Nif::NiAVObjectList& bones = skin->mBones; - std::vector> vertWeights(data->mBones.size()); - for (size_t i = 0; i < vertices.size(); i++) - for (int j = 0; j < 4; j++) - vertWeights[bsTriShape->mVertData[i].mBoneIndices[j]].emplace_back( - i, halfToFloat(bsTriShape->mVertData[i].mBoneWeights[j])); + std::vector boneInfo; + std::vector influences; + boneInfo.resize(bones.size()); + influences.resize(vertices.size()); for (std::size_t i = 0; i < bones.size(); ++i) { - std::string boneName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); - - SceneUtil::RigGeometry::BoneInfluence influence; - influence.mWeights = vertWeights[i]; - influence.mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); - influence.mBoundSphere = data->mBones[i].mBoundSphere; - - map->mData.emplace_back(boneName, influence); + boneInfo[i].mName = Misc::StringUtils::lowerCase(bones[i].getPtr()->mName); + boneInfo[i].mInvBindMatrix = data->mBones[i].mTransform.toMatrix(); + boneInfo[i].mBoundSphere = data->mBones[i].mBoundSphere; } - rig->setInfluenceMap(map); + + for (size_t i = 0; i < vertices.size(); i++) + { + const Nif::BSVertexData& vertData = bsTriShape->mVertData[i]; + for (int j = 0; j < 4; j++) + influences[i].emplace_back(vertData.mBoneIndices[j], halfToFloat(vertData.mBoneWeights[j])); + } + rig->setBoneInfo(std::move(boneInfo)); + rig->setInfluences(influences); drawable = rig; } diff --git a/components/sceneutil/riggeometry.cpp b/components/sceneutil/riggeometry.cpp index 1572fab338..92e316d412 100644 --- a/components/sceneutil/riggeometry.cpp +++ b/components/sceneutil/riggeometry.cpp @@ -301,23 +301,29 @@ namespace SceneUtil } } - void RigGeometry::setInfluenceMap(osg::ref_ptr influenceMap) + void RigGeometry::setBoneInfo(std::vector&& bones) { - mData = new InfluenceData; - mData->mBones.reserve(influenceMap->mData.size()); + if (!mData) + mData = new InfluenceData; - std::unordered_map> vertexToInfluences; + mData->mBones = std::move(bones); + } + + void RigGeometry::setInfluences(const std::vector& influences) + { + if (!mData) + mData = new InfluenceData; + + std::unordered_map vertexToInfluences; size_t index = 0; - for (const auto& [boneName, bi] : influenceMap->mData) + for (const auto& influence : influences) { - mData->mBones.push_back({ boneName, bi.mBoundSphere, bi.mInvBindMatrix }); - - for (const auto& [vertex, weight] : bi.mWeights) + for (const auto& [vertex, weight] : influence) vertexToInfluences[vertex].emplace_back(index, weight); index++; } - std::map, VertexList> influencesToVertices; + std::map influencesToVertices; for (const auto& [vertex, weights] : vertexToInfluences) influencesToVertices[weights].emplace_back(vertex); @@ -325,6 +331,19 @@ namespace SceneUtil mData->mInfluences.assign(influencesToVertices.begin(), influencesToVertices.end()); } + void RigGeometry::setInfluences(const std::vector& influences) + { + if (!mData) + mData = new InfluenceData; + + std::map influencesToVertices; + for (size_t i = 0; i < influences.size(); i++) + influencesToVertices[influences[i]].emplace_back(i); + + mData->mInfluences.reserve(influencesToVertices.size()); + mData->mInfluences.assign(influencesToVertices.begin(), influencesToVertices.end()); + } + void RigGeometry::accept(osg::NodeVisitor& nv) { if (!nv.validNodeMask(*this)) diff --git a/components/sceneutil/riggeometry.hpp b/components/sceneutil/riggeometry.hpp index d1c077288d..64ea1e2519 100644 --- a/components/sceneutil/riggeometry.hpp +++ b/components/sceneutil/riggeometry.hpp @@ -36,21 +36,23 @@ namespace SceneUtil // static parts of the model. void compileGLObjects(osg::RenderInfo& renderInfo) const override {} - // TODO: Make InfluenceMap more similar to InfluenceData - struct BoneInfluence + struct BoneInfo { - osg::Matrixf mInvBindMatrix; + std::string mName; osg::BoundingSpheref mBoundSphere; - // - std::vector> mWeights; + osg::Matrixf mInvBindMatrix; }; - struct InfluenceMap : public osg::Referenced - { - std::vector> mData; - }; + using VertexWeight = std::pair; + using VertexWeights = std::vector; + using BoneWeight = std::pair; + using BoneWeights = std::vector; - void setInfluenceMap(osg::ref_ptr influenceMap); + void setBoneInfo(std::vector&& bones); + // Convert influences in vertex and weight list per bone format + void setInfluences(const std::vector& influences); + // Convert influences in bone and weight list per vertex format + void setInfluences(const std::vector& influences); /// Initialize this geometry from the source geometry. /// @note The source geometry will not be modified. @@ -89,19 +91,11 @@ namespace SceneUtil osg::ref_ptr mGeomToSkelMatrix; - struct BoneInfo - { - std::string mName; - osg::BoundingSpheref mBoundSphere; - osg::Matrixf mInvBindMatrix; - }; - - using BoneWeight = std::pair; using VertexList = std::vector; struct InfluenceData : public osg::Referenced { std::vector mBones; - std::vector, VertexList>> mInfluences; + std::vector> mInfluences; }; osg::ref_ptr mData; std::vector mNodes; From cedc5289d785e44bad706133dfc80a988b32fb66 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 1 Dec 2023 19:24:20 +0100 Subject: [PATCH 035/231] Dejank movement solver vs animation movement accumulation --- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 4 +- apps/openmw/mwphysics/movementsolver.cpp | 2 + apps/openmw/mwphysics/mtphysics.cpp | 80 ++++++++++++++++++++--- apps/openmw/mwphysics/mtphysics.hpp | 7 +- apps/openmw/mwphysics/physicssystem.cpp | 16 +++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/ptrholder.hpp | 15 ++++- apps/openmw/mwrender/animation.cpp | 68 +++++++++++++++++-- apps/openmw/mwrender/animation.hpp | 3 +- apps/openmw/mwworld/datetimemanager.hpp | 5 ++ apps/openmw/mwworld/projectilemanager.cpp | 7 +- apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 2 +- 14 files changed, 183 insertions(+), 34 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 14e3b2b3b7..f67b9a0e05 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -295,7 +295,7 @@ namespace MWBase /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is /// obstructed). - virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) = 0; + virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) = 0; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 77bc51423e..9a9f62f9ac 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2420,7 +2420,7 @@ namespace MWMechanics } if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, vec); + world->queueMovement(mPtr, vec, duration); } movement = vec; @@ -2493,7 +2493,7 @@ namespace MWMechanics } // Update movement - world->queueMovement(mPtr, movement); + world->queueMovement(mPtr, movement, duration); } mSkipAnim = false; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index c0b5014b31..ae958c60c7 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -170,6 +170,8 @@ namespace MWPhysics } // Now that we have the effective movement vector, apply wind forces to it + // TODO: This will cause instability in idle animations and other in-place animations. Should include a flag for + // this when queueing up movement if (worldData.mIsInStorm && velocity.length() > 0) { osg::Vec3f stormDirection = worldData.mStormDirection; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 52b96d9d13..5df9a5c7a9 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -25,6 +25,7 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -35,6 +36,7 @@ #include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" +#include "ptrholder.hpp" namespace MWPhysics { @@ -195,6 +197,51 @@ namespace void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const {} }; + struct InitMovement + { + float mSteps = 1.f; + float mDelta = 0.f; + float mSimulationTime = 0.f; + + // Returns how the actor or projectile wants to move between startTime and endTime + osg::Vec3f takeMovement(MWPhysics::PtrHolder& actor, float startTime, float endTime) const + { + osg::Vec3f movement = osg::Vec3f(); + auto it = actor.movement().begin(); + while (it != actor.movement().end()) + { + float start = std::max(it->simulationTimeStart, startTime); + float stop = std::min(it->simulationTimeStop, endTime); + movement += it->velocity * (stop - start); + if (std::abs(stop - it->simulationTimeStop) < 0.0001f) + it = actor.movement().erase(it); + else + it++; + } + + return movement; + } + + void operator()(auto& sim) const + { + if (mSteps == 0 || mDelta < 0.00001f) + return; + + auto locked = sim.lock(); + if (!locked.has_value()) + return; + + auto& [ptrHolder, frameDataRef] = *locked; + + // Because takeMovement() returns movement instead of velocity, convert it back to velocity for the + // movement solver + osg::Vec3f velocity + = takeMovement(*ptrHolder, mSimulationTime, mSimulationTime + mDelta * mSteps) / (mSteps * mDelta); + + frameDataRef.get().mMovement += velocity; + } + }; + struct PreStep { btCollisionWorld* mCollisionWorld; @@ -501,18 +548,18 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, std::vector& simulations, - osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, float simulationTime, + std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { assert(mSimulations != &simulations); waitForWorkers(); - prepareWork(timeAccum, simulations, frameStart, frameNumber, stats); + prepareWork(timeAccum, simulationTime, simulations, frameStart, frameNumber, stats); if (mWorkersSync != nullptr) mWorkersSync->wakeUpWorkers(); } - void PhysicsTaskScheduler::prepareWork(float& timeAccum, std::vector& simulations, + void PhysicsTaskScheduler::prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. @@ -522,6 +569,9 @@ namespace MWPhysics double timeStart = mTimer->tick(); + // The simulation time when the movement solving begins. + float simulationTimeStart = simulationTime - timeAccum; + // start by finishing previous background computation if (mNumThreads != 0) { @@ -536,10 +586,15 @@ namespace MWPhysics timeAccum -= numSteps * newDelta; // init - const Visitors::InitPosition vis{ mCollisionWorld }; + const Visitors::InitPosition initPositionVisitor{ mCollisionWorld }; for (auto& sim : simulations) { - std::visit(vis, sim); + std::visit(initPositionVisitor, sim); + } + const Visitors::InitMovement initMovementVisitor{ numSteps, newDelta, simulationTimeStart }; + for (auto& sim : simulations) + { + std::visit(initMovementVisitor, sim); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; @@ -552,10 +607,11 @@ namespace MWPhysics mNextJob.store(0, std::memory_order_release); if (mAdvanceSimulation) + { + mNextJobSimTime = simulationTimeStart + (numSteps * newDelta); mWorldFrameData = std::make_unique(); - - if (mAdvanceSimulation) mBudgetCursor += 1; + } if (mNumThreads == 0) { @@ -756,6 +812,7 @@ namespace MWPhysics mLockingPolicy }; for (Simulation& sim : *mSimulations) std::visit(vis, sim); + mCurrentJobSimTime += mPhysicsDt; } bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) @@ -864,6 +921,10 @@ namespace MWPhysics std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), mLOSCache.end()); } + + // On paper, mCurrentJobSimTime should have added up to mNextJobSimTime already + // But to avoid accumulating floating point errors, assign this anyway. + mCurrentJobSimTime = mNextJobSimTime; mTimeEnd = mTimer->tick(); if (mWorkersSync != nullptr) mWorkersSync->workIsDone(); @@ -878,6 +939,9 @@ namespace MWPhysics std::visit(vis, sim); mSimulations->clear(); mSimulations = nullptr; + const float interpolationFactor = std::clamp(mTimeAccum / mPhysicsDt, 0.0f, 1.0f); + MWBase::Environment::get().getWorld()->getTimeManager()->setPhysicsSimulationTime( + mCurrentJobSimTime - mPhysicsDt * (1.f - interpolationFactor)); } // Attempt to acquire unique lock on mSimulationMutex while not all worker diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 57f3711096..d2eb320ea4 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -46,7 +46,7 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - void applyQueuedMovements(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -87,7 +87,7 @@ namespace MWPhysics void afterPostSim(); void syncWithMainThread(); void waitForWorkers(); - void prepareWork(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + void prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::unique_ptr mWorldFrameData; @@ -96,6 +96,9 @@ namespace MWPhysics float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; + float mNextJobSimTime = 0.f; + float mCurrentJobSimTime = 0.f; + float mPreviousJobSimTime = 0.f; btCollisionWorld* mCollisionWorld; MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 2196834a50..3c451497e1 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -43,6 +43,7 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/datetimemanager.hpp" #include "actor.hpp" #include "collisiontype.hpp" @@ -623,18 +624,19 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) { + float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) - found->second->setVelocity(velocity); + found->second->queueMovement(velocity, start, start + duration); } void PhysicsSystem::clearQueuedMovement() { for (const auto& [_, actor] : mActors) { - actor->setVelocity(osg::Vec3f()); + actor->clearMovement(); actor->setInertialForce(osg::Vec3f()); } } @@ -722,8 +724,10 @@ namespace MWPhysics { std::vector& simulations = mSimulations[mSimulationsCounter++ % mSimulations.size()]; prepareSimulation(mTimeAccum >= mPhysicsDt, simulations); + float simulationTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime() + dt; // modifies mTimeAccum - mTaskScheduler->applyQueuedMovements(mTimeAccum, simulations, frameStart, frameNumber, stats); + mTaskScheduler->applyQueuedMovements( + mTimeAccum, simulationTime, simulations, frameStart, frameNumber, stats); } } @@ -907,7 +911,7 @@ namespace MWPhysics ->mValue.getFloat())) , mSlowFall(slowFall) , mRotation() - , mMovement(actor.velocity()) + , mMovement() , mWaterlevel(waterlevel) , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) @@ -922,7 +926,7 @@ namespace MWPhysics ProjectileFrameData::ProjectileFrameData(Projectile& projectile) : mPosition(projectile.getPosition()) - , mMovement(projectile.velocity()) + , mMovement() , mCaster(projectile.getCasterCollisionObject()) , mCollisionObject(projectile.getCollisionObject()) , mProjectile(&projectile) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index ad56581eb3..3babdef9aa 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -245,7 +245,7 @@ namespace MWPhysics /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation - void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity); + void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index fc8fd94c30..270d2af37b 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -13,6 +14,13 @@ namespace MWPhysics { + struct Movement + { + osg::Vec3f velocity = osg::Vec3f(); + float simulationTimeStart = 0.f; // The time at which this movement begun + float simulationTimeStop = 0.f; // The time at which this movement finished + }; + class PtrHolder { public: @@ -32,9 +40,10 @@ namespace MWPhysics btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - void setVelocity(osg::Vec3f velocity) { mVelocity = velocity; } + void clearMovement() { mMovement = { }; } + void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop) { mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop }); } - osg::Vec3f velocity() { return std::exchange(mVelocity, osg::Vec3f()); } + std::deque& movement() { return mMovement; } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } @@ -53,7 +62,7 @@ namespace MWPhysics protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; - osg::Vec3f mVelocity; + std::deque mMovement; osg::Vec3f mSimulationPosition; osg::Vec3d mPosition; osg::Vec3d mPreviousPosition; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5b0e1f82bd..b9d6759dc7 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -50,6 +50,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -489,12 +490,21 @@ namespace MWRender class ResetAccumRootCallback : public SceneUtil::NodeCallback { + struct AccumulatedMovement + { + osg::Vec3f mMovement = osg::Vec3f(); + float mSimStartTime = 0.f; + float mSimStopTime = 0.f; + }; + public: void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { osg::Matrix mat = transform->getMatrix(); osg::Vec3f position = mat.getTrans(); position = osg::componentMultiply(mResetAxes, position); + // Add back the offset that the movement solver has not consumed yet + position += computeRemainder(); mat.setTrans(position); transform->setMatrix(mat); @@ -509,7 +519,35 @@ namespace MWRender mResetAxes.z() = accumulate.z() != 0.f ? 0.f : 1.f; } + void accumulate(const osg::Vec3f& movement, float dt) + { + if (dt < 0.00001f) + return; + + float simTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); + mMovement.emplace_back(AccumulatedMovement{ movement, simTime, simTime + dt }); + } + + const osg::Vec3f computeRemainder() + { + float physSimTime = MWBase::Environment::get().getWorld()->getTimeManager()->getPhysicsSimulationTime(); + // Start by erasing all movement that has been fully consumed by the physics code + std::erase_if(mMovement, + [physSimTime](const AccumulatedMovement& movement) { return movement.mSimStopTime <= physSimTime; }); + + // Accumulate all the movement that hasn't been consumed. + osg::Vec3f movement; + for (const auto& m : mMovement) + { + float startTime = std::max(physSimTime, m.mSimStartTime); + float fraction = (m.mSimStopTime - startTime) / (m.mSimStopTime - m.mSimStartTime); + movement += m.mMovement * fraction; + } + return movement; + } + private: + std::deque mMovement; osg::Vec3f mResetAxes; }; @@ -1129,11 +1167,27 @@ namespace MWRender return velocity; } - void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position) + void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement) { // Get the difference from the last update, and move the position - osg::Vec3f off = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); - position += off - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); + osg::Vec3f offset = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); + if (!hasMovement) + { + // When animations have no velocity, the character should have zero net movement through a complete loop or + // animation sequence. Although any subsequence of the animation may move. This works because each sequence + // starts and stops with Bip01 at the same position, totaling 0 movement. This allows us to accurately move + // the character by just moving it from the position Bip01 was last frame to where it is this frame, without + // needing to accumulate anything in-between. + position += offset - mPreviousPosition; + mPreviousPosition = offset; + } + else + { + // When animations have velocity, net movement is expected. The above block would negate that movement every time + // the animation resets. Therefore we have to accumulate from oldtime to newtime instead, which works because + // oldtime < newtime is a guarantee even when the animation has looped. + position += offset - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); + } } osg::Vec3f Animation::runAnimation(float duration) @@ -1151,6 +1205,7 @@ namespace MWRender const SceneUtil::TextKeyMap& textkeys = state.mSource->getTextKeys(); auto textkey = textkeys.upperBound(state.getTime()); + bool hasMovement = getVelocity(stateiter->first) > 0.001f; float timepassed = duration * state.mSpeedMult; while (state.mPlaying) @@ -1161,13 +1216,13 @@ namespace MWRender if (textkey == textkeys.end() || textkey->first > targetTime) { if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), targetTime, movement); + updatePosition(state.getTime(), targetTime, movement, hasMovement); state.setTime(std::min(targetTime, state.mStopTime)); } else { if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), textkey->first, movement); + updatePosition(state.getTime(), textkey->first, movement, hasMovement); state.setTime(textkey->first); } @@ -1249,6 +1304,9 @@ namespace MWRender osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1))); } + + if (mResetAccumRootCallback) + mResetAccumRootCallback->accumulate(movement, duration); return movement; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 24366889c4..990910fc50 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -276,6 +276,7 @@ namespace MWRender float mUpperBodyYawRadians; float mLegsYawRadians; float mBodyPitchRadians; + osg::Vec3f mPreviousPosition; osg::ref_ptr addRotateController(std::string_view bone); @@ -304,7 +305,7 @@ namespace MWRender /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ - void updatePosition(float oldtime, float newtime, osg::Vec3f& position); + void updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement); /* Resets the animation to the time of the specified start marker, without * moving anything, and set the end time to the specified stop marker. If diff --git a/apps/openmw/mwworld/datetimemanager.hpp b/apps/openmw/mwworld/datetimemanager.hpp index af62d9ba3f..20d968f4ea 100644 --- a/apps/openmw/mwworld/datetimemanager.hpp +++ b/apps/openmw/mwworld/datetimemanager.hpp @@ -29,6 +29,10 @@ namespace MWWorld float getGameTimeScale() const { return mGameTimeScale; } void setGameTimeScale(float scale); // game time to simulation time ratio + // Physics simulation time + double getPhysicsSimulationTime() const { return mPhysicsSimulationTime; } + void setPhysicsSimulationTime(double t) { mPhysicsSimulationTime = t; } + // Rendering simulation time (summary simulation time of rendering frames since application start). double getRenderingSimulationTime() const { return mRenderingSimulationTime; } void setRenderingSimulationTime(double t) { mRenderingSimulationTime = t; } @@ -70,6 +74,7 @@ namespace MWWorld float mSimulationTimeScale = 1.0; double mRenderingSimulationTime = 0.0; double mSimulationTime = 0.0; + double mPhysicsSimulationTime = 0.0; bool mPaused = false; std::set> mPausedTags; }; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index d873f16a59..3496b5dab5 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -33,6 +33,7 @@ #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" @@ -457,7 +458,8 @@ namespace MWWorld } osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); direction.normalize(); - projectile->setVelocity(direction * speed); + float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); + projectile->queueMovement(direction * speed, start, start + duration); update(magicBoltState, duration); @@ -485,7 +487,8 @@ namespace MWWorld projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - projectile->setVelocity(projectileState.mVelocity); + float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); + projectile->queueMovement(projectileState.mVelocity, start, start + duration); // rotation does not work well for throwing projectiles - their roll angle will depend on shooting // direction. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d945aa5848..9db72d1604 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1448,9 +1448,9 @@ namespace MWWorld return placed; } - void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) + void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) { - mPhysics->queueObjectMovement(ptr, velocity); + mPhysics->queueObjectMovement(ptr, velocity, duration); } void World::updateAnimatedCollisionShape(const Ptr& ptr) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4b9a0ccb98..486e240fdc 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -383,7 +383,7 @@ namespace MWWorld float getMaxActivationDistance() const override; - void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) override; + void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) override; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. From 81095686bf42ffbf33e447e8d9167cbc7ad0b1bc Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 13:06:09 +0100 Subject: [PATCH 036/231] Reset mPreviousAccumulatePosition when not accumulating to avoid an instant transition when resuming idle anims. --- apps/openmw/mwmechanics/character.cpp | 5 +++-- apps/openmw/mwrender/animation.cpp | 18 ++++++++++++------ apps/openmw/mwrender/animation.hpp | 5 +++-- apps/openmw/mwrender/creatureanimation.cpp | 4 ++-- apps/openmw/mwrender/creatureanimation.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 ++-- apps/openmw/mwrender/npcanimation.hpp | 2 +- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9a9f62f9ac..2ef3479cd9 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2446,10 +2446,11 @@ namespace MWMechanics } } + bool doMovementAccumulation = isMovementAnimationControlled(); osg::Vec3f movementFromAnimation - = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); + = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration, doMovementAccumulation); - if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) + if (mPtr.getClass().isActor() && doMovementAccumulation && !isScriptedAnimPlaying()) { if (duration > 0.0f) movementFromAnimation /= duration; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b9d6759dc7..cb51bcd00f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1178,8 +1178,11 @@ namespace MWRender // starts and stops with Bip01 at the same position, totaling 0 movement. This allows us to accurately move // the character by just moving it from the position Bip01 was last frame to where it is this frame, without // needing to accumulate anything in-between. - position += offset - mPreviousPosition; - mPreviousPosition = offset; + if (mPreviousAccumulatePosition) + { + position += offset - mPreviousAccumulatePosition.value(); + } + mPreviousAccumulatePosition = offset; } else { @@ -1190,8 +1193,11 @@ namespace MWRender } } - osg::Vec3f Animation::runAnimation(float duration) + osg::Vec3f Animation::runAnimation(float duration, bool accumulateMovement) { + if (!accumulateMovement) + mPreviousAccumulatePosition = std::nullopt; + osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); while (stateiter != mStates.end()) @@ -1215,13 +1221,13 @@ namespace MWRender float targetTime = state.getTime() + timepassed; if (textkey == textkeys.end() || textkey->first > targetTime) { - if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + if (accumulateMovement && mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) updatePosition(state.getTime(), targetTime, movement, hasMovement); state.setTime(std::min(targetTime, state.mStopTime)); } else { - if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + if (accumulateMovement && mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) updatePosition(state.getTime(), textkey->first, movement, hasMovement); state.setTime(textkey->first); } @@ -1305,7 +1311,7 @@ namespace MWRender } - if (mResetAccumRootCallback) + if (accumulateMovement && mResetAccumRootCallback) mResetAccumRootCallback->accumulate(movement, duration); return movement; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 990910fc50..0e748180e8 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace ESM { @@ -265,6 +266,7 @@ namespace MWRender Resource::ResourceSystem* mResourceSystem; osg::Vec3f mAccumulate; + std::optional mPreviousAccumulatePosition; TextKeyListener* mTextKeyListener; @@ -276,7 +278,6 @@ namespace MWRender float mUpperBodyYawRadians; float mLegsYawRadians; float mBodyPitchRadians; - osg::Vec3f mPreviousPosition; osg::ref_ptr addRotateController(std::string_view bone); @@ -465,7 +466,7 @@ namespace MWRender /** Retrieves the velocity (in units per second) that the animation will move. */ float getVelocity(std::string_view groupname) const; - virtual osg::Vec3f runAnimation(float duration); + virtual osg::Vec3f runAnimation(float duration, bool accumulateMovement = false); void setLoopingEnabled(std::string_view groupname, bool enabled); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 7767520715..65f48f5ade 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -258,9 +258,9 @@ namespace MWRender WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); } - osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) + osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration, bool accumulateMovement) { - osg::Vec3f ret = Animation::runAnimation(duration); + osg::Vec3f ret = Animation::runAnimation(duration, accumulateMovement); WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 05235e5191..9ff3d55375 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -59,7 +59,7 @@ namespace MWRender void addControllers() override; - osg::Vec3f runAnimation(float duration) override; + osg::Vec3f runAnimation(float duration, bool accumulateMovement = false) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 469978e6eb..951d16aefe 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -693,9 +693,9 @@ namespace MWRender return std::make_unique(attached); } - osg::Vec3f NpcAnimation::runAnimation(float timepassed) + osg::Vec3f NpcAnimation::runAnimation(float timepassed, bool accumulateMovement) { - osg::Vec3f ret = Animation::runAnimation(timepassed); + osg::Vec3f ret = Animation::runAnimation(timepassed, accumulateMovement); mHeadAnimationTime->update(timepassed); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index a03ee28f3a..0c2d67db86 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -130,7 +130,7 @@ namespace MWRender void setWeaponGroup(const std::string& group, bool relativeDuration) override; - osg::Vec3f runAnimation(float timepassed) override; + osg::Vec3f runAnimation(float timepassed, bool accumulateMovement = false) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. From 28eeef59bca179b10bd252473ce7b127228563d8 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 13:35:02 +0100 Subject: [PATCH 037/231] Reduce movement solver same-position epsilon size. The previous value causes very stable idles to very slightly slide. --- apps/openmw/mwphysics/movementsolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index ae958c60c7..02d24e735c 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -214,7 +214,7 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - if ((newPosition - nextpos).length2() > 0.0001) + if ((newPosition - nextpos).length2() > 0.00001) { // trace to where character would go if there were no obstructions tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); From 18a6422c1c3b5cc371837f772dde8f8010eaeac5 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 14:21:14 +0100 Subject: [PATCH 038/231] clang format --- apps/openmw/mwphysics/ptrholder.hpp | 9 ++++++--- apps/openmw/mwrender/animation.cpp | 8 ++++---- apps/openmw/mwrender/animation.hpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 270d2af37b..5884372e42 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -1,10 +1,10 @@ #ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H +#include #include #include #include -#include #include @@ -40,8 +40,11 @@ namespace MWPhysics btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - void clearMovement() { mMovement = { }; } - void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop) { mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop }); } + void clearMovement() { mMovement = {}; } + void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop) + { + mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop }); + } std::deque& movement() { return mMovement; } diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cb51bcd00f..e12e8212e6 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -49,8 +49,8 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/esmstore.hpp" #include "../mwworld/datetimemanager.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -1186,9 +1186,9 @@ namespace MWRender } else { - // When animations have velocity, net movement is expected. The above block would negate that movement every time - // the animation resets. Therefore we have to accumulate from oldtime to newtime instead, which works because - // oldtime < newtime is a guarantee even when the animation has looped. + // When animations have velocity, net movement is expected. The above block would negate that movement every + // time the animation resets. Therefore we have to accumulate from oldtime to newtime instead, which works + // because oldtime < newtime is a guarantee even when the animation has looped. position += offset - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 0e748180e8..8e159700ea 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -10,11 +10,11 @@ #include #include +#include #include #include #include #include -#include namespace ESM { diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3496b5dab5..b3a01e8c3e 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -30,10 +30,10 @@ #include #include "../mwworld/class.hpp" +#include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" -#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" From aa30ec81d6d11721a04b3ba139fed232ab5aff7e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 16:27:21 +0100 Subject: [PATCH 039/231] more clang format --- apps/openmw/mwphysics/mtphysics.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index d2eb320ea4..83d3010359 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -46,8 +46,8 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, - unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector& simulations, + osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -87,8 +87,8 @@ namespace MWPhysics void afterPostSim(); void syncWithMainThread(); void waitForWorkers(); - void prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, osg::Timer_t frameStart, - unsigned int frameNumber, osg::Stats& stats); + void prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, + osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); std::unique_ptr mWorldFrameData; std::vector* mSimulations = nullptr; From 3683f24b2818e3b49a70900e85f15db428fde707 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 17:47:18 +0100 Subject: [PATCH 040/231] changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b36eb2525..531e51c812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ------ Bug #2623: Snowy Granius doesn't prioritize conjuration spells + Bug #3330: Backward displacement of the character when attacking in 3rd person Bug #3438: NPCs can't hit bull netch with melee weapons Bug #3842: Body part skeletons override the main skeleton Bug #4127: Weapon animation looks choppy From edf8c3b81c2b64648388687cfccb997631b631ca Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 3 Dec 2023 17:53:40 +0100 Subject: [PATCH 041/231] mSteps should be an int. --- apps/openmw/mwphysics/mtphysics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 5df9a5c7a9..09eb309593 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -199,7 +199,7 @@ namespace struct InitMovement { - float mSteps = 1.f; + int mSteps = 0; float mDelta = 0.f; float mSimulationTime = 0.f; @@ -224,7 +224,7 @@ namespace void operator()(auto& sim) const { - if (mSteps == 0 || mDelta < 0.00001f) + if (mSteps <= 0 || mDelta < 0.00001f) return; auto locked = sim.lock(); From 0037fd78c17ef2977e4a16cb1f6a7f10471f3b8b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 4 Dec 2023 20:08:42 +0100 Subject: [PATCH 042/231] Use std::numeric_limits::epsilon() instead of picking our own epsilon. --- apps/openmw/mwphysics/movementsolver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 02d24e735c..4952a561e7 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -214,7 +214,7 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - if ((newPosition - nextpos).length2() > 0.00001) + if ((newPosition - nextpos).length2() > std::numeric_limits::epsilon()) { // trace to where character would go if there were no obstructions tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); From 26817e9cc59dc4ae7e42262f23bef2ffca48161e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 4 Dec 2023 20:36:22 +0100 Subject: [PATCH 043/231] Change the comparison of positions to avoid a problem if both positions are large numbers. --- apps/openmw/mwphysics/movementsolver.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 4952a561e7..d67758a618 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -202,7 +202,8 @@ namespace MWPhysics for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { - osg::Vec3f nextpos = newPosition + velocity * remainingTime; + osg::Vec3f diff = velocity * remainingTime; + osg::Vec3f nextpos = newPosition + diff; bool underwater = newPosition.z() < swimlevel; // If not able to fly, don't allow to swim up into the air @@ -214,7 +215,9 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - if ((newPosition - nextpos).length2() > std::numeric_limits::epsilon()) + // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely + // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle animations. + if (diff.length2() > 1e-6 && (newPosition - nextpos).length2() > 1e-6) { // trace to where character would go if there were no obstructions tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); From 15c143e27258a9b37d63d7f5031a1d755535c774 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 4 Dec 2023 20:53:45 +0100 Subject: [PATCH 044/231] Comment --- apps/openmw/mwphysics/movementsolver.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index d67758a618..d187470801 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -217,6 +217,8 @@ namespace MWPhysics // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle animations. + // Note that, although both these comparisons to 1e-6 are logically the same, they test separate floating point + // accuracy cases. if (diff.length2() > 1e-6 && (newPosition - nextpos).length2() > 1e-6) { // trace to where character would go if there were no obstructions From af9312d869683dad87a2c0ad53c858207e304e5a Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 4 Dec 2023 20:56:04 +0100 Subject: [PATCH 045/231] clang format --- apps/openmw/mwphysics/movementsolver.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index d187470801..63ffb055dd 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -215,10 +215,10 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely - // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle animations. - // Note that, although both these comparisons to 1e-6 are logically the same, they test separate floating point - // accuracy cases. + // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely + // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle + // animations. Note that, although both these comparisons to 1e-6 are logically the same, they test separate + // floating point accuracy cases. if (diff.length2() > 1e-6 && (newPosition - nextpos).length2() > 1e-6) { // trace to where character would go if there were no obstructions From 9cdaf2c29be1875fc3abe532ccae083c2098a6b2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 8 Dec 2023 22:53:00 +0300 Subject: [PATCH 046/231] Handle NiFogProperty (feature #5173) --- CHANGELOG.md | 1 + components/CMakeLists.txt | 2 +- components/nif/property.hpp | 11 +++++++++++ components/nifosg/fog.cpp | 31 ++++++++++++++++++++++++++++++ components/nifosg/fog.hpp | 29 ++++++++++++++++++++++++++++ components/nifosg/nifloader.cpp | 28 ++++++++++++++++++++++++++- components/sceneutil/serialize.cpp | 15 +++++++++++++++ 7 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 components/nifosg/fog.cpp create mode 100644 components/nifosg/fog.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b36eb2525..e94bf28c32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,6 +104,7 @@ Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders Feature #3537: Shader-based water ripples + Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 7e3c7aea23..f61a5bd0b2 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -130,7 +130,7 @@ add_component_dir (nif ) add_component_dir (nifosg - nifloader controller particle matrixtransform + nifloader controller particle matrixtransform fog ) add_component_dir (nifbullet diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 2506633867..7d420f1650 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -461,11 +461,22 @@ namespace Nif struct NiFogProperty : NiProperty { + enum Flags : uint16_t + { + Enabled = 0x02, + Radial = 0x08, + VertexAlpha = 0x10, + }; + uint16_t mFlags; float mFogDepth; osg::Vec3f mColour; void read(NIFStream* nif) override; + + bool enabled() const { return mFlags & Flags::Enabled; } + bool radial() const { return mFlags & Flags::Radial; } + bool vertexAlpha() const { return mFlags & Flags::VertexAlpha; } }; struct NiMaterialProperty : NiProperty diff --git a/components/nifosg/fog.cpp b/components/nifosg/fog.cpp new file mode 100644 index 0000000000..497193ec42 --- /dev/null +++ b/components/nifosg/fog.cpp @@ -0,0 +1,31 @@ +#include "fog.hpp" + +#include +#include + +namespace NifOsg +{ + + Fog::Fog() + : osg::Fog() + { + } + + Fog::Fog(const Fog& copy, const osg::CopyOp& copyop) + : osg::Fog(copy, copyop) + , mDepth(copy.mDepth) + { + } + + void Fog::apply(osg::State& state) const + { + osg::Fog::apply(state); +#ifdef OSG_GL_FIXED_FUNCTION_AVAILABLE + float fov, aspect, near, far; + state.getProjectionMatrix().getPerspective(fov, aspect, near, far); + glFogf(GL_FOG_START, near * mDepth + far * (1.f - mDepth)); + glFogf(GL_FOG_END, far); +#endif + } + +} diff --git a/components/nifosg/fog.hpp b/components/nifosg/fog.hpp new file mode 100644 index 0000000000..5c49392a24 --- /dev/null +++ b/components/nifosg/fog.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_COMPONENTS_NIFOSG_FOG_H +#define OPENMW_COMPONENTS_NIFOSG_FOG_H + +#include + +namespace NifOsg +{ + + // osg::Fog-based wrapper for NiFogProperty that autocalculates the fog start and end distance. + class Fog : public osg::Fog + { + public: + Fog(); + Fog(const Fog& copy, const osg::CopyOp& copyop); + + META_StateAttribute(NifOsg, Fog, FOG) + + void setDepth(float depth) { mDepth = depth; } + float getDepth() const { return mDepth; } + + void apply(osg::State& state) const override; + + private: + float mDepth{ 1.f }; + }; + +} + +#endif diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 436f2e1d34..c55d580e36 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -56,6 +56,7 @@ #include #include +#include "fog.hpp" #include "matrixtransform.hpp" #include "particle.hpp" @@ -2495,10 +2496,35 @@ namespace NifOsg handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } + case Nif::RC_NiFogProperty: + { + const Nif::NiFogProperty* fogprop = static_cast(property); + osg::StateSet* stateset = node->getOrCreateStateSet(); + // Vertex alpha mode appears to be broken + if (!fogprop->vertexAlpha() && fogprop->enabled()) + { + osg::ref_ptr fog = new NifOsg::Fog; + fog->setMode(osg::Fog::LINEAR); + fog->setColor(osg::Vec4f(fogprop->mColour, 1.f)); + fog->setDepth(fogprop->mFogDepth); + stateset->setAttributeAndModes(fog, osg::StateAttribute::ON); + // Intentionally ignoring radial fog flag + // We don't really want to override the global setting + } + else + { + osg::ref_ptr fog = new osg::Fog; + // Shaders don't respect glDisable(GL_FOG) + fog->setMode(osg::Fog::LINEAR); + fog->setStart(10000000); + fog->setEnd(10000000); + stateset->setAttributeAndModes(fog, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE); + } + break; + } // unused by mw case Nif::RC_NiShadeProperty: case Nif::RC_NiDitherProperty: - case Nif::RC_NiFogProperty: { break; } diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 784dafafa5..8d8acacae4 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -123,6 +124,19 @@ namespace SceneUtil } }; + class FogSerializer : public osgDB::ObjectWrapper + { + public: + FogSerializer() + : osgDB::ObjectWrapper( + createInstanceFunc, "NifOsg::Fog", "osg::Object osg::StateAttribute osg::Fog NifOsg::Fog") + { + addSerializer(new osgDB::PropByValSerializer( + "Depth", 1.f, &NifOsg::Fog::getDepth, &NifOsg::Fog::setDepth), + osgDB::BaseSerializer::RW_FLOAT); + } + }; + osgDB::ObjectWrapper* makeDummySerializer(const std::string& classname) { return new osgDB::ObjectWrapper(createInstanceFunc, classname, "osg::Object"); @@ -153,6 +167,7 @@ namespace SceneUtil mgr->addWrapper(new LightManagerSerializer); mgr->addWrapper(new CameraRelativeTransformSerializer); mgr->addWrapper(new MatrixTransformSerializer); + mgr->addWrapper(new FogSerializer); // Don't serialize Geometry data as we are more interested in the overall structure rather than tons of // vertex data that would make the file large and hard to read. From 0da620b3f9eb73f3ac7e1260a41dc64a8fb64235 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 15:40:23 +0100 Subject: [PATCH 047/231] Don't crash on spells or enchantments without effects --- CHANGELOG.md | 1 + apps/openmw/mwgui/hud.cpp | 26 +++++++++++++++----------- apps/openmw/mwgui/itemwidget.cpp | 2 +- apps/openmw/mwgui/itemwidget.hpp | 2 +- apps/openmw/mwgui/tooltips.cpp | 14 ++++++++------ apps/openmw/mwmechanics/character.cpp | 19 +++++++------------ apps/openmw/mwworld/class.cpp | 4 +--- 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b36eb2525..f549eac686 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders + Bug #7712: Casting doesn't support spells and enchantments with no effects Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index bd38174183..1c8aad5447 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -423,17 +423,21 @@ namespace MWGui mSpellBox->setUserString("Spell", spellId.serialize()); mSpellBox->setUserData(MyGUI::Any::Null); - // use the icon of the first effect - const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( - spell->mEffects.mList.front().mEffectID); - - std::string icon = effect->mIcon; - std::replace(icon.begin(), icon.end(), '/', '\\'); - int slashPos = icon.rfind('\\'); - icon.insert(slashPos + 1, "b_"); - icon = Misc::ResourceHelpers::correctIconPath(icon, MWBase::Environment::get().getResourceSystem()->getVFS()); - - mSpellImage->setSpellIcon(icon); + if (!spell->mEffects.mList.empty()) + { + // use the icon of the first effect + const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find( + spell->mEffects.mList.front().mEffectID); + std::string icon = effect->mIcon; + std::replace(icon.begin(), icon.end(), '/', '\\'); + size_t slashPos = icon.rfind('\\'); + icon.insert(slashPos + 1, "b_"); + icon = Misc::ResourceHelpers::correctIconPath( + icon, MWBase::Environment::get().getResourceSystem()->getVFS()); + mSpellImage->setSpellIcon(icon); + } + else + mSpellImage->setSpellIcon({}); } void HUD::setSelectedEnchantItem(const MWWorld::Ptr& item, int chargePercent) diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index 5ee74c6e87..05fff2d40f 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -202,7 +202,7 @@ namespace MWGui setIcon(ptr); } - void SpellWidget::setSpellIcon(const std::string& icon) + void SpellWidget::setSpellIcon(std::string_view icon) { if (mFrame && !mCurrentFrame.empty()) { diff --git a/apps/openmw/mwgui/itemwidget.hpp b/apps/openmw/mwgui/itemwidget.hpp index 29b0063203..63837ae92f 100644 --- a/apps/openmw/mwgui/itemwidget.hpp +++ b/apps/openmw/mwgui/itemwidget.hpp @@ -58,7 +58,7 @@ namespace MWGui { MYGUI_RTTI_DERIVED(SpellWidget) public: - void setSpellIcon(const std::string& icon); + void setSpellIcon(std::string_view icon); }; } diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index bdcc4e76d7..a13cfe4d77 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -237,13 +237,15 @@ namespace MWGui params.mNoTarget = false; effects.push_back(params); } - if (MWMechanics::spellIncreasesSkill( - spell)) // display school of spells that contribute to skill progress + // display school of spells that contribute to skill progress + if (MWMechanics::spellIncreasesSkill(spell)) { - MWWorld::Ptr player = MWMechanics::getPlayer(); - const auto& school - = store->get().find(MWMechanics::getSpellSchool(spell, player))->mSchool; - info.text = "#{sSchool}: " + MyGUI::TextIterator::toTagsString(school->mName).asUTF8(); + ESM::RefId id = MWMechanics::getSpellSchool(spell, MWMechanics::getPlayer()); + if (!id.empty()) + { + const auto& school = store->get().find(id)->mSchool; + info.text = "#{sSchool}: " + MyGUI::TextIterator::toTagsString(school->mName).asUTF8(); + } } auto cost = focus->getUserString("SpellCost"); if (!cost.empty() && cost != "0") diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 77bc51423e..262283b365 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1605,7 +1605,7 @@ namespace MWMechanics effects = &spell->mEffects.mList; cast.playSpellCastingEffects(spell); } - if (mCanCast) + if (mCanCast && !effects->empty()) { const ESM::MagicEffect* effect = store.get().find( effects->back().mEffectID); // use last effect of list for color of VFX_Hands @@ -1615,18 +1615,13 @@ namespace MWMechanics const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - if (!effects->empty()) - { - if (mAnimation->getNode("Bip01 L Hand")) - mAnimation->addEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, - "Bip01 L Hand", effect->mParticle); + if (mAnimation->getNode("Bip01 L Hand")) + mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), + -1, false, "Bip01 L Hand", effect->mParticle); - if (mAnimation->getNode("Bip01 R Hand")) - mAnimation->addEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, - "Bip01 R Hand", effect->mParticle); - } + if (mAnimation->getNode("Bip01 R Hand")) + mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), + -1, false, "Bip01 R Hand", effect->mParticle); } const ESM::ENAMstruct& firstEffect = effects->at(0); // first effect used for casting animation diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index de3c2b011d..e6080ce447 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -524,11 +524,9 @@ namespace MWWorld const ESM::Enchantment* enchantment = MWBase::Environment::get().getESMStore()->get().search(enchantmentName); - if (!enchantment) + if (!enchantment || enchantment->mEffects.mList.empty()) return result; - assert(enchantment->mEffects.mList.size()); - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getESMStore()->get().search( enchantment->mEffects.mList.front().mEffectID); if (!magicEffect) From 32d391f5485cb63853bb5139b2ab07e2c6a05ce6 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Dec 2023 14:29:26 +0100 Subject: [PATCH 048/231] Revert accumulating movement in the reset accum root callback. --- apps/openmw/mwmechanics/character.cpp | 4 +- apps/openmw/mwphysics/mtphysics.cpp | 8 -- apps/openmw/mwphysics/mtphysics.hpp | 3 - apps/openmw/mwrender/animation.cpp | 89 ++++------------------ apps/openmw/mwrender/animation.hpp | 6 +- apps/openmw/mwrender/creatureanimation.cpp | 4 +- apps/openmw/mwrender/creatureanimation.hpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 4 +- apps/openmw/mwrender/npcanimation.hpp | 2 +- apps/openmw/mwworld/datetimemanager.hpp | 5 -- 10 files changed, 23 insertions(+), 104 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2ef3479cd9..3eb392daf1 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2448,9 +2448,9 @@ namespace MWMechanics bool doMovementAccumulation = isMovementAnimationControlled(); osg::Vec3f movementFromAnimation - = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration, doMovementAccumulation); + = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - if (mPtr.getClass().isActor() && doMovementAccumulation && !isScriptedAnimPlaying()) + if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) { if (duration > 0.0f) movementFromAnimation /= duration; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 09eb309593..deef837992 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -608,7 +608,6 @@ namespace MWPhysics if (mAdvanceSimulation) { - mNextJobSimTime = simulationTimeStart + (numSteps * newDelta); mWorldFrameData = std::make_unique(); mBudgetCursor += 1; } @@ -812,7 +811,6 @@ namespace MWPhysics mLockingPolicy }; for (Simulation& sim : *mSimulations) std::visit(vis, sim); - mCurrentJobSimTime += mPhysicsDt; } bool PhysicsTaskScheduler::hasLineOfSight(const Actor* actor1, const Actor* actor2) @@ -922,9 +920,6 @@ namespace MWPhysics mLOSCache.end()); } - // On paper, mCurrentJobSimTime should have added up to mNextJobSimTime already - // But to avoid accumulating floating point errors, assign this anyway. - mCurrentJobSimTime = mNextJobSimTime; mTimeEnd = mTimer->tick(); if (mWorkersSync != nullptr) mWorkersSync->workIsDone(); @@ -939,9 +934,6 @@ namespace MWPhysics std::visit(vis, sim); mSimulations->clear(); mSimulations = nullptr; - const float interpolationFactor = std::clamp(mTimeAccum / mPhysicsDt, 0.0f, 1.0f); - MWBase::Environment::get().getWorld()->getTimeManager()->setPhysicsSimulationTime( - mCurrentJobSimTime - mPhysicsDt * (1.f - interpolationFactor)); } // Attempt to acquire unique lock on mSimulationMutex while not all worker diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 83d3010359..986f2be973 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -96,9 +96,6 @@ namespace MWPhysics float mDefaultPhysicsDt; float mPhysicsDt; float mTimeAccum; - float mNextJobSimTime = 0.f; - float mCurrentJobSimTime = 0.f; - float mPreviousJobSimTime = 0.f; btCollisionWorld* mCollisionWorld; MWRender::DebugDrawer* mDebugDrawer; std::vector mLOSCache; diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e12e8212e6..e780a30fbb 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -49,7 +49,6 @@ #include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" -#include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/character.hpp" // FIXME: for MWMechanics::Priority @@ -490,23 +489,14 @@ namespace MWRender class ResetAccumRootCallback : public SceneUtil::NodeCallback { - struct AccumulatedMovement - { - osg::Vec3f mMovement = osg::Vec3f(); - float mSimStartTime = 0.f; - float mSimStopTime = 0.f; - }; - public: void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { - osg::Matrix mat = transform->getMatrix(); - osg::Vec3f position = mat.getTrans(); - position = osg::componentMultiply(mResetAxes, position); - // Add back the offset that the movement solver has not consumed yet - position += computeRemainder(); - mat.setTrans(position); - transform->setMatrix(mat); + osg::Matrix mat = transform->getMatrix(); + osg::Vec3f position = mat.getTrans(); + position = osg::componentMultiply(mResetAxes, position); + mat.setTrans(position); + transform->setMatrix(mat); traverse(transform, nv); } @@ -519,35 +509,7 @@ namespace MWRender mResetAxes.z() = accumulate.z() != 0.f ? 0.f : 1.f; } - void accumulate(const osg::Vec3f& movement, float dt) - { - if (dt < 0.00001f) - return; - - float simTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); - mMovement.emplace_back(AccumulatedMovement{ movement, simTime, simTime + dt }); - } - - const osg::Vec3f computeRemainder() - { - float physSimTime = MWBase::Environment::get().getWorld()->getTimeManager()->getPhysicsSimulationTime(); - // Start by erasing all movement that has been fully consumed by the physics code - std::erase_if(mMovement, - [physSimTime](const AccumulatedMovement& movement) { return movement.mSimStopTime <= physSimTime; }); - - // Accumulate all the movement that hasn't been consumed. - osg::Vec3f movement; - for (const auto& m : mMovement) - { - float startTime = std::max(physSimTime, m.mSimStartTime); - float fraction = (m.mSimStopTime - startTime) / (m.mSimStopTime - m.mSimStartTime); - movement += m.mMovement * fraction; - } - return movement; - } - private: - std::deque mMovement; osg::Vec3f mResetAxes; }; @@ -1167,37 +1129,15 @@ namespace MWRender return velocity; } - void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement) + void Animation::updatePosition(float oldtime, float newtime, osg::Vec3f& position) { // Get the difference from the last update, and move the position - osg::Vec3f offset = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); - if (!hasMovement) - { - // When animations have no velocity, the character should have zero net movement through a complete loop or - // animation sequence. Although any subsequence of the animation may move. This works because each sequence - // starts and stops with Bip01 at the same position, totaling 0 movement. This allows us to accurately move - // the character by just moving it from the position Bip01 was last frame to where it is this frame, without - // needing to accumulate anything in-between. - if (mPreviousAccumulatePosition) - { - position += offset - mPreviousAccumulatePosition.value(); - } - mPreviousAccumulatePosition = offset; - } - else - { - // When animations have velocity, net movement is expected. The above block would negate that movement every - // time the animation resets. Therefore we have to accumulate from oldtime to newtime instead, which works - // because oldtime < newtime is a guarantee even when the animation has looped. - position += offset - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); - } + osg::Vec3f off = osg::componentMultiply(mAccumCtrl->getTranslation(newtime), mAccumulate); + position += off - osg::componentMultiply(mAccumCtrl->getTranslation(oldtime), mAccumulate); } - osg::Vec3f Animation::runAnimation(float duration, bool accumulateMovement) + osg::Vec3f Animation::runAnimation(float duration) { - if (!accumulateMovement) - mPreviousAccumulatePosition = std::nullopt; - osg::Vec3f movement(0.f, 0.f, 0.f); AnimStateMap::iterator stateiter = mStates.begin(); while (stateiter != mStates.end()) @@ -1211,7 +1151,6 @@ namespace MWRender const SceneUtil::TextKeyMap& textkeys = state.mSource->getTextKeys(); auto textkey = textkeys.upperBound(state.getTime()); - bool hasMovement = getVelocity(stateiter->first) > 0.001f; float timepassed = duration * state.mSpeedMult; while (state.mPlaying) @@ -1221,14 +1160,14 @@ namespace MWRender float targetTime = state.getTime() + timepassed; if (textkey == textkeys.end() || textkey->first > targetTime) { - if (accumulateMovement && mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), targetTime, movement, hasMovement); + if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), targetTime, movement); state.setTime(std::min(targetTime, state.mStopTime)); } else { - if (accumulateMovement && mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) - updatePosition(state.getTime(), textkey->first, movement, hasMovement); + if (mAccumCtrl && state.mTime == mAnimationTimePtr[0]->getTimePtr()) + updatePosition(state.getTime(), textkey->first, movement); state.setTime(textkey->first); } @@ -1311,8 +1250,6 @@ namespace MWRender } - if (accumulateMovement && mResetAccumRootCallback) - mResetAccumRootCallback->accumulate(movement, duration); return movement; } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 8e159700ea..24366889c4 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -266,7 +265,6 @@ namespace MWRender Resource::ResourceSystem* mResourceSystem; osg::Vec3f mAccumulate; - std::optional mPreviousAccumulatePosition; TextKeyListener* mTextKeyListener; @@ -306,7 +304,7 @@ namespace MWRender /* Updates the position of the accum root node for the given time, and * returns the wanted movement vector from the previous time. */ - void updatePosition(float oldtime, float newtime, osg::Vec3f& position, bool hasMovement); + void updatePosition(float oldtime, float newtime, osg::Vec3f& position); /* Resets the animation to the time of the specified start marker, without * moving anything, and set the end time to the specified stop marker. If @@ -466,7 +464,7 @@ namespace MWRender /** Retrieves the velocity (in units per second) that the animation will move. */ float getVelocity(std::string_view groupname) const; - virtual osg::Vec3f runAnimation(float duration, bool accumulateMovement = false); + virtual osg::Vec3f runAnimation(float duration); void setLoopingEnabled(std::string_view groupname, bool enabled); diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 65f48f5ade..7767520715 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -258,9 +258,9 @@ namespace MWRender WeaponAnimation::addControllers(mNodeMap, mActiveControllers, mObjectRoot.get()); } - osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration, bool accumulateMovement) + osg::Vec3f CreatureWeaponAnimation::runAnimation(float duration) { - osg::Vec3f ret = Animation::runAnimation(duration, accumulateMovement); + osg::Vec3f ret = Animation::runAnimation(duration); WeaponAnimation::configureControllers(mPtr.getRefData().getPosition().rot[0] + getBodyPitchRadians()); diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index 9ff3d55375..05235e5191 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -59,7 +59,7 @@ namespace MWRender void addControllers() override; - osg::Vec3f runAnimation(float duration, bool accumulateMovement = false) override; + osg::Vec3f runAnimation(float duration) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 951d16aefe..469978e6eb 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -693,9 +693,9 @@ namespace MWRender return std::make_unique(attached); } - osg::Vec3f NpcAnimation::runAnimation(float timepassed, bool accumulateMovement) + osg::Vec3f NpcAnimation::runAnimation(float timepassed) { - osg::Vec3f ret = Animation::runAnimation(timepassed, accumulateMovement); + osg::Vec3f ret = Animation::runAnimation(timepassed); mHeadAnimationTime->update(timepassed); diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index 0c2d67db86..a03ee28f3a 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -130,7 +130,7 @@ namespace MWRender void setWeaponGroup(const std::string& group, bool relativeDuration) override; - osg::Vec3f runAnimation(float timepassed, bool accumulateMovement = false) override; + osg::Vec3f runAnimation(float timepassed) override; /// A relative factor (0-1) that decides if and how much the skeleton should be pitched /// to indicate the facing orientation of the character. diff --git a/apps/openmw/mwworld/datetimemanager.hpp b/apps/openmw/mwworld/datetimemanager.hpp index 20d968f4ea..af62d9ba3f 100644 --- a/apps/openmw/mwworld/datetimemanager.hpp +++ b/apps/openmw/mwworld/datetimemanager.hpp @@ -29,10 +29,6 @@ namespace MWWorld float getGameTimeScale() const { return mGameTimeScale; } void setGameTimeScale(float scale); // game time to simulation time ratio - // Physics simulation time - double getPhysicsSimulationTime() const { return mPhysicsSimulationTime; } - void setPhysicsSimulationTime(double t) { mPhysicsSimulationTime = t; } - // Rendering simulation time (summary simulation time of rendering frames since application start). double getRenderingSimulationTime() const { return mRenderingSimulationTime; } void setRenderingSimulationTime(double t) { mRenderingSimulationTime = t; } @@ -74,7 +70,6 @@ namespace MWWorld float mSimulationTimeScale = 1.0; double mRenderingSimulationTime = 0.0; double mSimulationTime = 0.0; - double mPhysicsSimulationTime = 0.0; bool mPaused = false; std::set> mPausedTags; }; From 4062f0225be17ef4f929be713747d49bd767f323 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 16:13:56 +0100 Subject: [PATCH 049/231] Use the right getContainerStore --- apps/openmw/mwclass/container.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 0efbbc84fd..a985fe6d71 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -306,7 +306,7 @@ namespace MWClass if (newPtr.getRefData().getCustomData()) { MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); - newPtr.getContainerStore()->setPtr(newPtr); + getContainerStore(newPtr).setPtr(newPtr); } return newPtr; } From c79446818ef27a802d016e4e41e2e40d1970afe7 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Dec 2023 16:48:04 +0100 Subject: [PATCH 050/231] Add a flag for jump when queueing movement, so inertia can be added accurately. --- apps/openmw/mwbase/world.hpp | 15 +++++++++---- apps/openmw/mwmechanics/character.cpp | 7 ++++--- apps/openmw/mwphysics/mtphysics.cpp | 28 ++++++++++++++++++++++++- apps/openmw/mwphysics/physicssystem.cpp | 4 ++-- apps/openmw/mwphysics/physicssystem.hpp | 2 +- apps/openmw/mwphysics/ptrholder.hpp | 5 +++-- apps/openmw/mwworld/worldimp.cpp | 4 ++-- apps/openmw/mwworld/worldimp.hpp | 4 +++- 8 files changed, 53 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f67b9a0e05..8670202965 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -295,9 +295,13 @@ namespace MWBase /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is /// obstructed). - virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) = 0; + virtual void queueMovement( + const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false) + = 0; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. + /// \param duration The duration this speed shall be held, starting at current simulation time + /// \param jump Whether the movement shall be run over time, or immediately added as inertia instead virtual void updateAnimatedCollisionShape(const MWWorld::Ptr& ptr) = 0; @@ -567,7 +571,8 @@ namespace MWBase virtual DetourNavigator::Navigator* getNavigator() const = 0; virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, - const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const = 0; + const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const + = 0; virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0; @@ -576,10 +581,12 @@ namespace MWBase virtual DetourNavigator::AgentBounds getPathfindingAgentBounds(const MWWorld::ConstPtr& actor) const = 0; virtual bool hasCollisionWithDoor( - const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; + const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const + = 0; virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, - std::span ignore, std::vector* occupyingActors = nullptr) const = 0; + std::span ignore, std::vector* occupyingActors = nullptr) const + = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 3eb392daf1..2aece5f86f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2420,7 +2420,9 @@ namespace MWMechanics } if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - world->queueMovement(mPtr, vec, duration); + { + world->queueMovement(mPtr, vec, duration, mInJump && mJumpState == JumpState_None); + } } movement = vec; @@ -2446,7 +2448,6 @@ namespace MWMechanics } } - bool doMovementAccumulation = isMovementAnimationControlled(); osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); @@ -2494,7 +2495,7 @@ namespace MWMechanics } // Update movement - world->queueMovement(mPtr, movement, duration); + world->queueMovement(mPtr, movement, duration, mInJump && mJumpState == JumpState_None); } mSkipAnim = false; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index deef837992..cc0c38a471 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -210,6 +210,13 @@ namespace auto it = actor.movement().begin(); while (it != actor.movement().end()) { + if (it->jump) + { + // Adjusting inertia is instant and should not be performed over time like other movement is. + it++; + continue; + } + float start = std::max(it->simulationTimeStart, startTime); float stop = std::min(it->simulationTimeStop, endTime); movement += it->velocity * (stop - start); @@ -222,6 +229,23 @@ namespace return movement; } + std::optional takeInertia(MWPhysics::PtrHolder& actor, float startTime) const + { + std::optional inertia = std::nullopt; + auto it = actor.movement().begin(); + while (it != actor.movement().end()) + { + if (it->jump && it->simulationTimeStart >= startTime) + { + inertia = it->velocity; + it = actor.movement().erase(it); + } + else + it++; + } + return inertia; + } + void operator()(auto& sim) const { if (mSteps <= 0 || mDelta < 0.00001f) @@ -237,8 +261,10 @@ namespace // movement solver osg::Vec3f velocity = takeMovement(*ptrHolder, mSimulationTime, mSimulationTime + mDelta * mSteps) / (mSteps * mDelta); + // takeInertia() returns a velocity and should be taken over the velocity calculated above to initiate a jump + auto inertia = takeInertia(*ptrHolder, mSimulationTime); - frameDataRef.get().mMovement += velocity; + frameDataRef.get().mMovement += inertia.value_or(velocity); } }; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 3c451497e1..6f57c7db01 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -624,12 +624,12 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration) + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) { float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) - found->second->queueMovement(velocity, start, start + duration); + found->second->queueMovement(velocity, start, start + duration, jump); } void PhysicsSystem::clearQueuedMovement() diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 3babdef9aa..7729f274f1 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -245,7 +245,7 @@ namespace MWPhysics /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation - void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration); + void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 5884372e42..d7a6f887bb 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -19,6 +19,7 @@ namespace MWPhysics osg::Vec3f velocity = osg::Vec3f(); float simulationTimeStart = 0.f; // The time at which this movement begun float simulationTimeStop = 0.f; // The time at which this movement finished + bool jump = false; }; class PtrHolder @@ -41,9 +42,9 @@ namespace MWPhysics btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } void clearMovement() { mMovement = {}; } - void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop) + void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop, bool jump = false) { - mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop }); + mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop, jump }); } std::deque& movement() { return mMovement; } diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 9db72d1604..b6b7de24b4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1448,9 +1448,9 @@ namespace MWWorld return placed; } - void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) + void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) { - mPhysics->queueObjectMovement(ptr, velocity, duration); + mPhysics->queueObjectMovement(ptr, velocity, duration, jump); } void World::updateAnimatedCollisionShape(const Ptr& ptr) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 486e240fdc..aa5f9d56f0 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -383,9 +383,11 @@ namespace MWWorld float getMaxActivationDistance() const override; - void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration) override; + void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false) override; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. + /// \param duration The duration this speed shall be held, starting at current simulation time + /// \param jump Whether the movement shall be run over time, or immediately added as inertia instead void updateAnimatedCollisionShape(const Ptr& ptr) override; From 521cff08f83e7e616a8d87980550f7030c0a9862 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 17:22:11 +0100 Subject: [PATCH 051/231] Drop support for save game format 1 --- apps/openmw/mwworld/weather.cpp | 47 +++++++++++++------------------ components/esm3/creaturestats.cpp | 2 -- components/esm3/formatversion.hpp | 3 +- 3 files changed, 20 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 5d739a9161..aa75730b40 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -898,36 +898,27 @@ namespace MWWorld { if (ESM::REC_WTHR == type) { - if (reader.getFormatVersion() <= ESM::MaxOldWeatherFormatVersion) + ESM::WeatherState state; + state.load(reader); + + std::swap(mCurrentRegion, state.mCurrentRegion); + mTimePassed = state.mTimePassed; + mFastForward = state.mFastForward; + mWeatherUpdateTime = state.mWeatherUpdateTime; + mTransitionFactor = state.mTransitionFactor; + mCurrentWeather = state.mCurrentWeather; + mNextWeather = state.mNextWeather; + mQueuedWeather = state.mQueuedWeather; + + mRegions.clear(); + importRegions(); + + for (auto it = state.mRegions.begin(); it != state.mRegions.end(); ++it) { - // Weather state isn't really all that important, so to preserve older save games, we'll just discard - // the older weather records, rather than fail to handle the record. - reader.skipRecord(); - } - else - { - ESM::WeatherState state; - state.load(reader); - - std::swap(mCurrentRegion, state.mCurrentRegion); - mTimePassed = state.mTimePassed; - mFastForward = state.mFastForward; - mWeatherUpdateTime = state.mWeatherUpdateTime; - mTransitionFactor = state.mTransitionFactor; - mCurrentWeather = state.mCurrentWeather; - mNextWeather = state.mNextWeather; - mQueuedWeather = state.mQueuedWeather; - - mRegions.clear(); - importRegions(); - - for (auto it = state.mRegions.begin(); it != state.mRegions.end(); ++it) + auto found = mRegions.find(it->first); + if (found != mRegions.end()) { - auto found = mRegions.find(it->first); - if (found != mRegions.end()) - { - found->second = RegionWeather(it->second); - } + found->second = RegionWeather(it->second); } } diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index 8281916e30..a393da697f 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -49,8 +49,6 @@ namespace ESM esm.getHNOT(mTalkedTo, "TALK"); esm.getHNOT(mAlarmed, "ALRM"); esm.getHNOT(mAttacked, "ATKD"); - if (esm.isNextSub("ATCK")) - esm.skipHSub(); // attackingOrSpell, no longer used esm.getHNOT(mKnockdown, "KNCK"); esm.getHNOT(mKnockdownOneFrame, "KNC1"); esm.getHNOT(mKnockdownOverOneFrame, "KNCO"); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index e02e0176a9..82fff88614 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -9,7 +9,6 @@ namespace ESM inline constexpr FormatVersion DefaultFormatVersion = 0; inline constexpr FormatVersion CurrentContentFormatVersion = 1; - inline constexpr FormatVersion MaxOldWeatherFormatVersion = 1; inline constexpr FormatVersion MaxOldDeathAnimationFormatVersion = 2; inline constexpr FormatVersion MaxOldFogOfWarFormatVersion = 6; inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7; @@ -28,7 +27,7 @@ namespace ESM inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 1; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 2; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } From becc5ef8fa96640b7952d65420708174a61cd5a4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 17:45:42 +0100 Subject: [PATCH 052/231] Drop support for save game format 2 --- components/esm3/creaturestats.cpp | 2 -- components/esm3/formatversion.hpp | 3 +-- components/esm3/inventorystate.cpp | 18 ++++-------------- components/esm3/inventorystate.hpp | 2 -- components/esm3/objectstate.cpp | 3 --- 5 files changed, 5 insertions(+), 23 deletions(-) diff --git a/components/esm3/creaturestats.cpp b/components/esm3/creaturestats.cpp index a393da697f..44c3bd993b 100644 --- a/components/esm3/creaturestats.cpp +++ b/components/esm3/creaturestats.cpp @@ -42,8 +42,6 @@ namespace ESM { esm.getHNOT(mDead, "DEAD"); esm.getHNOT(mDeathAnimationFinished, "DFNT"); - if (esm.getFormatVersion() <= MaxOldDeathAnimationFormatVersion && mDead) - mDeathAnimationFinished = true; esm.getHNOT(mDied, "DIED"); esm.getHNOT(mMurdered, "MURD"); esm.getHNOT(mTalkedTo, "TALK"); diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 82fff88614..854502b220 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -9,7 +9,6 @@ namespace ESM inline constexpr FormatVersion DefaultFormatVersion = 0; inline constexpr FormatVersion CurrentContentFormatVersion = 1; - inline constexpr FormatVersion MaxOldDeathAnimationFormatVersion = 2; inline constexpr FormatVersion MaxOldFogOfWarFormatVersion = 6; inline constexpr FormatVersion MaxUnoptimizedCharacterDataFormatVersion = 7; inline constexpr FormatVersion MaxOldTimeLeftFormatVersion = 8; @@ -27,7 +26,7 @@ namespace ESM inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 2; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 3; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index a6130af473..ded2d1c33b 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -52,20 +52,17 @@ namespace ESM mItems.push_back(state); } + std::map, int32_t> levelledItemMap; // Next item is Levelled item while (esm.isNextSub("LEVM")) { // Get its name ESM::RefId id = esm.getRefId(); int32_t count; - std::string parentGroup; // Then get its count esm.getHNT(count, "COUN"); - // Old save formats don't have information about parent group; check for that - if (esm.isNextSub("LGRP")) - // Newest saves contain parent group - parentGroup = esm.getHString(); - mLevelledItemMap[std::make_pair(id, parentGroup)] = count; + std::string parentGroup = esm.getHNString("LGRP"); + levelledItemMap[std::make_pair(id, parentGroup)] = count; } while (esm.isNextSub("MAGI")) @@ -117,7 +114,7 @@ namespace ESM // Old saves had restocking levelled items in a special map // This turns items from that map into negative quantities - for (const auto& entry : mLevelledItemMap) + for (const auto& entry : levelledItemMap) { const ESM::RefId& id = entry.first.first; const int count = entry.second; @@ -141,13 +138,6 @@ namespace ESM } } - for (auto it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) - { - esm.writeHNRefId("LEVM", it->first.first); - esm.writeHNT("COUN", it->second); - esm.writeHNString("LGRP", it->first.second); - } - for (const auto& [id, params] : mPermanentMagicEffectMagnitudes) { esm.writeHNRefId("MAGI", id); diff --git a/components/esm3/inventorystate.hpp b/components/esm3/inventorystate.hpp index 050d1eb92f..814236ce46 100644 --- a/components/esm3/inventorystate.hpp +++ b/components/esm3/inventorystate.hpp @@ -22,8 +22,6 @@ namespace ESM // std::map mEquipmentSlots; - std::map, int32_t> mLevelledItemMap; - std::map>> mPermanentMagicEffectMagnitudes; std::optional mSelectedEnchantItem; // For inventories only diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index fca4c64f5f..6e2621df29 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -42,9 +42,6 @@ namespace ESM else mPosition = mRef.mPos; - if (esm.isNextSub("LROT")) - esm.skipHSub(); // local rotation, no longer used - mFlags = 0; esm.getHNOT(mFlags, "FLAG"); From b0ef42ae3ccbd5cbbb2acc6e09586c114f4c89c2 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 18:05:57 +0100 Subject: [PATCH 053/231] Drop support for save game format 3 --- components/esm3/animationstate.cpp | 13 +------------ components/esm3/formatversion.hpp | 2 +- components/esm3/projectilestate.cpp | 7 ------- 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/components/esm3/animationstate.cpp b/components/esm3/animationstate.cpp index 14edf01a83..79dcad7578 100644 --- a/components/esm3/animationstate.cpp +++ b/components/esm3/animationstate.cpp @@ -21,18 +21,7 @@ namespace ESM anim.mGroup = esm.getHString(); esm.getHNOT(anim.mTime, "TIME"); esm.getHNOT(anim.mAbsolute, "ABST"); - - esm.getSubNameIs("COUN"); - // workaround bug in earlier version where size_t was used - esm.getSubHeader(); - if (esm.getSubSize() == 8) - esm.getT(anim.mLoopCount); - else - { - uint32_t loopcount; - esm.getT(loopcount); - anim.mLoopCount = (uint64_t)loopcount; - } + esm.getHNT(anim.mLoopCount, "COUN"); mScriptedAnims.push_back(anim); } diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 854502b220..949cdadc38 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -26,7 +26,7 @@ namespace ESM inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 3; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } diff --git a/components/esm3/projectilestate.cpp b/components/esm3/projectilestate.cpp index 15ff5fff64..bed9073999 100644 --- a/components/esm3/projectilestate.cpp +++ b/components/esm3/projectilestate.cpp @@ -37,18 +37,11 @@ namespace ESM BaseProjectileState::load(esm); mSpellId = esm.getHNRefId("SPEL"); - if (esm.isNextSub("SRCN")) // for backwards compatibility - esm.skipHSub(); - EffectList().load(esm); // for backwards compatibility esm.getHNT(mSpeed, "SPED"); if (esm.peekNextSub("ITEM")) mItem = esm.getFormId(true, "ITEM"); if (esm.isNextSub("SLOT")) // for backwards compatibility esm.skipHSub(); - if (esm.isNextSub("STCK")) // for backwards compatibility - esm.skipHSub(); - if (esm.isNextSub("SOUN")) // for backwards compatibility - esm.skipHSub(); } void ProjectileState::save(ESMWriter& esm) const From 41dc409238c59d9159f5f6aa95cb99b9cb7ef40d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 18:20:10 +0100 Subject: [PATCH 054/231] Don't consider empty effect lists exceptional --- apps/openmw/mwmechanics/character.cpp | 104 ++++++++++++++------------ 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 262283b365..713add719b 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1605,61 +1605,67 @@ namespace MWMechanics effects = &spell->mEffects.mList; cast.playSpellCastingEffects(spell); } - if (mCanCast && !effects->empty()) + if (!effects->empty()) { - const ESM::MagicEffect* effect = store.get().find( - effects->back().mEffectID); // use last effect of list for color of VFX_Hands - - const ESM::Static* castStatic - = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); - - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - - if (mAnimation->getNode("Bip01 L Hand")) - mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), - -1, false, "Bip01 L Hand", effect->mParticle); - - if (mAnimation->getNode("Bip01 R Hand")) - mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), - -1, false, "Bip01 R Hand", effect->mParticle); - } - - const ESM::ENAMstruct& firstEffect = effects->at(0); // first effect used for casting animation - - std::string startKey; - std::string stopKey; - if (isRandomAttackAnimation(mCurrentWeapon)) - { - startKey = "start"; - stopKey = "stop"; if (mCanCast) - world->castSpell( - mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately - mCastingManualSpell = false; - mCanCast = false; - } - else - { - switch (firstEffect.mRange) { - case 0: - mAttackType = "self"; - break; - case 1: - mAttackType = "touch"; - break; - case 2: - mAttackType = "target"; - break; + const ESM::MagicEffect* effect = store.get().find( + effects->back().mEffectID); // use last effect of list for color of VFX_Hands + + const ESM::Static* castStatic + = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); + + const VFS::Manager* const vfs + = MWBase::Environment::get().getResourceSystem()->getVFS(); + + if (mAnimation->getNode("Bip01 L Hand")) + mAnimation->addEffect( + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, + "Bip01 L Hand", effect->mParticle); + + if (mAnimation->getNode("Bip01 R Hand")) + mAnimation->addEffect( + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, + "Bip01 R Hand", effect->mParticle); + } + // first effect used for casting animation + const ESM::ENAMstruct& firstEffect = effects->front(); + + std::string startKey; + std::string stopKey; + if (isRandomAttackAnimation(mCurrentWeapon)) + { + startKey = "start"; + stopKey = "stop"; + if (mCanCast) + world->castSpell( + mPtr, mCastingManualSpell); // No "release" text key to use, so cast immediately + mCastingManualSpell = false; + mCanCast = false; + } + else + { + switch (firstEffect.mRange) + { + case 0: + mAttackType = "self"; + break; + case 1: + mAttackType = "touch"; + break; + case 2: + mAttackType = "target"; + break; + } + + startKey = mAttackType + " start"; + stopKey = mAttackType + " stop"; } - startKey = mAttackType + " start"; - stopKey = mAttackType + " stop"; + mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, + 1, startKey, stopKey, 0.0f, 0); + mUpperBodyState = UpperBodyState::Casting; } - - mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, false, 1, - startKey, stopKey, 0.0f, 0); - mUpperBodyState = UpperBodyState::Casting; } else { From 7b8c0d1d88d8ba5f2d3661e443d4279bc2e9addc Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 9 Dec 2023 19:00:42 +0100 Subject: [PATCH 055/231] Remove dropped formats from tests --- apps/openmw_test_suite/mwworld/test_store.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw_test_suite/mwworld/test_store.cpp b/apps/openmw_test_suite/mwworld/test_store.cpp index f80c12917c..b63c0902ab 100644 --- a/apps/openmw_test_suite/mwworld/test_store.cpp +++ b/apps/openmw_test_suite/mwworld/test_store.cpp @@ -270,8 +270,6 @@ namespace std::vector result({ ESM::DefaultFormatVersion, ESM::CurrentContentFormatVersion, - ESM::MaxOldWeatherFormatVersion, - ESM::MaxOldDeathAnimationFormatVersion, ESM::MaxOldFogOfWarFormatVersion, ESM::MaxUnoptimizedCharacterDataFormatVersion, ESM::MaxOldTimeLeftFormatVersion, From f269b25bd07fb1830984e6f1a7607288ef8772e2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 9 Dec 2023 22:00:35 +0300 Subject: [PATCH 056/231] Remove unused field --- apps/openmw/mwrender/globalmap.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index e0582b20fa..07d7731e31 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -111,8 +111,6 @@ namespace MWRender ImageDestMap mPendingImageDest; - std::vector> mExploredCells; - osg::ref_ptr mBaseTexture; osg::ref_ptr mAlphaTexture; From 76232c49df5458a67a7161ebeb6f6bfeb2efb2f8 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 9 Dec 2023 20:08:14 +0100 Subject: [PATCH 057/231] clang format --- apps/openmw/mwbase/world.hpp | 9 +++------ apps/openmw/mwphysics/mtphysics.cpp | 3 ++- apps/openmw/mwphysics/physicssystem.cpp | 3 ++- apps/openmw/mwphysics/physicssystem.hpp | 3 ++- apps/openmw/mwrender/animation.cpp | 11 +++++------ 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 8670202965..23961554ca 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -571,8 +571,7 @@ namespace MWBase virtual DetourNavigator::Navigator* getNavigator() const = 0; virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque& path, - const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const - = 0; + const DetourNavigator::AgentBounds& agentBounds, const osg::Vec3f& start, const osg::Vec3f& end) const = 0; virtual void removeActorPath(const MWWorld::ConstPtr& actor) const = 0; @@ -581,12 +580,10 @@ namespace MWBase virtual DetourNavigator::AgentBounds getPathfindingAgentBounds(const MWWorld::ConstPtr& actor) const = 0; virtual bool hasCollisionWithDoor( - const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const - = 0; + const MWWorld::ConstPtr& door, const osg::Vec3f& position, const osg::Vec3f& destination) const = 0; virtual bool isAreaOccupiedByOtherActor(const osg::Vec3f& position, const float radius, - std::span ignore, std::vector* occupyingActors = nullptr) const - = 0; + std::span ignore, std::vector* occupyingActors = nullptr) const = 0; virtual void reportStats(unsigned int frameNumber, osg::Stats& stats) const = 0; diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index cc0c38a471..9a4ec2cba2 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -261,7 +261,8 @@ namespace // movement solver osg::Vec3f velocity = takeMovement(*ptrHolder, mSimulationTime, mSimulationTime + mDelta * mSteps) / (mSteps * mDelta); - // takeInertia() returns a velocity and should be taken over the velocity calculated above to initiate a jump + // takeInertia() returns a velocity and should be taken over the velocity calculated above to initiate a + // jump auto inertia = takeInertia(*ptrHolder, mSimulationTime); frameDataRef.get().mMovement += inertia.value_or(velocity); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 6f57c7db01..9a85ee009f 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -624,7 +624,8 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) + void PhysicsSystem::queueObjectMovement( + const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) { float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); ActorMap::iterator found = mActors.find(ptr.mRef); diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 7729f274f1..7758c6dfd7 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -245,7 +245,8 @@ namespace MWPhysics /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation - void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false); + void queueObjectMovement( + const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index e780a30fbb..5b0e1f82bd 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -492,11 +492,11 @@ namespace MWRender public: void operator()(osg::MatrixTransform* transform, osg::NodeVisitor* nv) { - osg::Matrix mat = transform->getMatrix(); - osg::Vec3f position = mat.getTrans(); - position = osg::componentMultiply(mResetAxes, position); - mat.setTrans(position); - transform->setMatrix(mat); + osg::Matrix mat = transform->getMatrix(); + osg::Vec3f position = mat.getTrans(); + position = osg::componentMultiply(mResetAxes, position); + mat.setTrans(position); + transform->setMatrix(mat); traverse(transform, nv); } @@ -1249,7 +1249,6 @@ namespace MWRender osg::Quat(mHeadPitchRadians, osg::Vec3f(1, 0, 0)) * osg::Quat(yaw, osg::Vec3f(0, 0, 1))); } - return movement; } From ca19f7006c7e6f129ce2c03241eb214f3c7fd20f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 2 Dec 2023 11:15:54 +0400 Subject: [PATCH 058/231] Make hardcoded strings in Launcher and Wizard localizable --- apps/launcher/datafilespage.cpp | 27 +++++++++++++------ apps/launcher/graphicspage.cpp | 18 ++++++++++--- apps/wizard/installationpage.cpp | 3 ++- apps/wizard/languageselectionpage.cpp | 12 +++++---- apps/wizard/mainwizard.cpp | 5 ++-- .../contentselector/view/contentselector.cpp | 2 +- 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 4ad99c99f1..dc2c07d9bd 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -164,11 +164,14 @@ Launcher::DataFilesPage::DataFilesPage(const Files::ConfigurationManager& cfg, C const QString encoding = mGameSettings.value("encoding", "win1252"); mSelector->setEncoding(encoding); - QStringList languages; - languages << tr("English") << tr("French") << tr("German") << tr("Italian") << tr("Polish") << tr("Russian") - << tr("Spanish"); + QVector> languages = { { "English", tr("English") }, { "French", tr("French") }, + { "German", tr("German") }, { "Italian", tr("Italian") }, { "Polish", tr("Polish") }, + { "Russian", tr("Russian") }, { "Spanish", tr("Spanish") } }; - mSelector->languageBox()->addItems(languages); + for (auto lang : languages) + { + mSelector->languageBox()->addItem(lang.second, lang.first); + } mNewProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); mCloneProfileDialog = new TextInputDialog(tr("Clone Content List"), tr("Content List name:"), this); @@ -254,9 +257,17 @@ bool Launcher::DataFilesPage::loadSettings() if (!currentProfile.isEmpty()) addProfile(currentProfile, true); - const int index = mSelector->languageBox()->findText(mLauncherSettings.getLanguage()); - if (index != -1) - mSelector->languageBox()->setCurrentIndex(index); + auto language = mLauncherSettings.getLanguage(); + + for (int i = 0; i < mSelector->languageBox()->count(); ++i) + { + QString languageItem = mSelector->languageBox()->itemData(i).toString(); + if (language == languageItem) + { + mSelector->languageBox()->setCurrentIndex(i); + break; + } + } return true; } @@ -386,7 +397,7 @@ void Launcher::DataFilesPage::saveSettings(const QString& profile) mLauncherSettings.setContentList(profileName, dirList, selectedArchivePaths(), fileNames); mGameSettings.setContentList(dirList, selectedArchivePaths(), fileNames); - QString language(mSelector->languageBox()->currentText()); + QString language(mSelector->languageBox()->currentData().toString()); mLauncherSettings.setLanguage(language); diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 84d5049d6c..fa9d5eb479 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -154,8 +154,13 @@ bool Launcher::GraphicsPage::loadSettings() if (Settings::shadows().mEnableIndoorShadows) indoorShadowsCheckBox->setCheckState(Qt::Checked); - shadowComputeSceneBoundsComboBox->setCurrentIndex( - shadowComputeSceneBoundsComboBox->findText(QString(tr(Settings::shadows().mComputeSceneBounds.get().c_str())))); + auto boundMethod = Settings::shadows().mComputeSceneBounds.get(); + if (boundMethod == "bounds") + shadowComputeSceneBoundsComboBox->setCurrentIndex(0); + else if (boundMethod == "primitives") + shadowComputeSceneBoundsComboBox->setCurrentIndex(1); + else + shadowComputeSceneBoundsComboBox->setCurrentIndex(2); const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance; if (shadowDistLimit > 0) @@ -254,7 +259,14 @@ void Launcher::GraphicsPage::saveSettings() Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked); Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt()); - Settings::shadows().mComputeSceneBounds.set(shadowComputeSceneBoundsComboBox->currentText().toStdString()); + + auto index = shadowComputeSceneBoundsComboBox->currentIndex(); + if (index == 0) + Settings::shadows().mComputeSceneBounds.set("bounds"); + else if (index == 1) + Settings::shadows().mComputeSceneBounds.set("primitives"); + else + Settings::shadows().mComputeSceneBounds.set("none"); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index cf2e3671e1..2dd796ab3f 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -1,5 +1,6 @@ #include "installationpage.hpp" +#include #include #include #include @@ -123,7 +124,7 @@ void Wizard::InstallationPage::startInstallation() mUnshield->setPath(path); // Set the right codec to use for Morrowind.ini - QString language(field(QLatin1String("installation.language")).toString()); + QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); if (language == QLatin1String("Polish")) { diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 9808d3c56c..38050b1cab 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -14,12 +14,14 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget* parent) void Wizard::LanguageSelectionPage::initializePage() { - QStringList languages; - languages << QLatin1String("English") << QLatin1String("French") << QLatin1String("German") - << QLatin1String("Italian") << QLatin1String("Polish") << QLatin1String("Russian") - << QLatin1String("Spanish"); + QVector> languages = { { "English", tr("English") }, { "French", tr("French") }, + { "German", tr("German") }, { "Italian", tr("Italian") }, { "Polish", tr("Polish") }, + { "Russian", tr("Russian") }, { "Spanish", tr("Spanish") } }; - languageComboBox->addItems(languages); + for (auto lang : languages) + { + languageComboBox->addItem(lang.second, lang.first); + } } int Wizard::LanguageSelectionPage::nextId() const diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index e9cce3db5e..5fd316d17a 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -270,8 +270,7 @@ void Wizard::MainWizard::runSettingsImporter() arguments.append(QLatin1String("--encoding")); // Set encoding - QString language(field(QLatin1String("installation.language")).toString()); - + QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); if (language == QLatin1String("Polish")) { arguments.append(QLatin1String("win1250")); @@ -392,7 +391,7 @@ void Wizard::MainWizard::reject() void Wizard::MainWizard::writeSettings() { // Write the encoding and language settings - QString language(field(QLatin1String("installation.language")).toString()); + QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); mLauncherSettings.setLanguage(language); if (language == QLatin1String("Polish")) diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 2f01200927..62d476b944 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -32,7 +32,7 @@ void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts void ContentSelectorView::ContentSelector::buildGameFileView() { - ui.gameFileView->addItem(""); + ui.gameFileView->addItem(tr("")); ui.gameFileView->setVisible(true); connect(ui.gameFileView, qOverload(&ComboBox::currentIndexChanged), this, From 4a9688532381ac5813da164ba287f653bdf97213 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 10 Dec 2023 21:45:15 +0300 Subject: [PATCH 059/231] Untangle normals and parallax handling Move tangent space generation to the vertex shaders Support diffuse parallax when no normal map is present Don't use diffuse parallax if there's no diffuse map Generalize normal-to-view conversion Rewrite parallax --- components/shader/shadervisitor.cpp | 18 +++--- files/shaders/CMakeLists.txt | 1 + files/shaders/compatibility/bs/default.frag | 15 ++--- files/shaders/compatibility/bs/default.vert | 15 +++-- files/shaders/compatibility/groundcover.frag | 22 ++----- files/shaders/compatibility/groundcover.vert | 12 ++-- files/shaders/compatibility/normals.glsl | 14 ++++ files/shaders/compatibility/objects.frag | 67 +++++++------------- files/shaders/compatibility/objects.vert | 23 ++++--- files/shaders/compatibility/terrain.frag | 38 +++-------- files/shaders/compatibility/terrain.vert | 16 +++-- files/shaders/lib/material/parallax.glsl | 7 +- 12 files changed, 118 insertions(+), 130 deletions(-) create mode 100644 files/shaders/compatibility/normals.glsl diff --git a/components/shader/shadervisitor.cpp b/components/shader/shadervisitor.cpp index a08652620d..70464f571e 100644 --- a/components/shader/shadervisitor.cpp +++ b/components/shader/shadervisitor.cpp @@ -304,14 +304,6 @@ namespace Shader if (node.getUserValue(Misc::OsgUserValues::sXSoftEffect, softEffect) && softEffect) mRequirements.back().mSoftParticles = true; - int applyMode; - // Oblivion parallax - if (node.getUserValue("applyMode", applyMode) && applyMode == 4) - { - mRequirements.back().mShaderRequired = true; - mRequirements.back().mDiffuseHeight = true; - } - // Make sure to disregard any state that came from a previous call to createProgram osg::ref_ptr addedState = getAddedState(*stateset); @@ -359,7 +351,17 @@ namespace Shader normalMap = texture; } else if (texName == "diffuseMap") + { + int applyMode; + // Oblivion parallax + if (node.getUserValue("applyMode", applyMode) && applyMode == 4) + { + mRequirements.back().mShaderRequired = true; + mRequirements.back().mDiffuseHeight = true; + mRequirements.back().mTexStageRequiringTangents = unit; + } diffuseMap = texture; + } else if (texName == "specularMap") specularMap = texture; else if (texName == "bumpMap") diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 1b73acf758..6ead738477 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -45,6 +45,7 @@ set(SHADER_FILES compatibility/shadowcasting.vert compatibility/shadowcasting.frag compatibility/vertexcolors.glsl + compatibility/normals.glsl compatibility/multiview_resolve.vert compatibility/multiview_resolve.frag compatibility/depthclipped.vert diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index f1be8da80c..7e2be9aa8f 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -24,7 +24,6 @@ varying vec2 emissiveMapUV; #if @normalMap uniform sampler2D normalMap; varying vec2 normalMapUV; -varying vec4 passTangent; #endif varying float euclideanDepth; @@ -46,11 +45,10 @@ uniform bool useTreeAnim; #include "compatibility/vertexcolors.glsl" #include "compatibility/shadows_fragment.glsl" #include "compatibility/fog.glsl" +#include "compatibility/normals.glsl" void main() { - vec3 normal = normalize(passNormal); - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); @@ -65,15 +63,10 @@ void main() #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); - - vec3 normalizedNormal = normal; - vec3 normalizedTangent = normalize(passTangent.xyz); - vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; - mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); + vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); #endif - vec3 viewNormal = normalize(gl_NormalMatrix * normal); float shadowing = unshadowedLightRatio(linearDepth); vec3 diffuseLight, ambientLight; diff --git a/files/shaders/compatibility/bs/default.vert b/files/shaders/compatibility/bs/default.vert index 712a3f3d0c..d9d47843c0 100644 --- a/files/shaders/compatibility/bs/default.vert +++ b/files/shaders/compatibility/bs/default.vert @@ -36,6 +36,7 @@ varying vec3 passNormal; #include "compatibility/vertexcolors.glsl" #include "compatibility/shadows_vertex.glsl" +#include "compatibility/normals.glsl" void main(void) { @@ -45,6 +46,14 @@ void main(void) gl_ClipVertex = viewPos; euclideanDepth = length(viewPos.xyz); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); + passColor = gl_Color; + passViewPos = viewPos.xyz; + passNormal = gl_Normal.xyz; + normalToViewMatrix = gl_NormalMatrix; + +#if @normalMap + normalToViewMatrix *= generateTangentSpace(gl_MultiTexCoord7.xyzw, passNormal); +#endif #if @diffuseMap diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy; @@ -56,15 +65,11 @@ void main(void) #if @normalMap normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; - passTangent = gl_MultiTexCoord7.xyzw; #endif - passColor = gl_Color; - passViewPos = viewPos.xyz; - passNormal = gl_Normal.xyz; #if @shadows_enabled - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + vec3 viewNormal = normalToView(passNormal); setupShadowCoords(viewPos, viewNormal); #endif } diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index 9dfd71b32e..f87beaf447 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -18,7 +18,6 @@ varying vec2 diffuseMapUV; #if @normalMap uniform sampler2D normalMap; varying vec2 normalMapUV; -varying vec4 passTangent; #endif // Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. @@ -44,23 +43,10 @@ varying vec3 passNormal; #include "lib/light/lighting.glsl" #include "lib/material/alpha.glsl" #include "fog.glsl" +#include "compatibility/normals.glsl" void main() { - vec3 normal = normalize(passNormal); - -#if @normalMap - vec4 normalTex = texture2D(normalMap, normalMapUV); - - vec3 normalizedNormal = normal; - vec3 normalizedTangent = normalize(passTangent.xyz); - vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; - mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); -#endif - vec3 viewNormal = normalize(gl_NormalMatrix * normal); - #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); #else @@ -72,6 +58,12 @@ void main() gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); +#if @normalMap + vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV).xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); +#endif + float shadowing = unshadowedLightRatio(linearDepth); vec3 lighting; diff --git a/files/shaders/compatibility/groundcover.vert b/files/shaders/compatibility/groundcover.vert index dd8d083a47..c1bb35da05 100644 --- a/files/shaders/compatibility/groundcover.vert +++ b/files/shaders/compatibility/groundcover.vert @@ -21,7 +21,6 @@ varying vec2 diffuseMapUV; #if @normalMap varying vec2 normalMapUV; -varying vec4 passTangent; #endif // Other shaders respect forcePPL, but legacy groundcover mods were designed to work with vertex lighting. @@ -41,6 +40,7 @@ centroid varying vec3 shadowDiffuseLighting; varying vec3 passNormal; #include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" #include "lib/light/lighting.glsl" #include "lib/view/depth.glsl" @@ -149,8 +149,14 @@ void main(void) linearDepth = getLinearDepth(gl_Position.z, viewPos.z); + passNormal = rotation3(rotation) * gl_Normal.xyz; + normalToViewMatrix = gl_NormalMatrix; +#if @normalMap + normalToViewMatrix *= generateTangentSpace(gl_MultiTexCoord7.xyzw * rotation, passNormal); +#endif + #if (!PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalize((gl_NormalMatrix * rotation3(rotation) * gl_Normal).xyz); + vec3 viewNormal = normalToView(passNormal); #endif #if @diffuseMap @@ -159,10 +165,8 @@ void main(void) #if @normalMap normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; - passTangent = gl_MultiTexCoord7.xyzw * rotation; #endif - passNormal = rotation3(rotation) * gl_Normal.xyz; #if PER_PIXEL_LIGHTING passViewPos = viewPos.xyz; #else diff --git a/files/shaders/compatibility/normals.glsl b/files/shaders/compatibility/normals.glsl new file mode 100644 index 0000000000..8df16a4b12 --- /dev/null +++ b/files/shaders/compatibility/normals.glsl @@ -0,0 +1,14 @@ +varying mat3 normalToViewMatrix; + +mat3 generateTangentSpace(vec4 tangent, vec3 normal) +{ + vec3 normalizedNormal = normalize(normal); + vec3 normalizedTangent = normalize(tangent.xyz); + vec3 binormal = cross(normalizedTangent, normalizedNormal) * tangent.w; + return mat3(normalizedTangent, binormal, normalizedNormal); +} + +vec3 normalToView(vec3 normal) +{ + return normalize(normalToViewMatrix * normal); +} diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 6de1f6d02f..dd9c3e5f3b 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -37,7 +37,6 @@ varying vec2 emissiveMapUV; #if @normalMap uniform sampler2D normalMap; varying vec2 normalMapUV; -varying vec4 passTangent; #endif #if @envMap @@ -79,6 +78,9 @@ uniform float emissiveMult; uniform float specStrength; varying vec3 passViewPos; varying vec3 passNormal; +#if @normalMap || @diffuseParallax +varying vec4 passTangent; +#endif #if @additiveBlending #define ADDITIVE_BLENDING @@ -91,6 +93,7 @@ varying vec3 passNormal; #include "fog.glsl" #include "vertexcolors.glsl" #include "shadows_fragment.glsl" +#include "compatibility/normals.glsl" #if @softParticles #include "lib/particle/soft.glsl" @@ -113,62 +116,32 @@ void main() applyOcclusionDiscard(orthoDepthMapCoord, texture2D(orthoDepthMap, orthoDepthMapCoord.xy * 0.5 + 0.5).r); #endif - vec3 normal = normalize(passNormal); - vec3 viewVec = normalize(passViewPos.xyz); + // only offset diffuse and normal maps for now, other textures are more likely to be using a completely different UV set + vec2 offset = vec2(0.0); -#if @normalMap - vec4 normalTex = texture2D(normalMap, normalMapUV); - - vec3 normalizedNormal = normal; - vec3 normalizedTangent = normalize(passTangent.xyz); - vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w; - mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); -#endif - -#if !@diffuseMap - gl_FragData[0] = vec4(1.0); -#else - vec2 adjustedDiffuseUV = diffuseMapUV; - -#if @normalMap && (@parallax || @diffuseParallax) - vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; - vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; - vec3 eyeDir = normalize(cameraPos - objectPos); +#if @parallax || @diffuseParallax #if @parallax - float height = normalTex.a; + float height = texture2D(normalMap, normalMapUV).a; float flipY = (passTangent.w > 0.0) ? -1.f : 1.f; #else float height = texture2D(diffuseMap, diffuseMapUV).a; // FIXME: shouldn't be necessary, but in this path false-positives are common float flipY = -1.f; #endif - vec2 offset = getParallaxOffset(eyeDir, tbnTranspose, height, flipY); - adjustedDiffuseUV += offset; // only offset diffuse for now, other textures are more likely to be using a completely different UV set - - // TODO: check not working as the same UV buffer is being bound to different targets - // if diffuseMapUV == normalMapUV -#if 1 - // fetch a new normal using updated coordinates - normalTex = texture2D(normalMap, adjustedDiffuseUV); - - normal = normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0)); + offset = getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), height, flipY); #endif -#endif - - vec4 diffuseTex = texture2D(diffuseMap, adjustedDiffuseUV); - gl_FragData[0].xyz = diffuseTex.xyz; -#if !@diffuseParallax - gl_FragData[0].a = diffuseTex.a * coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV); -#else +#if @diffuseMap + gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV + offset); +#if @diffuseParallax gl_FragData[0].a = 1.0; +#else + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV + offset); #endif +#else + gl_FragData[0] = vec4(1.0); #endif - vec3 viewNormal = normalize(gl_NormalMatrix * normal); - vec4 diffuseColor = getDiffuseColor(); gl_FragData[0].a *= diffuseColor.a; @@ -179,6 +152,14 @@ void main() gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); +#if @normalMap + vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV + offset).xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); +#endif + + vec3 viewVec = normalize(passViewPos); + #if @detailMap gl_FragData[0].xyz *= texture2D(detailMap, detailMapUV).xyz * 2.0; #endif diff --git a/files/shaders/compatibility/objects.vert b/files/shaders/compatibility/objects.vert index 1ea4a1553f..1ec0917ea8 100644 --- a/files/shaders/compatibility/objects.vert +++ b/files/shaders/compatibility/objects.vert @@ -31,7 +31,6 @@ varying vec2 emissiveMapUV; #if @normalMap varying vec2 normalMapUV; -varying vec4 passTangent; #endif #if @envMap @@ -59,9 +58,13 @@ uniform float emissiveMult; #endif varying vec3 passViewPos; varying vec3 passNormal; +#if @normalMap || @diffuseParallax +varying vec4 passTangent; +#endif #include "vertexcolors.glsl" #include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" #include "lib/light/lighting.glsl" #include "lib/view/depth.glsl" @@ -84,9 +87,18 @@ void main(void) vec4 viewPos = modelToView(gl_Vertex); gl_ClipVertex = viewPos; + passColor = gl_Color; + passViewPos = viewPos.xyz; + passNormal = gl_Normal.xyz; + normalToViewMatrix = gl_NormalMatrix; -#if (@envMap || !PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); +#if @normalMap || @diffuseParallax + passTangent = gl_MultiTexCoord7.xyzw; + normalToViewMatrix *= generateTangentSpace(passTangent, passNormal); +#endif + +#if @envMap || !PER_PIXEL_LIGHTING || @shadows_enabled + vec3 viewNormal = normalToView(passNormal); #endif #if @envMap @@ -118,7 +130,6 @@ void main(void) #if @normalMap normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy; - passTangent = gl_MultiTexCoord7.xyzw; #endif #if @bumpMap @@ -133,10 +144,6 @@ void main(void) glossMapUV = (gl_TextureMatrix[@glossMapUV] * gl_MultiTexCoord@glossMapUV).xy; #endif - passColor = gl_Color; - passViewPos = viewPos.xyz; - passNormal = gl_Normal.xyz; - #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index 744a56d18b..734a358590 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -40,49 +40,31 @@ uniform float far; #include "lib/light/lighting.glsl" #include "lib/material/parallax.glsl" #include "fog.glsl" +#include "compatibility/normals.glsl" void main() { vec2 adjustedUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy; - vec3 normal = normalize(passNormal); - -#if @normalMap - vec4 normalTex = texture2D(normalMap, adjustedUV); - - vec3 normalizedNormal = normal; - vec3 tangent = vec3(1.0, 0.0, 0.0); - vec3 binormal = normalize(cross(tangent, normalizedNormal)); - tangent = normalize(cross(normalizedNormal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal - mat3 tbnTranspose = mat3(tangent, binormal, normalizedNormal); - - normal = tbnTranspose * (normalTex.xyz * 2.0 - 1.0); -#endif - #if @parallax - vec3 cameraPos = (gl_ModelViewMatrixInverse * vec4(0,0,0,1)).xyz; - vec3 objectPos = (gl_ModelViewMatrixInverse * vec4(passViewPos, 1)).xyz; - vec3 eyeDir = normalize(cameraPos - objectPos); - adjustedUV += getParallaxOffset(eyeDir, tbnTranspose, normalTex.a, 1.f); - - // update normal using new coordinates - normalTex = texture2D(normalMap, adjustedUV); - - normal = tbnTranspose * (normalTex.xyz * 2.0 - 1.0); + adjustedUV += getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), texture2D(normalMap, adjustedUV).a, 1.f); #endif - - vec3 viewNormal = normalize(gl_NormalMatrix * normal); - vec4 diffuseTex = texture2D(diffuseMap, adjustedUV); gl_FragData[0] = vec4(diffuseTex.xyz, 1.0); + vec4 diffuseColor = getDiffuseColor(); + gl_FragData[0].a *= diffuseColor.a; + #if @blendMap vec2 blendMapUV = (gl_TextureMatrix[1] * vec4(uv, 0.0, 1.0)).xy; gl_FragData[0].a *= texture2D(blendMap, blendMapUV).a; #endif - vec4 diffuseColor = getDiffuseColor(); - gl_FragData[0].a *= diffuseColor.a; +#if @normalMap + vec3 viewNormal = normalToView(texture2D(normalMap, adjustedUV).xyz * 2.0 - 1.0); +#else + vec3 viewNormal = normalToView(normalize(passNormal)); +#endif float shadowing = unshadowedLightRatio(linearDepth); vec3 lighting; diff --git a/files/shaders/compatibility/terrain.vert b/files/shaders/compatibility/terrain.vert index 5e154d912a..f74bc1a95f 100644 --- a/files/shaders/compatibility/terrain.vert +++ b/files/shaders/compatibility/terrain.vert @@ -24,6 +24,7 @@ varying vec3 passNormal; #include "vertexcolors.glsl" #include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" #include "lib/light/lighting.glsl" #include "lib/view/depth.glsl" @@ -37,13 +38,20 @@ void main(void) euclideanDepth = length(viewPos.xyz); linearDepth = getLinearDepth(gl_Position.z, viewPos.z); -#if (!PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); -#endif - passColor = gl_Color; passNormal = gl_Normal.xyz; passViewPos = viewPos.xyz; + normalToViewMatrix = gl_NormalMatrix; + +#if @normalMap + mat3 tbnMatrix = generateTangentSpace(vec4(1.0, 0.0, 0.0, 1.0), passNormal); + tbnMatrix[0] = normalize(cross(tbnMatrix[2], tbnMatrix[1])); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + normalToViewMatrix *= tbnMatrix; +#endif + +#if !PER_PIXEL_LIGHTING || @shadows_enabled + vec3 viewNormal = normalToView(passNormal); +#endif #if !PER_PIXEL_LIGHTING vec3 diffuseLight, ambientLight; diff --git a/files/shaders/lib/material/parallax.glsl b/files/shaders/lib/material/parallax.glsl index 7f4ce2d1dc..5525281f75 100644 --- a/files/shaders/lib/material/parallax.glsl +++ b/files/shaders/lib/material/parallax.glsl @@ -4,10 +4,9 @@ #define PARALLAX_SCALE 0.04 #define PARALLAX_BIAS -0.02 -vec2 getParallaxOffset(vec3 eyeDir, mat3 tbnTranspose, float height, float flipY) +vec2 getParallaxOffset(vec3 eyeDir, float height, float flipY) { - vec3 TSeyeDir = normalize(eyeDir * tbnTranspose); - return vec2(TSeyeDir.x, TSeyeDir.y * flipY) * ( height * PARALLAX_SCALE + PARALLAX_BIAS ); + return vec2(eyeDir.x, eyeDir.y * flipY) * ( height * PARALLAX_SCALE + PARALLAX_BIAS ); } -#endif \ No newline at end of file +#endif From f80cd06256d860436ea382a67779662d2d3ba48c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 12 Dec 2023 22:06:52 +0100 Subject: [PATCH 060/231] Don't count the actor we're following as siding with us if we're in combat with them but they aren't in combat with us --- apps/openmw/mwmechanics/actors.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 73bd331de2..3efed1d540 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -2090,6 +2090,10 @@ namespace MWMechanics if (ally.getClass().getCreatureStats(ally).getAiSequence().getCombatTargets(enemies) && std::find(enemies.begin(), enemies.end(), actorPtr) != enemies.end()) break; + enemies.clear(); + if (actorPtr.getClass().getCreatureStats(actorPtr).getAiSequence().getCombatTargets(enemies) + && std::find(enemies.begin(), enemies.end(), ally) != enemies.end()) + break; } list.push_back(package->getTarget()); } From a0694d4134566e5db0f5ba0a03bfdb557c4f514c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 12 Dec 2023 22:11:32 +0100 Subject: [PATCH 061/231] Don't assert that spells have a school --- apps/openmw/mwmechanics/autocalcspell.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/autocalcspell.cpp b/apps/openmw/mwmechanics/autocalcspell.cpp index 6581aacdd2..a2f6d479f1 100644 --- a/apps/openmw/mwmechanics/autocalcspell.cpp +++ b/apps/openmw/mwmechanics/autocalcspell.cpp @@ -74,7 +74,8 @@ namespace MWMechanics ESM::RefId school; float skillTerm; calcWeakestSchool(&spell, actorSkills, school, skillTerm); - assert(!school.empty()); + if (school.empty()) + continue; SchoolCaps& cap = schoolCaps[school]; if (cap.mReachedLimit && spellCost <= cap.mMinCost) From 5b02b77a392c4f5079fc7ba4aeda3390a597522c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 13 Dec 2023 20:02:26 +0300 Subject: [PATCH 062/231] Base AiFollow activation range on follow distance (bug #7685) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aifollow.cpp | 32 +++++++++++++++------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69f76214f..cb3abc7315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders + Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7712: Casting doesn't support spells and enchantments with no effects Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index b78c1fd6ee..5c07c18166 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -119,21 +119,6 @@ namespace MWMechanics const osg::Vec3f targetPos(target.getRefData().getPosition().asVec3()); const osg::Vec3f targetDir = targetPos - actorPos; - // AiFollow requires the target to be in range and within sight for the initial activation - if (!mActive) - { - storage.mTimer -= duration; - - if (storage.mTimer < 0) - { - if (targetDir.length2() < 500 * 500 && MWBase::Environment::get().getWorld()->getLOS(actor, target)) - mActive = true; - storage.mTimer = 0.5f; - } - } - if (!mActive) - return false; - // In the original engine the first follower stays closer to the player than any subsequent followers. // Followers beyond the first usually attempt to stand inside each other. osg::Vec3f::value_type floatingDistance = 0; @@ -152,6 +137,23 @@ namespace MWMechanics floatingDistance += getHalfExtents(actor) * 2; short followDistance = static_cast(floatingDistance); + // AiFollow requires the target to be in range and within sight for the initial activation + if (!mActive) + { + storage.mTimer -= duration; + + if (storage.mTimer < 0) + { + float activeRange = followDistance + 384.f; + if (targetDir.length2() < activeRange * activeRange + && MWBase::Environment::get().getWorld()->getLOS(actor, target)) + mActive = true; + storage.mTimer = 0.5f; + } + } + if (!mActive) + return false; + if (!mAlwaysFollow) // Update if you only follow for a bit { // Check if we've run out of time From 2628b02b4e05c3af7438af7e54948a334009e812 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 13 Dec 2023 20:59:31 +0300 Subject: [PATCH 063/231] NpcAnimation: Assign parent animation time sources to body part controllers (bug #4822) --- CHANGELOG.md | 1 + apps/openmw/mwrender/actoranimation.cpp | 5 ++--- apps/openmw/mwrender/creatureanimation.cpp | 2 +- apps/openmw/mwrender/npcanimation.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69f76214f..6364d0fb8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Bug #4743: PlayGroup doesn't play non-looping animations correctly Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached + Bug #4822: Non-weapon equipment and body parts can't inherit time from parent animation Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation Bug #5066: Quirks with starting and stopping scripted animations diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 600ae6f0ed..390ac12ea7 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -315,6 +315,7 @@ namespace MWRender if (node == nullptr) return; + // This is used to avoid playing animations intended for equipped weapons on holstered weapons. SceneUtil::ForceControllerSourcesVisitor removeVisitor(std::make_shared()); node->accept(removeVisitor); } @@ -346,9 +347,7 @@ namespace MWRender if (mesh.empty() || boneName.empty()) return; - // If the scabbard is not found, use a weapon mesh as fallback. - // Note: it is unclear how to handle time for controllers attached to bodyparts, so disable them for now. - // We use the similar approach for other bodyparts. + // If the scabbard is not found, use the weapon mesh as fallback. scabbardName = scabbardName.replace(scabbardName.size() - 4, 4, "_sh.nif"); bool isEnchanted = !weapon->getClass().getEnchantment(*weapon).empty(); if (!mResourceSystem->getVFS()->exists(scabbardName)) diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 7767520715..040ba320a1 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -168,7 +168,7 @@ namespace MWRender if (slot == MWWorld::InventoryStore::Slot_CarriedRight) source = mWeaponAnimationTime; else - source = std::make_shared(); + source = mAnimationTimePtr[0]; SceneUtil::AssignControllerSourcesVisitor assignVisitor(source); attached->accept(assignVisitor); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 469978e6eb..1b599fb0c4 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -851,7 +851,7 @@ namespace MWRender if (type == ESM::PRT_Weapon) src = mWeaponAnimationTime; else - src = std::make_shared(); + src = mAnimationTimePtr[0]; SceneUtil::AssignControllerSourcesVisitor assignVisitor(src); node->accept(assignVisitor); } From 2a747529bb9e308ddca1143c730db105b0efe809 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 13 Dec 2023 15:44:32 -0600 Subject: [PATCH 064/231] Feat(CS): Add new shortcut for duplicating instances --- apps/opencs/model/prefs/state.cpp | 1 + apps/opencs/model/prefs/values.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 364f7b3320..5838afb2c3 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -410,6 +410,7 @@ void CSMPrefs::State::declare() declareShortcut("scene-edit-abort", "Abort", QKeySequence(Qt::Key_Escape)); declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); + declareShortcut("scene-duplicate", "Duplicate Instance", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); declareSubcategory("1st/Free Camera"); declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W)); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 247c025e80..4683258e57 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -464,6 +464,7 @@ namespace CSMPrefs "scene-instance-drop-terrain-separately", "" }; Settings::SettingValue mSceneInstanceDropCollisionSeparately{ mIndex, sName, "scene-instance-drop-collision-separately", "" }; + Settings::SettingValue mSceneDuplicate{ mIndex, sName, "scene-duplicate", "Shift+C" }; Settings::SettingValue mSceneLoadCamCell{ mIndex, sName, "scene-load-cam-cell", "Keypad+5" }; Settings::SettingValue mSceneLoadCamEastcell{ mIndex, sName, "scene-load-cam-eastcell", "Keypad+6" }; From 2bb8ceef5655ab898bf3b50815d2f02677f93520 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 13 Dec 2023 15:59:13 -0600 Subject: [PATCH 065/231] Fix(CS): Correct invalid refNum for cloned objects so they actually appear ingame --- apps/opencs/model/world/refcollection.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/opencs/model/world/refcollection.cpp b/apps/opencs/model/world/refcollection.cpp index 1afa9027a9..642edcfb64 100644 --- a/apps/opencs/model/world/refcollection.cpp +++ b/apps/opencs/model/world/refcollection.cpp @@ -297,6 +297,7 @@ void CSMWorld::RefCollection::cloneRecord( const ESM::RefId& origin, const ESM::RefId& destination, const UniversalId::Type type) { auto copy = std::make_unique>(); + int index = getAppendIndex(ESM::RefId(), type); copy->mModified = getRecord(origin).get(); copy->mState = RecordBase::State_ModifiedOnly; @@ -304,6 +305,15 @@ void CSMWorld::RefCollection::cloneRecord( copy->get().mId = destination; copy->get().mIdNum = extractIdNum(destination.getRefIdString()); + if (copy->get().mRefNum.mContentFile != 0) + { + mRefIndex.insert(std::make_pair(static_cast*>(copy.get())->get().mIdNum, index)); + copy->get().mRefNum.mContentFile = 0; + copy->get().mRefNum.mIndex = index; + } + else + copy->get().mRefNum.mIndex = copy->get().mIdNum; + insertRecord(std::move(copy), getAppendIndex(destination, type)); // call RefCollection::insertRecord() } From 7069a970ae2390f815491d19f0ef922e747c1528 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 13 Dec 2023 15:59:49 -0600 Subject: [PATCH 066/231] Feat(CS): Implement instance cloning --- apps/opencs/view/render/instancemode.cpp | 28 ++++++++++++++++++++++++ apps/opencs/view/render/instancemode.hpp | 1 + 2 files changed, 29 insertions(+) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index df5bb02332..f55bc9a8e8 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -205,12 +205,19 @@ CSVRender::InstanceMode::InstanceMode( connect( deleteShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &InstanceMode::deleteSelectedInstances); + CSMPrefs::Shortcut* duplicateShortcut = new CSMPrefs::Shortcut("scene-duplicate", worldspaceWidget); + + connect( + duplicateShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::cloneSelectedInstances); + // Following classes could be simplified by using QSignalMapper, which is obsolete in Qt5.10, but not in Qt4.8 and // Qt5.14 CSMPrefs::Shortcut* dropToCollisionShortcut = new CSMPrefs::Shortcut("scene-instance-drop-collision", worldspaceWidget); + connect(dropToCollisionShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::dropSelectedInstancesToCollision); + CSMPrefs::Shortcut* dropToTerrainLevelShortcut = new CSMPrefs::Shortcut("scene-instance-drop-terrain", worldspaceWidget); connect(dropToTerrainLevelShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, @@ -1087,6 +1094,27 @@ void CSVRender::InstanceMode::deleteSelectedInstances(bool active) getWorldspaceWidget().clearSelection(Mask_Reference); } +void CSVRender::InstanceMode::cloneSelectedInstances() +{ + std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); + if (selection.empty()) + return; + + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& referencesTable + = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_References)); + QUndoStack& undoStack = document.getUndoStack(); + + CSMWorld::CommandMacro macro(undoStack, "Clone Instances"); + for (osg::ref_ptr tag : selection) + if (CSVRender::ObjectTag* objectTag = dynamic_cast(tag.get())) + { + macro.push(new CSMWorld::CloneCommand(referencesTable, objectTag->mObject->getReferenceId(), + "ref#" + std::to_string(referencesTable.rowCount()), CSMWorld::UniversalId::Type_Reference)); + } + // getWorldspaceWidget().clearSelection(Mask_Reference); +} + void CSVRender::InstanceMode::dropInstance(CSVRender::Object* object, float dropHeight) { object->setEdited(Object::Override_Position); diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 5055d08d5b..67af7854ef 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -132,6 +132,7 @@ namespace CSVRender void subModeChanged(const std::string& id); void deleteSelectedInstances(bool active); + void cloneSelectedInstances(); void dropSelectedInstancesToCollision(); void dropSelectedInstancesToTerrain(); void dropSelectedInstancesToCollisionSeparately(); From bc662aeb63ad928934e78da9f28bf4377ac04fea Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Wed, 13 Dec 2023 16:06:12 -0600 Subject: [PATCH 067/231] Fix(CS): Fix minor issue in deleteSelectedInstances impl which caused it to run twice --- apps/opencs/view/render/instancemode.cpp | 5 ++--- apps/opencs/view/render/instancemode.hpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index f55bc9a8e8..7332d9c84b 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -202,8 +202,7 @@ CSVRender::InstanceMode::InstanceMode( connect(this, &InstanceMode::requestFocus, worldspaceWidget, &WorldspaceWidget::requestFocus); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget); - connect( - deleteShortcut, qOverload(&CSMPrefs::Shortcut::activated), this, &InstanceMode::deleteSelectedInstances); + connect(deleteShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::deleteSelectedInstances); CSMPrefs::Shortcut* duplicateShortcut = new CSMPrefs::Shortcut("scene-duplicate", worldspaceWidget); @@ -1075,7 +1074,7 @@ void CSVRender::InstanceMode::handleSelectDrag(const QPoint& pos) mDragMode = DragMode_None; } -void CSVRender::InstanceMode::deleteSelectedInstances(bool active) +void CSVRender::InstanceMode::deleteSelectedInstances() { std::vector> selection = getWorldspaceWidget().getSelection(Mask_Reference); if (selection.empty()) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 67af7854ef..917fde301a 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -131,7 +131,7 @@ namespace CSVRender private slots: void subModeChanged(const std::string& id); - void deleteSelectedInstances(bool active); + void deleteSelectedInstances(); void cloneSelectedInstances(); void dropSelectedInstancesToCollision(); void dropSelectedInstancesToTerrain(); From 11db9eec1d0fd9676cb473e31af707f20b2bc7b1 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 08:23:06 +0100 Subject: [PATCH 068/231] Use settings values to declare modifier settings --- apps/opencs/model/prefs/modifiersetting.cpp | 2 +- apps/opencs/model/prefs/modifiersetting.hpp | 2 +- apps/opencs/model/prefs/setting.cpp | 2 +- apps/opencs/model/prefs/setting.hpp | 2 +- apps/opencs/model/prefs/shortcutmanager.cpp | 2 +- apps/opencs/model/prefs/shortcutmanager.hpp | 6 +++--- apps/opencs/model/prefs/state.cpp | 11 ++++++----- apps/opencs/model/prefs/state.hpp | 2 +- 8 files changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index 53db4c8521..86a8dc6274 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -20,7 +20,7 @@ class QWidget; namespace CSMPrefs { ModifierSetting::ModifierSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mButton(nullptr) , mEditorActive(false) diff --git a/apps/opencs/model/prefs/modifiersetting.hpp b/apps/opencs/model/prefs/modifiersetting.hpp index cf9ce8f149..76a3a82e71 100644 --- a/apps/opencs/model/prefs/modifiersetting.hpp +++ b/apps/opencs/model/prefs/modifiersetting.hpp @@ -22,7 +22,7 @@ namespace CSMPrefs public: explicit ModifierSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/setting.cpp b/apps/opencs/model/prefs/setting.cpp index cc5077dc83..3c2ac65c94 100644 --- a/apps/opencs/model/prefs/setting.cpp +++ b/apps/opencs/model/prefs/setting.cpp @@ -16,7 +16,7 @@ QMutex* CSMPrefs::Setting::getMutex() } CSMPrefs::Setting::Setting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : QObject(parent->getState()) , mParent(parent) , mMutex(mutex) diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index f5d9e83ef1..0f4840f713 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -62,7 +62,7 @@ namespace CSMPrefs public: explicit Setting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); ~Setting() override = default; diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index a6f1da4f85..3089281c91 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -91,7 +91,7 @@ namespace CSMPrefs return false; } - void ShortcutManager::setModifier(const std::string& name, int modifier) + void ShortcutManager::setModifier(std::string_view name, int modifier) { // Add to map/modify ModifierMap::iterator item = mModifiers.find(name); diff --git a/apps/opencs/model/prefs/shortcutmanager.hpp b/apps/opencs/model/prefs/shortcutmanager.hpp index fc8db3f2b0..87d2f2256c 100644 --- a/apps/opencs/model/prefs/shortcutmanager.hpp +++ b/apps/opencs/model/prefs/shortcutmanager.hpp @@ -32,7 +32,7 @@ namespace CSMPrefs void setSequence(const std::string& name, const QKeySequence& sequence); bool getModifier(const std::string& name, int& modifier) const; - void setModifier(const std::string& name, int modifier); + void setModifier(std::string_view name, int modifier); std::string convertToString(const QKeySequence& sequence) const; std::string convertToString(int modifier) const; @@ -49,9 +49,9 @@ namespace CSMPrefs private: // Need a multimap in case multiple shortcuts share the same name - typedef std::multimap ShortcutMap; + typedef std::multimap> ShortcutMap; typedef std::map SequenceMap; - typedef std::map ModifierMap; + typedef std::map> ModifierMap; typedef std::map NameMap; typedef std::map KeyMap; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 364f7b3320..2d511c3b50 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -396,7 +396,7 @@ void CSMPrefs::State::declare() "scene-select-secondary", "Secondary Select", QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton)); declareShortcut( "scene-select-tertiary", "Tertiary Select", QKeySequence(Qt::ShiftModifier | (int)Qt::MiddleButton)); - declareModifier("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift); + declareModifier(mValues->mKeyBindings.mSceneSpeedModifier, "Speed Modifier"); declareShortcut("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete)); declareShortcut("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G)); declareShortcut("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H)); @@ -553,7 +553,8 @@ CSMPrefs::StringSetting& CSMPrefs::State::declareString( return *setting; } -CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& key, const QString& label, int default_) +CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier( + Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -561,11 +562,11 @@ CSMPrefs::ModifierSetting& CSMPrefs::State::declareModifier(const std::string& k // Setup with actual data int modifier; - getShortcutManager().convertFromString(mIndex->get(mCurrentCategory->second.getKey(), key), modifier); - getShortcutManager().setModifier(key, modifier); + getShortcutManager().convertFromString(value.get(), modifier); + getShortcutManager().setModifier(value.mName, modifier); CSMPrefs::ModifierSetting* setting - = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::ModifierSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); return *setting; diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 018a81a8d9..5f82389c82 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -76,7 +76,7 @@ namespace CSMPrefs StringSetting& declareString(const std::string& key, const QString& label, const std::string& default_); - ModifierSetting& declareModifier(const std::string& key, const QString& label, int modifier_); + ModifierSetting& declareModifier(Settings::SettingValue& value, const QString& label); void declareSubcategory(const QString& label); From a29ae07957a0a80be8090f618778337698c6aa4b Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 14 Dec 2023 00:28:43 +0100 Subject: [PATCH 069/231] Fix CS Key Binding settings page This got broken by e07d8f3066. Creating QGridLayout with parent and setting it later has not the same effect. --- apps/opencs/model/prefs/boolsetting.cpp | 2 +- apps/opencs/model/prefs/coloursetting.cpp | 2 +- apps/opencs/model/prefs/doublesetting.cpp | 2 +- apps/opencs/model/prefs/enumsetting.cpp | 2 +- apps/opencs/model/prefs/intsetting.cpp | 2 +- apps/opencs/model/prefs/modifiersetting.cpp | 2 +- apps/opencs/model/prefs/setting.hpp | 1 - apps/opencs/model/prefs/shortcutsetting.cpp | 2 +- apps/opencs/model/prefs/stringsetting.cpp | 2 +- apps/opencs/model/prefs/subcategory.cpp | 5 +---- apps/opencs/view/prefs/keybindingpage.cpp | 6 +++--- 11 files changed, 12 insertions(+), 16 deletions(-) diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index 2ca8315245..c7520c415d 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -36,7 +36,7 @@ CSMPrefs::SettingWidgets CSMPrefs::BoolSetting::makeWidgets(QWidget* parent) connect(mWidget, &QCheckBox::stateChanged, this, &BoolSetting::valueChanged); - return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget }; } void CSMPrefs::BoolSetting::updateWidget() diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index 61aa92063e..9e237dd7a8 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -41,7 +41,7 @@ CSMPrefs::SettingWidgets CSMPrefs::ColourSetting::makeWidgets(QWidget* parent) connect(mWidget, &CSVWidget::ColorEditor::pickingFinished, this, &ColourSetting::valueChanged); - return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = mWidget }; } void CSMPrefs::ColourSetting::updateWidget() diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index b275c2e9b7..679c8d6a92 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -73,7 +73,7 @@ CSMPrefs::SettingWidgets CSMPrefs::DoubleSetting::makeWidgets(QWidget* parent) connect(mWidget, qOverload(&QDoubleSpinBox::valueChanged), this, &DoubleSetting::valueChanged); - return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = mWidget }; } void CSMPrefs::DoubleSetting::updateWidget() diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index 1521c3cc13..938da874e1 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -106,7 +106,7 @@ CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) connect(mWidget, qOverload(&QComboBox::currentIndexChanged), this, &EnumSetting::valueChanged); - return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = mWidget }; } void CSMPrefs::EnumSetting::updateWidget() diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 023339b9c1..54fd13233e 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -65,7 +65,7 @@ CSMPrefs::SettingWidgets CSMPrefs::IntSetting::makeWidgets(QWidget* parent) connect(mWidget, qOverload(&QSpinBox::valueChanged), this, &IntSetting::valueChanged); - return SettingWidgets{ .mLabel = label, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = mWidget }; } void CSMPrefs::IntSetting::updateWidget() diff --git a/apps/opencs/model/prefs/modifiersetting.cpp b/apps/opencs/model/prefs/modifiersetting.cpp index 53db4c8521..99caedb2c8 100644 --- a/apps/opencs/model/prefs/modifiersetting.cpp +++ b/apps/opencs/model/prefs/modifiersetting.cpp @@ -47,7 +47,7 @@ namespace CSMPrefs connect(widget, &QPushButton::toggled, this, &ModifierSetting::buttonToggled); - return SettingWidgets{ .mLabel = label, .mInput = widget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = widget }; } void ModifierSetting::updateWidget() diff --git a/apps/opencs/model/prefs/setting.hpp b/apps/opencs/model/prefs/setting.hpp index f5d9e83ef1..72b070b2fc 100644 --- a/apps/opencs/model/prefs/setting.hpp +++ b/apps/opencs/model/prefs/setting.hpp @@ -23,7 +23,6 @@ namespace CSMPrefs { QLabel* mLabel; QWidget* mInput; - QGridLayout* mLayout; }; class Setting : public QObject diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index 42902f02cd..33bb521290 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -51,7 +51,7 @@ namespace CSMPrefs connect(widget, &QPushButton::toggled, this, &ShortcutSetting::buttonToggled); - return SettingWidgets{ .mLabel = label, .mInput = widget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = label, .mInput = widget }; } void ShortcutSetting::updateWidget() diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index d70324ba56..56c46bb6af 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -36,7 +36,7 @@ CSMPrefs::SettingWidgets CSMPrefs::StringSetting::makeWidgets(QWidget* parent) connect(mWidget, &QLineEdit::textChanged, this, &StringSetting::textChanged); - return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget, .mLayout = nullptr }; + return SettingWidgets{ .mLabel = nullptr, .mInput = mWidget }; } void CSMPrefs::StringSetting::updateWidget() diff --git a/apps/opencs/model/prefs/subcategory.cpp b/apps/opencs/model/prefs/subcategory.cpp index ac5b78ee2c..815025daec 100644 --- a/apps/opencs/model/prefs/subcategory.cpp +++ b/apps/opencs/model/prefs/subcategory.cpp @@ -13,9 +13,6 @@ namespace CSMPrefs SettingWidgets Subcategory::makeWidgets(QWidget* /*parent*/) { - QGridLayout* const layout = new QGridLayout(); - layout->setSizeConstraint(QLayout::SetMinAndMaxSize); - - return SettingWidgets{ .mLabel = nullptr, .mInput = nullptr, .mLayout = layout }; + return SettingWidgets{ .mLabel = nullptr, .mInput = nullptr }; } } diff --git a/apps/opencs/view/prefs/keybindingpage.cpp b/apps/opencs/view/prefs/keybindingpage.cpp index b88627aca1..f292fa4cf5 100644 --- a/apps/opencs/view/prefs/keybindingpage.cpp +++ b/apps/opencs/view/prefs/keybindingpage.cpp @@ -81,12 +81,12 @@ namespace CSVPrefs int next = mPageLayout->rowCount(); mPageLayout->addWidget(widgets.mInput, next, 0, 1, 2); } - else if (widgets.mLayout != nullptr) + else { // Create new page QWidget* pageWidget = new QWidget(); - mPageLayout = widgets.mLayout; - mPageLayout->setParent(pageWidget); + mPageLayout = new QGridLayout(pageWidget); + mPageLayout->setSizeConstraint(QLayout::SetMinAndMaxSize); mStackedLayout->addWidget(pageWidget); From 27bd70a976096671a1408a5760f326be1f1fd8a9 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 14 Dec 2023 15:32:34 +0300 Subject: [PATCH 070/231] For constant enchantments, allow on-self range for any effect (bug #7643) --- CHANGELOG.md | 1 + apps/openmw/mwgui/spellcreationdialog.cpp | 28 +++++------------------ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69f76214f..665a8fbc4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -97,6 +97,7 @@ Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Bug #7641: loopgroup loops the animation one time too many for actors Bug #7642: Items in repair and recharge menus aren't sorted alphabetically + Bug #7643: Can't enchant items with constant effect on self magic effects for non-player character Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 1618f34a7a..bb97ccb82f 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -119,7 +119,7 @@ namespace MWGui void EditEffectDialog::newEffect(const ESM::MagicEffect* effect) { - bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; setMagicEffect(effect); @@ -240,7 +240,7 @@ namespace MWGui // cycle through range types until we find something that's allowed // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect // dialog) - bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; + bool allowSelf = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; if (mEffect.mRange == ESM::RT_Self && !allowSelf) @@ -629,7 +629,7 @@ namespace MWGui const ESM::MagicEffect* effect = MWBase::Environment::get().getESMStore()->get().find(mSelectedKnownEffectId); - bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0; + bool allowSelf = (effect->mData.mFlags & ESM::MagicEffect::CastSelf) != 0 || mConstantEffect; bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; @@ -751,25 +751,9 @@ namespace MWGui void EffectEditorBase::setConstantEffect(bool constant) { mAddEffectDialog.setConstantEffect(constant); + if (!mConstantEffect && constant) + for (ESM::ENAMstruct& effect : mEffects) + effect.mRange = ESM::RT_Self; mConstantEffect = constant; - - if (!constant) - return; - - for (auto it = mEffects.begin(); it != mEffects.end();) - { - if (it->mRange != ESM::RT_Self) - { - auto& store = *MWBase::Environment::get().getESMStore(); - auto magicEffect = store.get().find(it->mEffectID); - if ((magicEffect->mData.mFlags & ESM::MagicEffect::CastSelf) == 0) - { - it = mEffects.erase(it); - continue; - } - it->mRange = ESM::RT_Self; - } - ++it; - } } } From d1274fd3db5bf117d48c0edce346a0412f7eec3d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 15 Dec 2023 09:17:24 +0300 Subject: [PATCH 071/231] Deduplicate lambert calculations, consolidate doLighting --- files/shaders/lib/light/lighting.glsl | 77 +++++++++------------------ 1 file changed, 24 insertions(+), 53 deletions(-) diff --git a/files/shaders/lib/light/lighting.glsl b/files/shaders/lib/light/lighting.glsl index 8351fce8a0..689aee0911 100644 --- a/files/shaders/lib/light/lighting.glsl +++ b/files/shaders/lib/light/lighting.glsl @@ -3,15 +3,13 @@ #include "lighting_util.glsl" -void perLightSun(out vec3 diffuseOut, vec3 viewPos, vec3 viewNormal) +float calcLambert(vec3 viewNormal, vec3 lightDir, vec3 viewDir) { - vec3 lightDir = normalize(lcalcPosition(0)); - float lambert = dot(viewNormal.xyz, lightDir); - + float lambert = dot(viewNormal, lightDir); #ifndef GROUNDCOVER lambert = max(lambert, 0.0); #else - float eyeCosine = dot(normalize(viewPos), viewNormal.xyz); + float eyeCosine = dot(viewNormal, viewDir); if (lambert < 0.0) { lambert = -lambert; @@ -19,46 +17,7 @@ void perLightSun(out vec3 diffuseOut, vec3 viewPos, vec3 viewNormal) } lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); #endif - - diffuseOut = lcalcDiffuse(0).xyz * lambert; -} - -void perLightPoint(out vec3 ambientOut, out vec3 diffuseOut, int lightIndex, vec3 viewPos, vec3 viewNormal) -{ - vec3 lightPos = lcalcPosition(lightIndex) - viewPos; - float lightDistance = length(lightPos); - -// cull non-FFP point lighting by radius, light is guaranteed to not fall outside this bound with our cutoff -#if !@lightingMethodFFP - float radius = lcalcRadius(lightIndex); - - if (lightDistance > radius * 2.0) - { - ambientOut = vec3(0.0); - diffuseOut = vec3(0.0); - return; - } -#endif - - lightPos = normalize(lightPos); - - float illumination = lcalcIllumination(lightIndex, lightDistance); - ambientOut = lcalcAmbient(lightIndex) * illumination; - float lambert = dot(viewNormal.xyz, lightPos) * illumination; - -#ifndef GROUNDCOVER - lambert = max(lambert, 0.0); -#else - float eyeCosine = dot(normalize(viewPos), viewNormal.xyz); - if (lambert < 0.0) - { - lambert = -lambert; - eyeCosine = -eyeCosine; - } - lambert *= clamp(-8.0 * (1.0 - 0.3) * eyeCosine + 1.0, 0.3, 1.0); -#endif - - diffuseOut = lcalcDiffuse(lightIndex) * lambert; + return lambert; } #if PER_PIXEL_LIGHTING @@ -67,26 +26,38 @@ void doLighting(vec3 viewPos, vec3 viewNormal, float shadowing, out vec3 diffuse void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight, out vec3 shadowDiffuse) #endif { - vec3 ambientOut, diffuseOut; + vec3 viewDir = normalize(viewPos); - perLightSun(diffuseOut, viewPos, viewNormal); + diffuseLight = lcalcDiffuse(0).xyz * calcLambert(viewNormal, normalize(lcalcPosition(0)), viewDir); ambientLight = gl_LightModel.ambient.xyz; #if PER_PIXEL_LIGHTING - diffuseLight = diffuseOut * shadowing; + diffuseLight *= shadowing; #else - shadowDiffuse = diffuseOut; + shadowDiffuse = diffuseLight; diffuseLight = vec3(0.0); #endif for (int i = @startLight; i < @endLight; ++i) { #if @lightingMethodUBO - perLightPoint(ambientOut, diffuseOut, PointLightIndex[i], viewPos, viewNormal); + int lightIndex = PointLightIndex[i]; #else - perLightPoint(ambientOut, diffuseOut, i, viewPos, viewNormal); + int lightIndex = i; #endif - ambientLight += ambientOut; - diffuseLight += diffuseOut; + vec3 lightPos = lcalcPosition(lightIndex) - viewPos; + float lightDistance = length(lightPos); + + // cull non-FFP point lighting by radius, light is guaranteed to not fall outside this bound with our cutoff +#if !@lightingMethodFFP + if (lightDistance > lcalcRadius(lightIndex) * 2.0) + continue; +#endif + + vec3 lightDir = lightPos / lightDistance; + + float illumination = lcalcIllumination(lightIndex, lightDistance); + ambientLight += lcalcAmbient(lightIndex) * illumination; + diffuseLight += lcalcDiffuse(lightIndex) * calcLambert(viewNormal, lightDir, viewDir) * illumination; } } From 93ea9dbc3bd809509cd32449d0d9e48a68a48681 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 15 Dec 2023 10:23:10 +0300 Subject: [PATCH 072/231] Do all lighting calculations in one place, support per-vertex specularity Force PPL when specular maps are used --- files/shaders/compatibility/bs/default.frag | 22 +++--- files/shaders/compatibility/groundcover.frag | 4 +- files/shaders/compatibility/groundcover.vert | 5 +- files/shaders/compatibility/objects.frag | 43 ++++++------ files/shaders/compatibility/objects.vert | 14 ++-- files/shaders/compatibility/terrain.frag | 35 +++++----- files/shaders/compatibility/terrain.vert | 10 ++- files/shaders/lib/light/lighting.glsl | 70 +++++++------------- 8 files changed, 88 insertions(+), 115 deletions(-) diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 7e2be9aa8f..70d9ef7ba7 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -61,34 +61,30 @@ void main() gl_FragData[0].a *= diffuseColor.a; gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); + vec3 specularColor = getSpecularColor().xyz; #if @normalMap vec4 normalTex = texture2D(normalMap, normalMapUV); vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); + specularColor *= normalTex.a; #else vec3 viewNormal = normalToView(normalize(passNormal)); #endif float shadowing = unshadowedLightRatio(linearDepth); - vec3 diffuseLight, ambientLight; - doLighting(passViewPos, viewNormal, shadowing, diffuseLight, ambientLight); + vec3 diffuseLight, ambientLight, specularLight; + doLighting(passViewPos, viewNormal, gl_FrontMaterial.shininess, shadowing, diffuseLight, ambientLight, specularLight); + vec3 diffuse = diffuseColor.xyz * diffuseLight; + vec3 ambient = getAmbientColor().xyz * ambientLight; vec3 emission = getEmissionColor().xyz * emissiveMult; #if @emissiveMap emission *= texture2D(emissiveMap, emissiveMapUV).xyz; #endif - vec3 lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; + vec3 lighting = diffuse + ambient + emission; + vec3 specular = specularColor * specularLight * specStrength; clampLightingResult(lighting); - gl_FragData[0].xyz *= lighting; - - float shininess = gl_FrontMaterial.shininess; - vec3 matSpec = getSpecularColor().xyz * specStrength; -#if @normalMap - matSpec *= normalTex.a; -#endif - - if (matSpec != vec3(0.0)) - gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); + gl_FragData[0].xyz = gl_FragData[0].xyz * lighting + specular; gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/compatibility/groundcover.frag b/files/shaders/compatibility/groundcover.frag index f87beaf447..dfdd6518c3 100644 --- a/files/shaders/compatibility/groundcover.frag +++ b/files/shaders/compatibility/groundcover.frag @@ -70,8 +70,8 @@ void main() #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; #else - vec3 diffuseLight, ambientLight; - doLighting(passViewPos, viewNormal, shadowing, diffuseLight, ambientLight); + vec3 diffuseLight, ambientLight, specularLight; + doLighting(passViewPos, viewNormal, gl_FrontMaterial.shininess, shadowing, diffuseLight, ambientLight, specularLight); lighting = diffuseLight + ambientLight; #endif diff --git a/files/shaders/compatibility/groundcover.vert b/files/shaders/compatibility/groundcover.vert index c1bb35da05..95a17b5084 100644 --- a/files/shaders/compatibility/groundcover.vert +++ b/files/shaders/compatibility/groundcover.vert @@ -170,8 +170,9 @@ void main(void) #if PER_PIXEL_LIGHTING passViewPos = viewPos.xyz; #else - vec3 diffuseLight, ambientLight; - doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); + vec3 diffuseLight, ambientLight, specularLight; + vec3 unusedShadowSpecular; + doLighting(viewPos.xyz, viewNormal, gl_FrontMaterial.shininess, diffuseLight, ambientLight, specularLight, shadowDiffuseLighting, unusedShadowSpecular); passLighting = diffuseLight + ambientLight; clampLightingResult(passLighting); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index dd9c3e5f3b..80de6b0e9d 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -67,15 +67,17 @@ uniform float near; uniform float far; uniform float alphaRef; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; #else uniform float emissiveMult; -#endif uniform float specStrength; +#endif varying vec3 passViewPos; varying vec3 passNormal; #if @normalMap || @diffuseParallax @@ -200,19 +202,27 @@ void main() #endif float shadowing = unshadowedLightRatio(-passViewPos.z); - vec3 lighting; + vec3 lighting, specular; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; + specular = passSpecular + shadowSpecularLighting * shadowing; #else - vec3 diffuseLight, ambientLight; - doLighting(passViewPos, viewNormal, shadowing, diffuseLight, ambientLight); - vec3 emission = getEmissionColor().xyz * emissiveMult; - lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; +#if @specularMap + vec4 specTex = texture2D(specularMap, specularMapUV); + float shininess = specTex.a * 255.0; + vec3 specularColor = specTex.xyz; +#else + float shininess = gl_FrontMaterial.shininess; + vec3 specularColor = getSpecularColor().xyz; +#endif + vec3 diffuseLight, ambientLight, specularLight; + doLighting(passViewPos, viewNormal, shininess, shadowing, diffuseLight, ambientLight, specularLight); + lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz * emissiveMult; + specular = specularColor * specularLight * specStrength; #endif clampLightingResult(lighting); - - gl_FragData[0].xyz *= lighting; + gl_FragData[0].xyz = gl_FragData[0].xyz * lighting + specular; #if @envMap && !@preLightEnv gl_FragData[0].xyz += envEffect; @@ -222,21 +232,6 @@ void main() gl_FragData[0].xyz += texture2D(emissiveMap, emissiveMapUV).xyz; #endif -#if @specularMap - vec4 specTex = texture2D(specularMap, specularMapUV); - float shininess = specTex.a * 255.0; - vec3 matSpec = specTex.xyz; -#else - float shininess = gl_FrontMaterial.shininess; - vec3 matSpec = getSpecularColor().xyz; -#endif - - matSpec *= specStrength; - if (matSpec != vec3(0.0)) - { - gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); - } - gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far); vec2 screenCoords = gl_FragCoord.xy / screenRes; diff --git a/files/shaders/compatibility/objects.vert b/files/shaders/compatibility/objects.vert index 1ec0917ea8..2bebcd60bf 100644 --- a/files/shaders/compatibility/objects.vert +++ b/files/shaders/compatibility/objects.vert @@ -49,12 +49,15 @@ varying vec2 specularMapUV; varying vec2 glossMapUV; #endif -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; uniform float emissiveMult; +uniform float specStrength; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -145,12 +148,13 @@ void main(void) #endif #if !PER_PIXEL_LIGHTING - vec3 diffuseLight, ambientLight; - doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); - vec3 emission = getEmissionColor().xyz * emissiveMult; - passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission; + vec3 diffuseLight, ambientLight, specularLight; + doLighting(viewPos.xyz, viewNormal, gl_FrontMaterial.shininess, diffuseLight, ambientLight, specularLight, shadowDiffuseLighting, shadowSpecularLighting); + passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz * emissiveMult; + passSpecular = getSpecularColor().xyz * specularLight * specStrength; clampLightingResult(passLighting); shadowDiffuseLighting *= getDiffuseColor().xyz; + shadowSpecularLighting *= getSpecularColor().xyz * specStrength; #endif #if (@shadows_enabled) diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index 734a358590..38b985223e 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -23,11 +23,13 @@ uniform sampler2D blendMap; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -67,31 +69,26 @@ void main() #endif float shadowing = unshadowedLightRatio(linearDepth); - vec3 lighting; + vec3 lighting, specular; #if !PER_PIXEL_LIGHTING lighting = passLighting + shadowDiffuseLighting * shadowing; + specular = passSpecular + shadowSpecularLighting * shadowing; #else - vec3 diffuseLight, ambientLight; - doLighting(passViewPos, viewNormal, shadowing, diffuseLight, ambientLight); +#if @specularMap + float shininess = 128.0; // TODO: make configurable + vec3 specularColor = vec3(diffuseTex.a); +#else + float shininess = gl_FrontMaterial.shininess; + vec3 specularColor = getSpecularColor().xyz; +#endif + vec3 diffuseLight, ambientLight, specularLight; + doLighting(passViewPos, viewNormal, shininess, shadowing, diffuseLight, ambientLight, specularLight); lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + specular = specularColor * specularLight; #endif clampLightingResult(lighting); - - gl_FragData[0].xyz *= lighting; - -#if @specularMap - float shininess = 128.0; // TODO: make configurable - vec3 matSpec = vec3(diffuseTex.a); -#else - float shininess = gl_FrontMaterial.shininess; - vec3 matSpec = getSpecularColor().xyz; -#endif - - if (matSpec != vec3(0.0)) - { - gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); - } + gl_FragData[0].xyz = gl_FragData[0].xyz * lighting + specular; gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/compatibility/terrain.vert b/files/shaders/compatibility/terrain.vert index f74bc1a95f..3b2cb16db4 100644 --- a/files/shaders/compatibility/terrain.vert +++ b/files/shaders/compatibility/terrain.vert @@ -13,11 +13,13 @@ varying vec2 uv; varying float euclideanDepth; varying float linearDepth; -#define PER_PIXEL_LIGHTING (@normalMap || @forcePPL) +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) #if !PER_PIXEL_LIGHTING centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; #endif varying vec3 passViewPos; varying vec3 passNormal; @@ -54,11 +56,13 @@ void main(void) #endif #if !PER_PIXEL_LIGHTING - vec3 diffuseLight, ambientLight; - doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting); + vec3 diffuseLight, ambientLight, specularLight; + doLighting(viewPos.xyz, viewNormal, gl_FrontMaterial.shininess, diffuseLight, ambientLight, specularLight, shadowDiffuseLighting, shadowSpecularLighting); passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + passSpecular = getSpecularColor().xyz * specularLight; clampLightingResult(passLighting); shadowDiffuseLighting *= getDiffuseColor().xyz; + shadowSpecularLighting *= getSpecularColor().xyz; #endif uv = gl_MultiTexCoord0.xy; diff --git a/files/shaders/lib/light/lighting.glsl b/files/shaders/lib/light/lighting.glsl index 689aee0911..8bb6ba148f 100644 --- a/files/shaders/lib/light/lighting.glsl +++ b/files/shaders/lib/light/lighting.glsl @@ -20,21 +20,39 @@ float calcLambert(vec3 viewNormal, vec3 lightDir, vec3 viewDir) return lambert; } +float calcSpecIntensity(vec3 viewNormal, vec3 viewDir, float shininess, vec3 lightDir) +{ + if (dot(viewNormal, lightDir) > 0.0) + { + vec3 halfVec = normalize(lightDir - viewDir); + float NdotH = max(dot(viewNormal, halfVec), 0.0); + return pow(NdotH, shininess); + } + + return 0.0; +} + #if PER_PIXEL_LIGHTING -void doLighting(vec3 viewPos, vec3 viewNormal, float shadowing, out vec3 diffuseLight, out vec3 ambientLight) +void doLighting(vec3 viewPos, vec3 viewNormal, float shininess, float shadowing, out vec3 diffuseLight, out vec3 ambientLight, out vec3 specularLight) #else -void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 ambientLight, out vec3 shadowDiffuse) +void doLighting(vec3 viewPos, vec3 viewNormal, float shininess, out vec3 diffuseLight, out vec3 ambientLight, out vec3 specularLight, out vec3 shadowDiffuse, out vec3 shadowSpecular) #endif { vec3 viewDir = normalize(viewPos); + shininess = max(shininess, 1e-4); - diffuseLight = lcalcDiffuse(0).xyz * calcLambert(viewNormal, normalize(lcalcPosition(0)), viewDir); + vec3 sunDir = normalize(lcalcPosition(0)); + diffuseLight = lcalcDiffuse(0) * calcLambert(viewNormal, sunDir, viewDir); ambientLight = gl_LightModel.ambient.xyz; + specularLight = lcalcSpecular(0).xyz * calcSpecIntensity(viewNormal, viewDir, shininess, sunDir); #if PER_PIXEL_LIGHTING diffuseLight *= shadowing; + specularLight *= shadowing; #else shadowDiffuse = diffuseLight; + shadowSpecular = specularLight; diffuseLight = vec3(0.0); + specularLight = vec3(0.0); #endif for (int i = @startLight; i < @endLight; ++i) @@ -56,52 +74,10 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a vec3 lightDir = lightPos / lightDistance; float illumination = lcalcIllumination(lightIndex, lightDistance); - ambientLight += lcalcAmbient(lightIndex) * illumination; diffuseLight += lcalcDiffuse(lightIndex) * calcLambert(viewNormal, lightDir, viewDir) * illumination; + ambientLight += lcalcAmbient(lightIndex) * illumination; + specularLight += lcalcSpecular(lightIndex).xyz * calcSpecIntensity(viewNormal, viewDir, shininess, lightDir) * illumination; } } -float calcSpecIntensity(vec3 viewNormal, vec3 viewDir, float shininess, vec3 lightDir) -{ - if (dot(viewNormal, lightDir) > 0.0) - { - vec3 halfVec = normalize(lightDir - viewDir); - float NdotH = max(dot(viewNormal, halfVec), 0.0); - return pow(NdotH, shininess); - } - - return 0.0; -} - -vec3 getSpecular(vec3 viewNormal, vec3 viewPos, float shininess, float shadowing) -{ - shininess = max(shininess, 1e-4); - vec3 viewDir = normalize(viewPos); - vec3 specularLight = lcalcSpecular(0).xyz * calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lcalcPosition(0))); - specularLight *= shadowing; - - for (int i = @startLight; i < @endLight; ++i) - { -#if @lightingMethodUBO - int lightIndex = PointLightIndex[i]; -#else - int lightIndex = i; -#endif - - vec3 lightPos = lcalcPosition(lightIndex) - viewPos; - float lightDistance = length(lightPos); - -#if !@lightingMethodFFP - if (lightDistance > lcalcRadius(lightIndex) * 2.0) - continue; -#endif - - float illumination = lcalcIllumination(lightIndex, lightDistance); - float intensity = calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lightPos)); - specularLight += lcalcSpecular(lightIndex).xyz * intensity * illumination; - } - - return specularLight; -} - #endif From 82982bbc0513e6486bd9e247b4cb29a47ef51446 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 15 Dec 2023 21:28:09 +0300 Subject: [PATCH 073/231] Outlaw vampires and werewolves (bugs #7723, #7724) --- CHANGELOG.md | 2 ++ apps/openmw/mwmechanics/actors.cpp | 35 ++++++++++++------- .../mwmechanics/mechanicsmanagerimp.cpp | 11 +++--- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3abc7315..a2d72644c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,8 @@ Bug #7679: Scene luminance value flashes when toggling shaders Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7712: Casting doesn't support spells and enchantments with no effects + Bug #7723: Assaulting vampires and werewolves shouldn't be a crime + Bug #7724: Guards don't help vs werewolves Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 73bd331de2..e0baac0764 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -707,10 +707,9 @@ namespace MWMechanics } } - // Make guards go aggressive with creatures that are in combat, unless the creature is a follower or escorter + // Make guards go aggressive with creatures and werewolves that are in combat const auto world = MWBase::Environment::get().getWorld(); - if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && !actor2.getClass().isNpc() - && creatureStats2.getAiSequence().isInCombat()) + if (!aggressive && actor1.getClass().isClass(actor1, "Guard") && creatureStats2.getAiSequence().isInCombat()) { // Check if the creature is too far static const float fAlarmRadius @@ -718,20 +717,30 @@ namespace MWMechanics if (sqrDist > fAlarmRadius * fAlarmRadius) return; - bool followerOrEscorter = false; - for (const auto& package : creatureStats2.getAiSequence()) + bool targetIsCreature = !actor2.getClass().isNpc(); + if (targetIsCreature || actor2.getClass().getNpcStats(actor2).isWerewolf()) { - // The follow package must be first or have nothing but combat before it - if (package->sideWithTarget()) + bool followerOrEscorter = false; + // ...unless the creature has allies + if (targetIsCreature) { - followerOrEscorter = true; - break; + for (const auto& package : creatureStats2.getAiSequence()) + { + // The follow package must be first or have nothing but combat before it + if (package->sideWithTarget()) + { + followerOrEscorter = true; + break; + } + else if (package->getTypeId() != MWMechanics::AiPackageTypeId::Combat) + break; + } } - else if (package->getTypeId() != MWMechanics::AiPackageTypeId::Combat) - break; + // Morrowind also checks "known werewolf" flag, but the player is never in combat + // so this code is unreachable for the player + if (!followerOrEscorter) + aggressive = true; } - if (!followerOrEscorter) - aggressive = true; } // If any of the above conditions turned actor1 aggressive towards actor2, do an awareness check. If it passes, diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f95df16855..0bb76add16 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1466,10 +1466,13 @@ namespace MWMechanics bool MechanicsManager::canCommitCrimeAgainst(const MWWorld::Ptr& target, const MWWorld::Ptr& attacker) { - const MWMechanics::AiSequence& seq = target.getClass().getCreatureStats(target).getAiSequence(); - return target.getClass().isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) - && !isAggressive(target, attacker) && !seq.isEngagedWithActor() - && !target.getClass().getCreatureStats(target).getAiSequence().isInPursuit(); + const MWWorld::Class& cls = target.getClass(); + const MWMechanics::CreatureStats& stats = cls.getCreatureStats(target); + const MWMechanics::AiSequence& seq = stats.getAiSequence(); + return cls.isNpc() && !attacker.isEmpty() && !seq.isInCombat(attacker) && !isAggressive(target, attacker) + && !seq.isEngagedWithActor() && !stats.getAiSequence().isInPursuit() + && !cls.getNpcStats(target).isWerewolf() + && stats.getMagicEffects().getOrDefault(ESM::MagicEffect::Vampirism).getMagnitude() <= 0; } void MechanicsManager::actorKilled(const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) From a9e6e63c4ee2a2fda7f92558990d8fb09237e0a3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 13:00:14 +0100 Subject: [PATCH 074/231] Remove fixed size reads --- apps/esmtool/record.cpp | 26 ++++---- apps/opencs/model/tools/racecheck.cpp | 8 +-- apps/opencs/model/world/columnimp.hpp | 31 ++++++--- .../model/world/nestedcoladapterimp.cpp | 16 ++--- apps/openmw/mwclass/npc.cpp | 24 +++---- apps/openmw/mwmechanics/character.cpp | 2 +- .../mwmechanics/mechanicsmanagerimp.cpp | 8 +-- apps/openmw/mwworld/projectilemanager.cpp | 2 +- components/esm/defs.hpp | 6 +- components/esm3/cellref.cpp | 4 +- components/esm3/esmreader.cpp | 5 +- components/esm3/esmreader.hpp | 2 +- components/esm3/loadfact.cpp | 47 ++++++++++++-- components/esm3/loadfact.hpp | 6 ++ components/esm3/loadrace.cpp | 64 ++++++++++++++++--- components/esm3/loadrace.hpp | 24 +++---- components/esm3/loadtes3.cpp | 3 +- components/esm3/loadtes3.hpp | 5 -- components/esm3/player.cpp | 4 +- components/esm3/projectilestate.cpp | 6 +- components/esm3/savedgame.cpp | 2 +- 21 files changed, 190 insertions(+), 105 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 96c418c0c4..71158299aa 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -1169,19 +1169,23 @@ namespace EsmTool std::cout << " Description: " << mData.mDescription << std::endl; std::cout << " Flags: " << raceFlags(mData.mData.mFlags) << std::endl; - for (int i = 0; i < 2; ++i) + std::cout << " Male:" << std::endl; + for (int j = 0; j < ESM::Attribute::Length; ++j) { - bool male = i == 0; - - std::cout << (male ? " Male:" : " Female:") << std::endl; - - for (int j = 0; j < ESM::Attribute::Length; ++j) - std::cout << " " << ESM::Attribute::indexToRefId(j) << ": " - << mData.mData.mAttributeValues[j].getValue(male) << std::endl; - - std::cout << " Height: " << mData.mData.mHeight.getValue(male) << std::endl; - std::cout << " Weight: " << mData.mData.mWeight.getValue(male) << std::endl; + ESM::RefId id = ESM::Attribute::indexToRefId(j); + std::cout << " " << id << ": " << mData.mData.getAttribute(id, true) << std::endl; } + std::cout << " Height: " << mData.mData.mMaleHeight << std::endl; + std::cout << " Weight: " << mData.mData.mMaleWeight << std::endl; + + std::cout << " Female:" << std::endl; + for (int j = 0; j < ESM::Attribute::Length; ++j) + { + ESM::RefId id = ESM::Attribute::indexToRefId(j); + std::cout << " " << id << ": " << mData.mData.getAttribute(id, false) << std::endl; + } + std::cout << " Height: " << mData.mData.mFemaleHeight << std::endl; + std::cout << " Weight: " << mData.mData.mFemaleWeight << std::endl; for (const auto& bonus : mData.mData.mBonus) // Not all races have 7 skills. diff --git a/apps/opencs/model/tools/racecheck.cpp b/apps/opencs/model/tools/racecheck.cpp index 78f72f44c5..8f0df823c3 100644 --- a/apps/opencs/model/tools/racecheck.cpp +++ b/apps/opencs/model/tools/racecheck.cpp @@ -41,17 +41,17 @@ void CSMTools::RaceCheckStage::performPerRecord(int stage, CSMDoc::Messages& mes messages.add(id, "Description is missing", "", CSMDoc::Message::Severity_Warning); // test for positive height - if (race.mData.mHeight.mMale <= 0) + if (race.mData.mMaleHeight <= 0) messages.add(id, "Male height is non-positive", "", CSMDoc::Message::Severity_Error); - if (race.mData.mHeight.mFemale <= 0) + if (race.mData.mFemaleHeight <= 0) messages.add(id, "Female height is non-positive", "", CSMDoc::Message::Severity_Error); // test for non-negative weight - if (race.mData.mWeight.mMale < 0) + if (race.mData.mMaleWeight < 0) messages.add(id, "Male weight is negative", "", CSMDoc::Message::Severity_Error); - if (race.mData.mWeight.mFemale < 0) + if (race.mData.mFemaleWeight < 0) messages.add(id, "Female weight is negative", "", CSMDoc::Message::Severity_Error); /// \todo check data members that can't be edited in the table view diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 5e5ff83fcf..da805d5c6d 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -570,19 +570,34 @@ namespace CSMWorld QVariant get(const Record& record) const override { - const ESM::Race::MaleFemaleF& value = mWeight ? record.get().mData.mWeight : record.get().mData.mHeight; - - return mMale ? value.mMale : value.mFemale; + if (mWeight) + { + if (mMale) + return record.get().mData.mMaleWeight; + return record.get().mData.mFemaleWeight; + } + if (mMale) + return record.get().mData.mMaleHeight; + return record.get().mData.mFemaleHeight; } void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - - ESM::Race::MaleFemaleF& value = mWeight ? record2.mData.mWeight : record2.mData.mHeight; - - (mMale ? value.mMale : value.mFemale) = data.toFloat(); - + if (mWeight) + { + if (mMale) + record2.mData.mMaleWeight = data.toFloat(); + else + record2.mData.mFemaleWeight = data.toFloat(); + } + else + { + if (mMale) + record2.mData.mMaleHeight = data.toFloat(); + else + record2.mData.mFemaleHeight = data.toFloat(); + } record.setModified(record2); } diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index b96cf46465..13ae821a77 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -741,8 +741,8 @@ namespace CSMWorld QVariant RaceAttributeAdapter::getData(const Record& record, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); - - if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length) + ESM::RefId attribute = ESM::Attribute::indexToRefId(subRowIndex); + if (attribute.empty()) throw std::runtime_error("index out of range"); switch (subColIndex) @@ -750,9 +750,9 @@ namespace CSMWorld case 0: return subRowIndex; case 1: - return race.mData.mAttributeValues[subRowIndex].mMale; + return race.mData.getAttribute(attribute, true); case 2: - return race.mData.mAttributeValues[subRowIndex].mFemale; + return race.mData.getAttribute(attribute, false); default: throw std::runtime_error("Race Attribute subcolumn index out of range"); } @@ -762,8 +762,8 @@ namespace CSMWorld Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { ESM::Race race = record.get(); - - if (subRowIndex < 0 || subRowIndex >= ESM::Attribute::Length) + ESM::RefId attribute = ESM::Attribute::indexToRefId(subRowIndex); + if (attribute.empty()) throw std::runtime_error("index out of range"); switch (subColIndex) @@ -771,10 +771,10 @@ namespace CSMWorld case 0: return; // throw an exception here? case 1: - race.mData.mAttributeValues[subRowIndex].mMale = value.toInt(); + race.mData.setAttribute(attribute, true, value.toInt()); break; case 2: - race.mData.mAttributeValues[subRowIndex].mFemale = value.toInt(); + race.mData.setAttribute(attribute, false, value.toInt()); break; default: throw std::runtime_error("Race Attribute subcolumn index out of range"); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 95a3b713fa..c6276753de 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -92,13 +92,7 @@ namespace const auto& attributes = MWBase::Environment::get().getESMStore()->get(); int level = creatureStats.getLevel(); for (const ESM::Attribute& attribute : attributes) - { - auto index = ESM::Attribute::refIdToIndex(attribute.mId); - assert(index >= 0); - - const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast(index)]; - creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); - } + creatureStats.setAttribute(attribute.mId, race->mData.getAttribute(attribute.mId, male)); // class bonus const ESM::Class* class_ = MWBase::Environment::get().getESMStore()->get().find(npc->mClass); @@ -1199,24 +1193,24 @@ namespace MWClass if (ptr == MWMechanics::getPlayer() && ptr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson()) { if (ref->mBase->isMale()) - scale *= race->mData.mHeight.mMale; + scale *= race->mData.mMaleHeight; else - scale *= race->mData.mHeight.mFemale; + scale *= race->mData.mFemaleHeight; return; } if (ref->mBase->isMale()) { - scale.x() *= race->mData.mWeight.mMale; - scale.y() *= race->mData.mWeight.mMale; - scale.z() *= race->mData.mHeight.mMale; + scale.x() *= race->mData.mMaleWeight; + scale.y() *= race->mData.mMaleWeight; + scale.z() *= race->mData.mMaleHeight; } else { - scale.x() *= race->mData.mWeight.mFemale; - scale.y() *= race->mData.mWeight.mFemale; - scale.z() *= race->mData.mHeight.mFemale; + scale.x() *= race->mData.mFemaleWeight; + scale.y() *= race->mData.mFemaleWeight; + scale.z() *= race->mData.mFemaleHeight; } } diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 713add719b..3d9f4352ec 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1966,7 +1966,7 @@ namespace MWMechanics { const ESM::NPC* npc = mPtr.get()->mBase; const ESM::Race* race = world->getStore().get().find(npc->mRace); - float weight = npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; + float weight = npc->isMale() ? race->mData.mMaleWeight : race->mData.mFemaleWeight; scale *= weight; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index f95df16855..9a5b09ffe2 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -152,13 +152,7 @@ namespace MWMechanics bool male = (player->mFlags & ESM::NPC::Female) == 0; for (const ESM::Attribute& attribute : esmStore.get()) - { - auto index = ESM::Attribute::refIdToIndex(attribute.mId); - assert(index >= 0); - - const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast(index)]; - creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); - } + creatureStats.setAttribute(attribute.mId, race->mData.getAttribute(attribute.mId, male)); for (const ESM::Skill& skill : esmStore.get()) { diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index d873f16a59..b36d2a3221 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -453,7 +453,7 @@ namespace MWWorld { const auto npc = caster.get()->mBase; const auto race = store.get().find(npc->mRace); - speed *= npc->isMale() ? race->mData.mWeight.mMale : race->mData.mWeight.mFemale; + speed *= npc->isMale() ? race->mData.mMaleWeight : race->mData.mFemaleWeight; } osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); direction.normalize(); diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 55404ee768..6254830f63 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -18,9 +18,9 @@ namespace ESM struct EpochTimeStamp { float mGameHour; - int mDay; - int mMonth; - int mYear; + int32_t mDay; + int32_t mMonth; + int32_t mYear; }; // Pixel color value. Standard four-byte rr,gg,bb,aa format. diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index c4c2fca986..42edec8f1f 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -116,7 +116,7 @@ namespace ESM cellRef.mTeleport = true; } else - esm.skipHTSized<24, ESM::Position>(); + esm.skipHSub(); break; case fourCC("DNAM"): getHStringOrSkip(cellRef.mDestCell); @@ -134,7 +134,7 @@ namespace ESM if constexpr (load) esm.getHT(cellRef.mPos.pos, cellRef.mPos.rot); else - esm.skipHTSized<24, decltype(cellRef.mPos)>(); + esm.skipHSub(); break; case fourCC("NAM0"): { diff --git a/components/esm3/esmreader.cpp b/components/esm3/esmreader.cpp index 77468f22d5..92a04fb487 100644 --- a/components/esm3/esmreader.cpp +++ b/components/esm3/esmreader.cpp @@ -263,7 +263,7 @@ namespace ESM { FormId res; if (wide) - getHNTSized<8>(res, tag); + getHNT(tag, res.mIndex, res.mContentFile); else getHNT(res.mIndex, tag); return res; @@ -496,7 +496,8 @@ namespace ESM case RefIdType::FormId: { FormId formId{}; - getTSized<8>(formId); + getT(formId.mIndex); + getT(formId.mContentFile); if (applyContentFileMapping(formId)) return RefId(formId); else diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 63db1634fc..53af8f69e6 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -333,7 +333,7 @@ namespace ESM mEsm->read(static_cast(x), static_cast(size)); } - void getName(NAME& name) { getTSized<4>(name); } + void getName(NAME& name) { getT(name.mData); } void getUint(uint32_t& u) { getT(u); } std::string getMaybeFixedStringSize(std::size_t size); diff --git a/components/esm3/loadfact.cpp b/components/esm3/loadfact.cpp index 9d4b832f97..1dd1fe1a0e 100644 --- a/components/esm3/loadfact.cpp +++ b/components/esm3/loadfact.cpp @@ -17,6 +17,47 @@ namespace ESM return mSkills.at(index); } + void RankData::load(ESMReader& esm) + { + esm.getT(mAttribute1); + esm.getT(mAttribute2); + esm.getT(mPrimarySkill); + esm.getT(mFavouredSkill); + esm.getT(mFactReaction); + } + + void RankData::save(ESMWriter& esm) const + { + esm.writeT(mAttribute1); + esm.writeT(mAttribute2); + esm.writeT(mPrimarySkill); + esm.writeT(mFavouredSkill); + esm.writeT(mFactReaction); + } + + void Faction::FADTstruct::load(ESMReader& esm) + { + esm.getSubHeader(); + esm.getT(mAttribute); + for (auto& rank : mRankData) + rank.load(esm); + esm.getT(mSkills); + esm.getT(mIsHidden); + if (mIsHidden > 1) + esm.fail("Unknown flag!"); + } + + void Faction::FADTstruct::save(ESMWriter& esm) const + { + esm.startSubRecord("FADT"); + esm.writeT(mAttribute); + for (const auto& rank : mRankData) + rank.save(esm); + esm.writeT(mSkills); + esm.writeT(mIsHidden); + esm.endRecord("FADT"); + } + void Faction::load(ESMReader& esm, bool& isDeleted) { isDeleted = false; @@ -47,9 +88,7 @@ namespace ESM mRanks[rankCounter++] = esm.getHString(); break; case fourCC("FADT"): - esm.getHTSized<240>(mData); - if (mData.mIsHidden > 1) - esm.fail("Unknown flag!"); + mData.load(esm); hasData = true; break; case fourCC("ANAM"): @@ -101,7 +140,7 @@ namespace ESM esm.writeHNString("RNAM", rank, 32); } - esm.writeHNT("FADT", mData, 240); + mData.save(esm); for (auto it = mReactions.begin(); it != mReactions.end(); ++it) { diff --git a/components/esm3/loadfact.hpp b/components/esm3/loadfact.hpp index 2359d276a2..eef2126514 100644 --- a/components/esm3/loadfact.hpp +++ b/components/esm3/loadfact.hpp @@ -30,6 +30,9 @@ namespace ESM int32_t mPrimarySkill, mFavouredSkill; int32_t mFactReaction; // Reaction from faction members + + void load(ESMReader& esm); + void save(ESMWriter& esm) const; }; struct Faction @@ -60,6 +63,9 @@ namespace ESM int32_t getSkill(size_t index, bool ignored = false) const; ///< Throws an exception for invalid values of \a index. + + void load(ESMReader& esm); + void save(ESMWriter& esm) const; }; // 240 bytes FADTstruct mData; diff --git a/components/esm3/loadrace.cpp b/components/esm3/loadrace.cpp index eb8faf40e9..8c7b89d07d 100644 --- a/components/esm3/loadrace.cpp +++ b/components/esm3/loadrace.cpp @@ -3,16 +3,61 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include + namespace ESM { - int Race::MaleFemale::getValue(bool male) const + int32_t Race::RADTstruct::getAttribute(ESM::RefId attribute, bool male) const { - return male ? mMale : mFemale; + int index = ESM::Attribute::refIdToIndex(attribute); + if (index < 0) + return 0; + if (!male) + index++; + return mAttributeValues[static_cast(index)]; } - float Race::MaleFemaleF::getValue(bool male) const + void Race::RADTstruct::setAttribute(ESM::RefId attribute, bool male, int32_t value) { - return male ? mMale : mFemale; + int index = ESM::Attribute::refIdToIndex(attribute); + if (index < 0) + return; + if (!male) + index++; + mAttributeValues[static_cast(index)] = value; + } + + void Race::RADTstruct::load(ESMReader& esm) + { + esm.getSubHeader(); + for (auto& bonus : mBonus) + { + esm.getT(bonus.mSkill); + esm.getT(bonus.mBonus); + } + esm.getT(mAttributeValues); + esm.getT(mMaleHeight); + esm.getT(mFemaleHeight); + esm.getT(mMaleWeight); + esm.getT(mFemaleWeight); + esm.getT(mFlags); + } + + void Race::RADTstruct::save(ESMWriter& esm) const + { + esm.startSubRecord("RADT"); + for (const auto& bonus : mBonus) + { + esm.writeT(bonus.mSkill); + esm.writeT(bonus.mBonus); + } + esm.writeT(mAttributeValues); + esm.writeT(mMaleHeight); + esm.writeT(mFemaleHeight); + esm.writeT(mMaleWeight); + esm.writeT(mFemaleWeight); + esm.writeT(mFlags); + esm.endRecord("RADT"); } void Race::load(ESMReader& esm, bool& isDeleted) @@ -37,7 +82,7 @@ namespace ESM mName = esm.getHString(); break; case fourCC("RADT"): - esm.getHTSized<140>(mData); + mData.load(esm); hasData = true; break; case fourCC("DESC"): @@ -71,7 +116,7 @@ namespace ESM } esm.writeHNOCString("FNAM", mName); - esm.writeHNT("RADT", mData, 140); + mData.save(esm); mPowers.save(esm); esm.writeHNOString("DESC", mDescription); } @@ -90,11 +135,10 @@ namespace ESM bonus.mBonus = 0; } - for (auto& attribute : mData.mAttributeValues) - attribute.mMale = attribute.mFemale = 1; + mData.mAttributeValues.fill(1); - mData.mHeight.mMale = mData.mHeight.mFemale = 1; - mData.mWeight.mMale = mData.mWeight.mFemale = 1; + mData.mMaleHeight = mData.mFemaleHeight = 1; + mData.mMaleWeight = mData.mFemaleWeight = 1; mData.mFlags = 0; } diff --git a/components/esm3/loadrace.hpp b/components/esm3/loadrace.hpp index 8cb9d76118..4493240ac8 100644 --- a/components/esm3/loadrace.hpp +++ b/components/esm3/loadrace.hpp @@ -31,20 +31,6 @@ namespace ESM int32_t mBonus; }; - struct MaleFemale - { - int32_t mMale, mFemale; - - int getValue(bool male) const; - }; - - struct MaleFemaleF - { - float mMale, mFemale; - - float getValue(bool male) const; - }; - enum Flags { Playable = 0x01, @@ -57,14 +43,20 @@ namespace ESM std::array mBonus; // Attribute values for male/female - std::array mAttributeValues; + std::array mAttributeValues; // The actual eye level height (in game units) is (probably) given // as 'height' times 128. This has not been tested yet. - MaleFemaleF mHeight, mWeight; + float mMaleHeight, mFemaleHeight, mMaleWeight, mFemaleWeight; int32_t mFlags; // 0x1 - playable, 0x2 - beast race + int32_t getAttribute(ESM::RefId attribute, bool male) const; + void setAttribute(ESM::RefId attribute, bool male, int32_t value); + + void load(ESMReader& esm); + void save(ESMWriter& esm) const; + }; // Size = 140 bytes RADTstruct mData; diff --git a/components/esm3/loadtes3.cpp b/components/esm3/loadtes3.cpp index 86b62234da..131510ce89 100644 --- a/components/esm3/loadtes3.cpp +++ b/components/esm3/loadtes3.cpp @@ -45,7 +45,8 @@ namespace ESM if (esm.isNextSub("GMDT")) { - esm.getHTSized<124>(mGameData); + esm.getHT(mGameData.mCurrentHealth, mGameData.mMaximumHealth, mGameData.mHour, mGameData.unknown1, + mGameData.mCurrentCell.mData, mGameData.unknown2, mGameData.mPlayerName.mData); } if (esm.isNextSub("SCRD")) { diff --git a/components/esm3/loadtes3.hpp b/components/esm3/loadtes3.hpp index 8b14d41645..2f7493e15f 100644 --- a/components/esm3/loadtes3.hpp +++ b/components/esm3/loadtes3.hpp @@ -11,9 +11,6 @@ namespace ESM class ESMReader; class ESMWriter; -#pragma pack(push) -#pragma pack(1) - struct Data { /* File format version. This is actually a float, the supported @@ -38,8 +35,6 @@ namespace ESM NAME32 mPlayerName; }; -#pragma pack(pop) - /// \brief File header record struct Header { diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index 3b52f8d779..901b52ce1d 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -13,12 +13,12 @@ namespace ESM mCellId = esm.getCellId(); - esm.getHNTSized<12>(mLastKnownExteriorPosition, "LKEP"); + esm.getHNT("LKEP", mLastKnownExteriorPosition); if (esm.isNextSub("MARK")) { mHasMark = true; - esm.getHTSized<24>(mMarkedPosition); + esm.getHT(mMarkedPosition.pos, mMarkedPosition.rot); mMarkedCell = esm.getCellId(); } else diff --git a/components/esm3/projectilestate.cpp b/components/esm3/projectilestate.cpp index bed9073999..e20cefa882 100644 --- a/components/esm3/projectilestate.cpp +++ b/components/esm3/projectilestate.cpp @@ -17,8 +17,8 @@ namespace ESM void BaseProjectileState::load(ESMReader& esm) { mId = esm.getHNRefId("ID__"); - esm.getHNTSized<12>(mPosition, "VEC3"); - esm.getHNTSized<16>(mOrientation, "QUAT"); + esm.getHNT("VEC3", mPosition.mValues); + esm.getHNT("QUAT", mOrientation.mValues); esm.getHNT(mActorId, "ACTO"); } @@ -58,7 +58,7 @@ namespace ESM BaseProjectileState::load(esm); mBowId = esm.getHNRefId("BOW_"); - esm.getHNTSized<12>(mVelocity, "VEL_"); + esm.getHNT("VEL_", mVelocity.mValues); mAttackStrength = 1.f; esm.getHNOT(mAttackStrength, "STR_"); diff --git a/components/esm3/savedgame.cpp b/components/esm3/savedgame.cpp index cec2b5e189..3ffe062d76 100644 --- a/components/esm3/savedgame.cpp +++ b/components/esm3/savedgame.cpp @@ -17,7 +17,7 @@ namespace ESM mPlayerCellName = esm.getHNRefId("PLCE").toString(); else mPlayerCellName = esm.getHNString("PLCE"); - esm.getHNTSized<16>(mInGameTime, "TSTM"); + esm.getHNT("TSTM", mInGameTime.mGameHour, mInGameTime.mDay, mInGameTime.mMonth, mInGameTime.mYear); esm.getHNT(mTimePlayed, "TIME"); mDescription = esm.getHNString("DESC"); From dbf9d42cc5ce3f3f782a47daa98a80c7b959d470 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 14:03:45 +0100 Subject: [PATCH 075/231] Remove sized reads from essimporter --- apps/essimporter/converter.cpp | 8 ++++---- apps/essimporter/importacdt.hpp | 7 ++----- apps/essimporter/importcellref.cpp | 19 +++++++++++++------ apps/essimporter/importcntc.hpp | 2 +- apps/essimporter/importcrec.hpp | 2 +- apps/essimporter/importdial.cpp | 4 ++-- apps/essimporter/importdial.hpp | 2 +- apps/essimporter/importgame.cpp | 22 +++++++++++----------- apps/essimporter/importgame.hpp | 8 ++++---- apps/essimporter/importinventory.cpp | 10 +++++----- apps/essimporter/importinventory.hpp | 6 +++--- apps/essimporter/importklst.cpp | 2 +- apps/essimporter/importklst.hpp | 4 ++-- apps/essimporter/importnpcc.cpp | 2 +- apps/essimporter/importnpcc.hpp | 2 +- apps/essimporter/importplayer.cpp | 14 ++++++++++---- apps/essimporter/importplayer.hpp | 27 ++++++++++++--------------- apps/essimporter/importproj.cpp | 4 +++- apps/essimporter/importproj.h | 5 +---- apps/essimporter/importscpt.cpp | 3 ++- apps/essimporter/importscpt.hpp | 2 +- apps/essimporter/importscri.cpp | 6 +++--- apps/essimporter/importsplm.cpp | 6 ++++-- apps/essimporter/importsplm.h | 16 ++++++---------- 24 files changed, 94 insertions(+), 89 deletions(-) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 4751fd9497..00c24514ea 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -90,14 +90,14 @@ namespace ESSImport struct MAPH { - unsigned int size; - unsigned int value; + uint32_t size; + uint32_t value; }; void ConvertFMAP::read(ESM::ESMReader& esm) { MAPH maph; - esm.getHNTSized<8>(maph, "MAPH"); + esm.getHNT("MAPH", maph.size, maph.value); std::vector data; esm.getSubNameIs("MAPD"); esm.getSubHeader(); @@ -278,7 +278,7 @@ namespace ESSImport while (esm.isNextSub("MPCD")) { float notepos[3]; - esm.getHTSized<3 * sizeof(float)>(notepos); + esm.getHT(notepos); // Markers seem to be arranged in a 32*32 grid, notepos has grid-indices. // This seems to be the reason markers can't be placed everywhere in interior cells, diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index 785e988200..54910ac82c 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -25,14 +25,12 @@ namespace ESSImport }; /// Actor data, shared by (at least) REFR and CellRef -#pragma pack(push) -#pragma pack(1) struct ACDT { // Note, not stored at *all*: // - Level changes are lost on reload, except for the player (there it's in the NPC record). unsigned char mUnknown[12]; - unsigned int mFlags; + uint32_t mFlags; float mBreathMeter; // Seconds left before drowning unsigned char mUnknown2[20]; float mDynamic[3][2]; @@ -41,7 +39,7 @@ namespace ESSImport float mMagicEffects[27]; // Effect attributes: // https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes unsigned char mUnknown4[4]; - unsigned int mGoldPool; + uint32_t mGoldPool; unsigned char mCountDown; // seen the same value as in ACSC.mCorpseClearCountdown, maybe // this one is for respawning? unsigned char mUnknown5[3]; @@ -60,7 +58,6 @@ namespace ESSImport unsigned char mUnknown[3]; float mTime; }; -#pragma pack(pop) struct ActorData { diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index a900440a96..d9639eb0dd 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -48,14 +48,18 @@ namespace ESSImport if (esm.isNextSub("ACDT")) { mActorData.mHasACDT = true; - esm.getHTSized<264>(mActorData.mACDT); + esm.getHT(mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter, + mActorData.mACDT.mUnknown2, mActorData.mACDT.mDynamic, mActorData.mACDT.mUnknown3, + mActorData.mACDT.mAttributes, mActorData.mACDT.mMagicEffects, mActorData.mACDT.mUnknown4, + mActorData.mACDT.mGoldPool, mActorData.mACDT.mCountDown, mActorData.mACDT.mUnknown5); } mActorData.mHasACSC = false; if (esm.isNextSub("ACSC")) { mActorData.mHasACSC = true; - esm.getHTSized<112>(mActorData.mACSC); + esm.getHT(mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, mActorData.mACSC.mUnknown2, + mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3); } if (esm.isNextSub("ACSL")) @@ -137,7 +141,7 @@ namespace ESSImport if (esm.isNextSub("ANIS")) { mActorData.mHasANIS = true; - esm.getHTSized<8>(mActorData.mANIS); + esm.getHT(mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime); } if (esm.isNextSub("LVCR")) @@ -155,13 +159,16 @@ namespace ESSImport // DATA should occur for all references, except levelled creature spawners // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess - esm.getHNOTSized<24>(mPos, "DATA"); - esm.getHNOTSized<24>(mPos, "DATA"); + for (int i = 0; i < 2; ++i) + { + if (esm.isNextSub("DATA")) + esm.getHNT("DATA", mPos.pos, mPos.rot); + } mDeleted = 0; if (esm.isNextSub("DELE")) { - unsigned int deleted; + uint32_t deleted; esm.getHT(deleted); mDeleted = ((deleted >> 24) & 0x2) != 0; // the other 3 bytes seem to be uninitialized garbage } diff --git a/apps/essimporter/importcntc.hpp b/apps/essimporter/importcntc.hpp index 1bc7d94bd5..6ee843805e 100644 --- a/apps/essimporter/importcntc.hpp +++ b/apps/essimporter/importcntc.hpp @@ -14,7 +14,7 @@ namespace ESSImport /// Changed container contents struct CNTC { - int mIndex; + int32_t mIndex; Inventory mInventory; diff --git a/apps/essimporter/importcrec.hpp b/apps/essimporter/importcrec.hpp index 77933eafe8..87a2d311c8 100644 --- a/apps/essimporter/importcrec.hpp +++ b/apps/essimporter/importcrec.hpp @@ -15,7 +15,7 @@ namespace ESSImport /// Creature changes struct CREC { - int mIndex; + int32_t mIndex; Inventory mInventory; ESM::AIPackageList mAiPackages; diff --git a/apps/essimporter/importdial.cpp b/apps/essimporter/importdial.cpp index 6c45f9d059..43905738a1 100644 --- a/apps/essimporter/importdial.cpp +++ b/apps/essimporter/importdial.cpp @@ -8,11 +8,11 @@ namespace ESSImport void DIAL::load(ESM::ESMReader& esm) { // See ESM::Dialogue::Type enum, not sure why we would need this here though - int type = 0; + int32_t type = 0; esm.getHNOT(type, "DATA"); // Deleted dialogue in a savefile. No clue what this means... - int deleted = 0; + int32_t deleted = 0; esm.getHNOT(deleted, "DELE"); mIndex = 0; diff --git a/apps/essimporter/importdial.hpp b/apps/essimporter/importdial.hpp index 9a1e882332..2a09af9f2a 100644 --- a/apps/essimporter/importdial.hpp +++ b/apps/essimporter/importdial.hpp @@ -10,7 +10,7 @@ namespace ESSImport struct DIAL { - int mIndex; // Journal index + int32_t mIndex; // Journal index void load(ESM::ESMReader& esm); }; diff --git a/apps/essimporter/importgame.cpp b/apps/essimporter/importgame.cpp index 5295d2a1e8..8161a20031 100644 --- a/apps/essimporter/importgame.cpp +++ b/apps/essimporter/importgame.cpp @@ -9,17 +9,17 @@ namespace ESSImport { esm.getSubNameIs("GMDT"); esm.getSubHeader(); - if (esm.getSubSize() == 92) - { - esm.getExact(&mGMDT, 92); - mGMDT.mSecundaPhase = 0; - } - else if (esm.getSubSize() == 96) - { - esm.getTSized<96>(mGMDT); - } - else - esm.fail("unexpected subrecord size for GAME.GMDT"); + bool hasSecundaPhase = esm.getSubSize() == 96; + esm.getT(mGMDT.mCellName); + esm.getT(mGMDT.mFogColour); + esm.getT(mGMDT.mFogDensity); + esm.getT(mGMDT.mCurrentWeather); + esm.getT(mGMDT.mNextWeather); + esm.getT(mGMDT.mWeatherTransition); + esm.getT(mGMDT.mTimeOfNextTransition); + esm.getT(mGMDT.mMasserPhase); + if (hasSecundaPhase) + esm.getT(mGMDT.mSecundaPhase); mGMDT.mWeatherTransition &= (0x000000ff); mGMDT.mSecundaPhase &= (0x000000ff); diff --git a/apps/essimporter/importgame.hpp b/apps/essimporter/importgame.hpp index 8b26b9d8bd..e880166b45 100644 --- a/apps/essimporter/importgame.hpp +++ b/apps/essimporter/importgame.hpp @@ -15,12 +15,12 @@ namespace ESSImport struct GMDT { char mCellName[64]{}; - int mFogColour{ 0 }; + int32_t mFogColour{ 0 }; float mFogDensity{ 0.f }; - int mCurrentWeather{ 0 }, mNextWeather{ 0 }; - int mWeatherTransition{ 0 }; // 0-100 transition between weathers, top 3 bytes may be garbage + int32_t mCurrentWeather{ 0 }, mNextWeather{ 0 }; + int32_t mWeatherTransition{ 0 }; // 0-100 transition between weathers, top 3 bytes may be garbage float mTimeOfNextTransition{ 0.f }; // weather changes when gamehour == timeOfNextTransition - int mMasserPhase{ 0 }, mSecundaPhase{ 0 }; // top 3 bytes may be garbage + int32_t mMasserPhase{ 0 }, mSecundaPhase{ 0 }; // top 3 bytes may be garbage }; GMDT mGMDT; diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index 9d71c04f2a..f1db301bd0 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -12,7 +12,7 @@ namespace ESSImport while (esm.isNextSub("NPCO")) { ContItem contItem; - esm.getHTSized<36>(contItem); + esm.getHT(contItem.mCount, contItem.mItem.mData); InventoryItem item; item.mId = contItem.mItem.toString(); @@ -28,7 +28,7 @@ namespace ESSImport bool newStack = esm.isNextSub("XIDX"); if (newStack) { - unsigned int idx; + uint32_t idx; esm.getHT(idx); separateStacks = true; item.mCount = 1; @@ -40,7 +40,7 @@ namespace ESSImport bool isDeleted = false; item.ESM::CellRef::loadData(esm, isDeleted); - int charge = -1; + int32_t charge = -1; esm.getHNOT(charge, "XHLT"); item.mChargeInt = charge; @@ -60,7 +60,7 @@ namespace ESSImport // this is currently not handled properly. esm.getSubHeader(); - int itemIndex; // index of the item in the NPCO list + int32_t itemIndex; // index of the item in the NPCO list esm.getT(itemIndex); if (itemIndex < 0 || itemIndex >= int(mItems.size())) @@ -68,7 +68,7 @@ namespace ESSImport // appears to be a relative index for only the *possible* slots this item can be equipped in, // i.e. 0 most of the time - int slotIndex; + int32_t slotIndex; esm.getT(slotIndex); mItems[itemIndex].mRelativeEquipmentSlot = slotIndex; diff --git a/apps/essimporter/importinventory.hpp b/apps/essimporter/importinventory.hpp index 7a11b3f0a0..9e0dcbb30a 100644 --- a/apps/essimporter/importinventory.hpp +++ b/apps/essimporter/importinventory.hpp @@ -19,7 +19,7 @@ namespace ESSImport struct ContItem { - int mCount; + int32_t mCount; ESM::NAME32 mItem; }; @@ -28,8 +28,8 @@ namespace ESSImport struct InventoryItem : public ESM::CellRef { std::string mId; - int mCount; - int mRelativeEquipmentSlot; + int32_t mCount; + int32_t mRelativeEquipmentSlot; SCRI mSCRI; }; std::vector mItems; diff --git a/apps/essimporter/importklst.cpp b/apps/essimporter/importklst.cpp index d4cfc7f769..2d5e09e913 100644 --- a/apps/essimporter/importklst.cpp +++ b/apps/essimporter/importklst.cpp @@ -10,7 +10,7 @@ namespace ESSImport while (esm.isNextSub("KNAM")) { std::string refId = esm.getHString(); - int count; + int32_t count; esm.getHNT(count, "CNAM"); mKillCounter[refId] = count; } diff --git a/apps/essimporter/importklst.hpp b/apps/essimporter/importklst.hpp index 7c1ff03bb6..8a098ac501 100644 --- a/apps/essimporter/importklst.hpp +++ b/apps/essimporter/importklst.hpp @@ -18,9 +18,9 @@ namespace ESSImport void load(ESM::ESMReader& esm); /// RefId, kill count - std::map mKillCounter; + std::map mKillCounter; - int mWerewolfKills; + int32_t mWerewolfKills; }; } diff --git a/apps/essimporter/importnpcc.cpp b/apps/essimporter/importnpcc.cpp index c115040074..c1a53b6cef 100644 --- a/apps/essimporter/importnpcc.cpp +++ b/apps/essimporter/importnpcc.cpp @@ -7,7 +7,7 @@ namespace ESSImport void NPCC::load(ESM::ESMReader& esm) { - esm.getHNTSized<8>(mNPDT, "NPDT"); + esm.getHNT("NPDT", mNPDT.mDisposition, mNPDT.unknown, mNPDT.mReputation, mNPDT.unknown2, mNPDT.mIndex); while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") || esm.isNextSub("AI_A")) diff --git a/apps/essimporter/importnpcc.hpp b/apps/essimporter/importnpcc.hpp index 762add1906..f3532f2103 100644 --- a/apps/essimporter/importnpcc.hpp +++ b/apps/essimporter/importnpcc.hpp @@ -21,7 +21,7 @@ namespace ESSImport unsigned char unknown; unsigned char mReputation; unsigned char unknown2; - int mIndex; + int32_t mIndex; } mNPDT; Inventory mInventory; diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp index 165926d15a..b9e1d4f291 100644 --- a/apps/essimporter/importplayer.cpp +++ b/apps/essimporter/importplayer.cpp @@ -19,7 +19,12 @@ namespace ESSImport mMNAM = esm.getHString(); } - esm.getHNTSized<212>(mPNAM, "PNAM"); + esm.getHNT("PNAM", mPNAM.mPlayerFlags, mPNAM.mLevelProgress, mPNAM.mSkillProgress, mPNAM.mSkillIncreases, + mPNAM.mTelekinesisRangeBonus, mPNAM.mVisionBonus, mPNAM.mDetectKeyMagnitude, + mPNAM.mDetectEnchantmentMagnitude, mPNAM.mDetectAnimalMagnitude, mPNAM.mMarkLocation.mX, + mPNAM.mMarkLocation.mY, mPNAM.mMarkLocation.mZ, mPNAM.mMarkLocation.mRotZ, mPNAM.mMarkLocation.mCellX, + mPNAM.mMarkLocation.mCellY, mPNAM.mUnknown3, mPNAM.mVerticalRotation.mData, mPNAM.mSpecIncreases, + mPNAM.mUnknown4); if (esm.isNextSub("SNAM")) esm.skipHSub(); @@ -54,7 +59,7 @@ namespace ESSImport if (esm.isNextSub("ENAM")) { mHasENAM = true; - esm.getHTSized<8>(mENAM); + esm.getHT(mENAM.mCellX, mENAM.mCellY); } if (esm.isNextSub("LNAM")) @@ -63,7 +68,8 @@ namespace ESSImport while (esm.isNextSub("FNAM")) { FNAM fnam; - esm.getHTSized<44>(fnam); + esm.getHT( + fnam.mRank, fnam.mUnknown1, fnam.mReputation, fnam.mFlags, fnam.mUnknown2, fnam.mFactionName.mData); mFactions.push_back(fnam); } @@ -71,7 +77,7 @@ namespace ESSImport if (esm.isNextSub("AADT")) // Attack animation data? { mHasAADT = true; - esm.getHTSized<44>(mAADT); + esm.getHT(mAADT.animGroupIndex, mAADT.mUnknown5); } if (esm.isNextSub("KNAM")) diff --git a/apps/essimporter/importplayer.hpp b/apps/essimporter/importplayer.hpp index 0fb820cb64..3602abd9c3 100644 --- a/apps/essimporter/importplayer.hpp +++ b/apps/essimporter/importplayer.hpp @@ -17,7 +17,7 @@ namespace ESSImport /// Other player data struct PCDT { - int mBounty; + int32_t mBounty; std::string mBirthsign; std::vector mKnownDialogueTopics; @@ -41,13 +41,11 @@ namespace ESSImport PlayerFlags_LevitationDisabled = 0x80000 }; -#pragma pack(push) -#pragma pack(1) struct FNAM { unsigned char mRank; unsigned char mUnknown1[3]; - int mReputation; + int32_t mReputation; unsigned char mFlags; // 0x1: unknown, 0x2: expelled unsigned char mUnknown2[3]; ESM::NAME32 mFactionName; @@ -59,7 +57,7 @@ namespace ESSImport { float mX, mY, mZ; // worldspace position float mRotZ; // Z angle in radians - int mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0) + int32_t mCellX, mCellY; // grid coordinates; for interior cells this is always (0, 0) }; struct Rotation @@ -67,15 +65,15 @@ namespace ESSImport float mData[3][3]; }; - int mPlayerFlags; // controls, camera and draw state - unsigned int mLevelProgress; + int32_t mPlayerFlags; // controls, camera and draw state + uint32_t mLevelProgress; float mSkillProgress[27]; // skill progress, non-uniform scaled unsigned char mSkillIncreases[8]; // number of skill increases for each attribute - int mTelekinesisRangeBonus; // in units; seems redundant + int32_t mTelekinesisRangeBonus; // in units; seems redundant float mVisionBonus; // range: <0.0, 1.0>; affected by light spells and Get/Mod/SetPCVisionBonus - int mDetectKeyMagnitude; // seems redundant - int mDetectEnchantmentMagnitude; // seems redundant - int mDetectAnimalMagnitude; // seems redundant + int32_t mDetectKeyMagnitude; // seems redundant + int32_t mDetectEnchantmentMagnitude; // seems redundant + int32_t mDetectAnimalMagnitude; // seems redundant MarkLocation mMarkLocation; unsigned char mUnknown3[4]; Rotation mVerticalRotation; @@ -85,16 +83,15 @@ namespace ESSImport struct ENAM { - int mCellX; - int mCellY; + int32_t mCellX; + int32_t mCellY; }; struct AADT // 44 bytes { - int animGroupIndex; // See convertANIS() for the mapping. + int32_t animGroupIndex; // See convertANIS() for the mapping. unsigned char mUnknown5[40]; }; -#pragma pack(pop) std::vector mFactions; PNAM mPNAM; diff --git a/apps/essimporter/importproj.cpp b/apps/essimporter/importproj.cpp index f9a92095e0..a09ade81dd 100644 --- a/apps/essimporter/importproj.cpp +++ b/apps/essimporter/importproj.cpp @@ -10,7 +10,9 @@ namespace ESSImport while (esm.isNextSub("PNAM")) { PNAM pnam; - esm.getHTSized<184>(pnam); + esm.getHT(pnam.mAttackStrength, pnam.mSpeed, pnam.mUnknown, pnam.mFlightTime, pnam.mSplmIndex, + pnam.mUnknown2, pnam.mVelocity.mValues, pnam.mPosition.mValues, pnam.mUnknown3, pnam.mActorId.mData, + pnam.mArrowId.mData, pnam.mBowId.mData); mProjectiles.push_back(pnam); } } diff --git a/apps/essimporter/importproj.h b/apps/essimporter/importproj.h index d1c544f66f..01592af3e3 100644 --- a/apps/essimporter/importproj.h +++ b/apps/essimporter/importproj.h @@ -16,15 +16,13 @@ namespace ESSImport struct PROJ { -#pragma pack(push) -#pragma pack(1) struct PNAM // 184 bytes { float mAttackStrength; float mSpeed; unsigned char mUnknown[4 * 2]; float mFlightTime; - int mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles) + int32_t mSplmIndex; // reference to a SPLM record (0 for ballistic projectiles) unsigned char mUnknown2[4]; ESM::Vector3 mVelocity; ESM::Vector3 mPosition; @@ -35,7 +33,6 @@ namespace ESSImport bool isMagic() const { return mSplmIndex != 0; } }; -#pragma pack(pop) std::vector mProjectiles; diff --git a/apps/essimporter/importscpt.cpp b/apps/essimporter/importscpt.cpp index 746d0b90e7..8fe4afd336 100644 --- a/apps/essimporter/importscpt.cpp +++ b/apps/essimporter/importscpt.cpp @@ -7,7 +7,8 @@ namespace ESSImport void SCPT::load(ESM::ESMReader& esm) { - esm.getHNTSized<52>(mSCHD, "SCHD"); + esm.getHNT("SCHD", mSCHD.mName.mData, mSCHD.mData.mNumShorts, mSCHD.mData.mNumLongs, mSCHD.mData.mNumFloats, + mSCHD.mData.mScriptDataSize, mSCHD.mData.mStringTableSize); mSCRI.load(esm); diff --git a/apps/essimporter/importscpt.hpp b/apps/essimporter/importscpt.hpp index 8f60532447..a75a3e38da 100644 --- a/apps/essimporter/importscpt.hpp +++ b/apps/essimporter/importscpt.hpp @@ -29,7 +29,7 @@ namespace ESSImport SCRI mSCRI; bool mRunning; - int mRefNum; // Targeted reference, -1: no reference + int32_t mRefNum; // Targeted reference, -1: no reference void load(ESM::ESMReader& esm); }; diff --git a/apps/essimporter/importscri.cpp b/apps/essimporter/importscri.cpp index b6c1d4094c..c0425cef32 100644 --- a/apps/essimporter/importscri.cpp +++ b/apps/essimporter/importscri.cpp @@ -9,7 +9,7 @@ namespace ESSImport { mScript = esm.getHNOString("SCRI"); - int numShorts = 0, numLongs = 0, numFloats = 0; + int32_t numShorts = 0, numLongs = 0, numFloats = 0; if (esm.isNextSub("SLCS")) { esm.getSubHeader(); @@ -23,7 +23,7 @@ namespace ESSImport esm.getSubHeader(); for (int i = 0; i < numShorts; ++i) { - short val; + int16_t val; esm.getT(val); mShorts.push_back(val); } @@ -35,7 +35,7 @@ namespace ESSImport esm.getSubHeader(); for (int i = 0; i < numLongs; ++i) { - int val; + int32_t val; esm.getT(val); mLongs.push_back(val); } diff --git a/apps/essimporter/importsplm.cpp b/apps/essimporter/importsplm.cpp index a0478f4d92..6019183f83 100644 --- a/apps/essimporter/importsplm.cpp +++ b/apps/essimporter/importsplm.cpp @@ -11,13 +11,15 @@ namespace ESSImport { ActiveSpell spell; esm.getHT(spell.mIndex); - esm.getHNTSized<160>(spell.mSPDT, "SPDT"); + esm.getHNT("SPDT", spell.mSPDT.mType, spell.mSPDT.mId.mData, spell.mSPDT.mUnknown, + spell.mSPDT.mCasterId.mData, spell.mSPDT.mSourceId.mData, spell.mSPDT.mUnknown2); spell.mTarget = esm.getHNOString("TNAM"); while (esm.isNextSub("NPDT")) { ActiveEffect effect; - esm.getHTSized<56>(effect.mNPDT); + esm.getHT(effect.mNPDT.mAffectedActorId.mData, effect.mNPDT.mUnknown, effect.mNPDT.mMagnitude, + effect.mNPDT.mSecondsActive, effect.mNPDT.mUnknown2); // Effect-specific subrecords can follow: // - INAM for disintegration and bound effects diff --git a/apps/essimporter/importsplm.h b/apps/essimporter/importsplm.h index 8187afb131..39405db6d3 100644 --- a/apps/essimporter/importsplm.h +++ b/apps/essimporter/importsplm.h @@ -15,11 +15,9 @@ namespace ESSImport struct SPLM { -#pragma pack(push) -#pragma pack(1) struct SPDT // 160 bytes { - int mType; // 1 = spell, 2 = enchantment, 3 = potion + int32_t mType; // 1 = spell, 2 = enchantment, 3 = potion ESM::NAME32 mId; // base ID of a spell/enchantment/potion unsigned char mUnknown[4 * 4]; ESM::NAME32 mCasterId; @@ -31,31 +29,29 @@ namespace ESSImport { ESM::NAME32 mAffectedActorId; unsigned char mUnknown[4 * 2]; - int mMagnitude; + int32_t mMagnitude; float mSecondsActive; unsigned char mUnknown2[4 * 2]; }; struct INAM // 40 bytes { - int mUnknown; + int32_t mUnknown; unsigned char mUnknown2; ESM::FixedString<35> mItemId; // disintegrated item / bound item / item to re-equip after expiration }; struct CNAM // 36 bytes { - int mUnknown; // seems to always be 0 + int32_t mUnknown; // seems to always be 0 ESM::NAME32 mSummonedOrCommandedActor[32]; }; struct VNAM // 4 bytes { - int mUnknown; + int32_t mUnknown; }; -#pragma pack(pop) - struct ActiveEffect { NPDT mNPDT; @@ -63,7 +59,7 @@ namespace ESSImport struct ActiveSpell { - int mIndex; + int32_t mIndex; SPDT mSPDT; std::string mTarget; std::vector mActiveEffects; From c10b9297f0b7ee7208e891d34759d870c6b34a69 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 14:05:10 +0100 Subject: [PATCH 076/231] Remove Sized methods from ESMReader --- components/esm3/esmreader.hpp | 49 ++++------------------------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index 53af8f69e6..d753023645 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -131,10 +131,10 @@ namespace ESM ESM::RefId getCellId(); // Read data of a given type, stored in a subrecord of a given name - template >> + template void getHNT(X& x, NAME name) { - getHNTSized(x, name); + getHNT(name, x); } template @@ -149,26 +149,11 @@ namespace ESM } // Optional version of getHNT - template >> + template void getHNOT(X& x, NAME name) - { - getHNOTSized(x, name); - } - - // Version with extra size checking, to make sure the compiler - // doesn't mess up our struct padding. - template - void getHNTSized(X& x, NAME name) - { - getSubNameIs(name); - getHTSized(x); - } - - template - void getHNOTSized(X& x, NAME name) { if (isNextSub(name)) - getHTSized(x); + getHT(x); } // Get data of a given type/size, including subrecord header @@ -185,37 +170,13 @@ namespace ESM template >> void skipHT() { - skipHTSized(); - } - - // Version with extra size checking, to make sure the compiler - // doesn't mess up our struct padding. - template - void getHTSized(X& x) - { - getSubHeader(); - if (mCtx.leftSub != size) - reportSubSizeMismatch(size, mCtx.leftSub); - getTSized(x); - } - - template - void skipHTSized() - { - static_assert(sizeof(T) == size); + constexpr size_t size = sizeof(T); getSubHeader(); if (mCtx.leftSub != size) reportSubSizeMismatch(size, mCtx.leftSub); skip(size); } - template - void getTSized(X& x) - { - static_assert(sizeof(X) == size); - getExact(&x, size); - } - // Read a string by the given name if it is the next record. std::string getHNOString(NAME name); From 37415b0382df3853caa3aac689db74e41d26176d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 15:16:32 +0100 Subject: [PATCH 077/231] Don't use getExact to read structs --- apps/esmtool/record.cpp | 10 +--- .../opencs/model/tools/referenceablecheck.cpp | 22 ++----- apps/opencs/model/world/refidadapterimp.cpp | 59 ++----------------- apps/openmw/mwclass/npc.cpp | 10 +--- .../mwmechanics/mechanicsmanagerimp.cpp | 11 +--- components/esm3/loadnpc.cpp | 31 ++++++---- components/esm3/loadnpc.hpp | 3 +- components/esm3/loadpgrd.cpp | 7 ++- components/esm3/loadrace.cpp | 2 + 9 files changed, 47 insertions(+), 108 deletions(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 71158299aa..044fbf9f93 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -1084,14 +1084,8 @@ namespace EsmTool std::cout << " Rank: " << (int)mData.mNpdt.mRank << std::endl; std::cout << " Attributes:" << std::endl; - std::cout << " Strength: " << (int)mData.mNpdt.mStrength << std::endl; - std::cout << " Intelligence: " << (int)mData.mNpdt.mIntelligence << std::endl; - std::cout << " Willpower: " << (int)mData.mNpdt.mWillpower << std::endl; - std::cout << " Agility: " << (int)mData.mNpdt.mAgility << std::endl; - std::cout << " Speed: " << (int)mData.mNpdt.mSpeed << std::endl; - std::cout << " Endurance: " << (int)mData.mNpdt.mEndurance << std::endl; - std::cout << " Personality: " << (int)mData.mNpdt.mPersonality << std::endl; - std::cout << " Luck: " << (int)mData.mNpdt.mLuck << std::endl; + for (size_t i = 0; i != mData.mNpdt.mAttributes.size(); i++) + std::cout << " " << attributeLabel(i) << ": " << int(mData.mNpdt.mAttributes[i]) << std::endl; std::cout << " Skills:" << std::endl; for (size_t i = 0; i != mData.mNpdt.mSkills.size(); i++) diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index e2e178f90a..d25568fd0a 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -693,22 +693,12 @@ void CSMTools::ReferenceableCheckStage::npcCheck( } else if (npc.mNpdt.mHealth != 0) { - if (npc.mNpdt.mStrength == 0) - messages.add(id, "Strength is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mIntelligence == 0) - messages.add(id, "Intelligence is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mWillpower == 0) - messages.add(id, "Willpower is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mAgility == 0) - messages.add(id, "Agility is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mSpeed == 0) - messages.add(id, "Speed is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mEndurance == 0) - messages.add(id, "Endurance is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mPersonality == 0) - messages.add(id, "Personality is equal to zero", "", CSMDoc::Message::Severity_Warning); - if (npc.mNpdt.mLuck == 0) - messages.add(id, "Luck is equal to zero", "", CSMDoc::Message::Severity_Warning); + for (size_t i = 0; i < npc.mNpdt.mAttributes.size(); ++i) + { + if (npc.mNpdt.mAttributes[i] == 0) + messages.add(id, ESM::Attribute::indexToRefId(i).getRefIdString() + " is equal to zero", {}, + CSMDoc::Message::Severity_Warning); + } } if (level <= 0) diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index 0ddfbbb051..c6179facb8 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -938,30 +938,9 @@ QVariant CSMWorld::NpcAttributesRefIdAdapter::getNestedData( if (subColIndex == 0) return subRowIndex; - else if (subColIndex == 1) - switch (subRowIndex) - { - case 0: - return static_cast(npcStruct.mStrength); - case 1: - return static_cast(npcStruct.mIntelligence); - case 2: - return static_cast(npcStruct.mWillpower); - case 3: - return static_cast(npcStruct.mAgility); - case 4: - return static_cast(npcStruct.mSpeed); - case 5: - return static_cast(npcStruct.mEndurance); - case 6: - return static_cast(npcStruct.mPersonality); - case 7: - return static_cast(npcStruct.mLuck); - default: - return QVariant(); // throw an exception here? - } - else - return QVariant(); // throw an exception here? + else if (subColIndex == 1 && subRowIndex >= 0 && subRowIndex < ESM::Attribute::Length) + return static_cast(npcStruct.mAttributes[subRowIndex]); + return QVariant(); // throw an exception here? } void CSMWorld::NpcAttributesRefIdAdapter::setNestedData( @@ -972,36 +951,8 @@ void CSMWorld::NpcAttributesRefIdAdapter::setNestedData( ESM::NPC npc = record.get(); ESM::NPC::NPDTstruct52& npcStruct = npc.mNpdt; - if (subColIndex == 1) - switch (subRowIndex) - { - case 0: - npcStruct.mStrength = static_cast(value.toInt()); - break; - case 1: - npcStruct.mIntelligence = static_cast(value.toInt()); - break; - case 2: - npcStruct.mWillpower = static_cast(value.toInt()); - break; - case 3: - npcStruct.mAgility = static_cast(value.toInt()); - break; - case 4: - npcStruct.mSpeed = static_cast(value.toInt()); - break; - case 5: - npcStruct.mEndurance = static_cast(value.toInt()); - break; - case 6: - npcStruct.mPersonality = static_cast(value.toInt()); - break; - case 7: - npcStruct.mLuck = static_cast(value.toInt()); - break; - default: - return; // throw an exception here? - } + if (subColIndex == 1 && subRowIndex >= 0 && subRowIndex < ESM::Attribute::Length) + npcStruct.mAttributes[subRowIndex] = static_cast(value.toInt()); else return; // throw an exception here? diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index c6276753de..7c6f16b06f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -313,14 +313,8 @@ namespace MWClass for (size_t i = 0; i < ref->mBase->mNpdt.mSkills.size(); ++i) data->mNpcStats.getSkill(ESM::Skill::indexToRefId(i)).setBase(ref->mBase->mNpdt.mSkills[i]); - data->mNpcStats.setAttribute(ESM::Attribute::Strength, ref->mBase->mNpdt.mStrength); - data->mNpcStats.setAttribute(ESM::Attribute::Intelligence, ref->mBase->mNpdt.mIntelligence); - data->mNpcStats.setAttribute(ESM::Attribute::Willpower, ref->mBase->mNpdt.mWillpower); - data->mNpcStats.setAttribute(ESM::Attribute::Agility, ref->mBase->mNpdt.mAgility); - data->mNpcStats.setAttribute(ESM::Attribute::Speed, ref->mBase->mNpdt.mSpeed); - data->mNpcStats.setAttribute(ESM::Attribute::Endurance, ref->mBase->mNpdt.mEndurance); - data->mNpcStats.setAttribute(ESM::Attribute::Personality, ref->mBase->mNpdt.mPersonality); - data->mNpcStats.setAttribute(ESM::Attribute::Luck, ref->mBase->mNpdt.mLuck); + for (size_t i = 0; i < ref->mBase->mNpdt.mAttributes.size(); ++i) + data->mNpcStats.setAttribute(ESM::Attribute::indexToRefId(i), ref->mBase->mNpdt.mAttributes[i]); data->mNpcStats.setHealth(ref->mBase->mNpdt.mHealth); data->mNpcStats.setMagicka(ref->mBase->mNpdt.mMana); diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 9a5b09ffe2..39b5286139 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -134,14 +134,9 @@ namespace MWMechanics for (size_t i = 0; i < player->mNpdt.mSkills.size(); ++i) npcStats.getSkill(ESM::Skill::indexToRefId(i)).setBase(player->mNpdt.mSkills[i]); - creatureStats.setAttribute(ESM::Attribute::Strength, player->mNpdt.mStrength); - creatureStats.setAttribute(ESM::Attribute::Intelligence, player->mNpdt.mIntelligence); - creatureStats.setAttribute(ESM::Attribute::Willpower, player->mNpdt.mWillpower); - creatureStats.setAttribute(ESM::Attribute::Agility, player->mNpdt.mAgility); - creatureStats.setAttribute(ESM::Attribute::Speed, player->mNpdt.mSpeed); - creatureStats.setAttribute(ESM::Attribute::Endurance, player->mNpdt.mEndurance); - creatureStats.setAttribute(ESM::Attribute::Personality, player->mNpdt.mPersonality); - creatureStats.setAttribute(ESM::Attribute::Luck, player->mNpdt.mLuck); + for (size_t i = 0; i < player->mNpdt.mAttributes.size(); ++i) + npcStats.setAttribute(ESM::Attribute::indexToRefId(i), player->mNpdt.mSkills[i]); + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); // race diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index d844f7d2bc..8a86780fe2 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -59,23 +59,31 @@ namespace ESM if (esm.getSubSize() == 52) { mNpdtType = NPC_DEFAULT; - esm.getExact(&mNpdt, 52); + esm.getT(mNpdt.mLevel); + esm.getT(mNpdt.mAttributes); + esm.getT(mNpdt.mSkills); + esm.getT(mNpdt.mUnknown1); + esm.getT(mNpdt.mHealth); + esm.getT(mNpdt.mMana); + esm.getT(mNpdt.mFatigue); + esm.getT(mNpdt.mDisposition); + esm.getT(mNpdt.mReputation); + esm.getT(mNpdt.mRank); + esm.getT(mNpdt.mUnknown2); + esm.getT(mNpdt.mGold); } else if (esm.getSubSize() == 12) { - // Reading into temporary NPDTstruct12 object - NPDTstruct12 npdt12; mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; - esm.getExact(&npdt12, 12); // Clearing the mNdpt struct to initialize all values blankNpdt(); - // Swiching to an internal representation - mNpdt.mLevel = npdt12.mLevel; - mNpdt.mDisposition = npdt12.mDisposition; - mNpdt.mReputation = npdt12.mReputation; - mNpdt.mRank = npdt12.mRank; - mNpdt.mGold = npdt12.mGold; + esm.getT(mNpdt.mLevel); + esm.getT(mNpdt.mDisposition); + esm.getT(mNpdt.mReputation); + esm.getT(mNpdt.mRank); + esm.skip(3); + esm.getT(mNpdt.mGold); } else esm.fail("NPC_NPDT must be 12 or 52 bytes long"); @@ -213,8 +221,7 @@ namespace ESM void NPC::blankNpdt() { mNpdt.mLevel = 0; - mNpdt.mStrength = mNpdt.mIntelligence = mNpdt.mWillpower = mNpdt.mAgility = mNpdt.mSpeed = mNpdt.mEndurance - = mNpdt.mPersonality = mNpdt.mLuck = 0; + mNpdt.mAttributes.fill(0); mNpdt.mSkills.fill(0); mNpdt.mReputation = 0; mNpdt.mHealth = mNpdt.mMana = mNpdt.mFatigue = 0; diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index af8c2a8574..c50dd3414d 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -6,6 +6,7 @@ #include #include "aipackage.hpp" +#include "components/esm/attr.hpp" #include "components/esm/defs.hpp" #include "components/esm/refid.hpp" #include "loadcont.hpp" @@ -80,7 +81,7 @@ namespace ESM struct NPDTstruct52 { int16_t mLevel; - unsigned char mStrength, mIntelligence, mWillpower, mAgility, mSpeed, mEndurance, mPersonality, mLuck; + std::array mAttributes; // mSkill can grow up to 200, it must be unsigned std::array mSkills; diff --git a/components/esm3/loadpgrd.cpp b/components/esm3/loadpgrd.cpp index 8d60d25524..4f0a62a9d4 100644 --- a/components/esm3/loadpgrd.cpp +++ b/components/esm3/loadpgrd.cpp @@ -70,7 +70,12 @@ namespace ESM for (uint16_t i = 0; i < mData.mPoints; ++i) { Point p; - esm.getExact(&p, sizeof(Point)); + esm.getT(p.mX); + esm.getT(p.mY); + esm.getT(p.mZ); + esm.getT(p.mAutogenerated); + esm.getT(p.mConnectionNum); + esm.getT(p.mUnknown); mPoints.push_back(p); edgeCount += p.mConnectionNum; } diff --git a/components/esm3/loadrace.cpp b/components/esm3/loadrace.cpp index 8c7b89d07d..0996a5ac48 100644 --- a/components/esm3/loadrace.cpp +++ b/components/esm3/loadrace.cpp @@ -12,6 +12,7 @@ namespace ESM int index = ESM::Attribute::refIdToIndex(attribute); if (index < 0) return 0; + index *= 2; if (!male) index++; return mAttributeValues[static_cast(index)]; @@ -22,6 +23,7 @@ namespace ESM int index = ESM::Attribute::refIdToIndex(attribute); if (index < 0) return; + index *= 2; if (!male) index++; mAttributeValues[static_cast(index)] = value; From 88731f864e46b8f7f7cd38802738465e2c6f835b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 15:21:12 +0100 Subject: [PATCH 078/231] Add imports --- apps/essimporter/converter.cpp | 1 + apps/essimporter/importacdt.hpp | 1 + apps/essimporter/importcellref.cpp | 1 + apps/essimporter/importcntc.cpp | 1 + apps/essimporter/importcrec.hpp | 1 + apps/essimporter/importdial.hpp | 3 +++ apps/essimporter/importgame.hpp | 2 ++ apps/essimporter/importinventory.hpp | 1 + apps/essimporter/importklst.hpp | 1 + apps/essimporter/importnpcc.hpp | 1 + apps/essimporter/importplayer.hpp | 1 + apps/essimporter/importproj.h | 1 + apps/essimporter/importscpt.hpp | 2 ++ apps/essimporter/importscri.hpp | 1 + apps/essimporter/importsplm.h | 1 + 15 files changed, 19 insertions(+) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index 00c24514ea..b44d376842 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -1,6 +1,7 @@ #include "converter.hpp" #include +#include #include #include diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp index 54910ac82c..65519c6a6c 100644 --- a/apps/essimporter/importacdt.hpp +++ b/apps/essimporter/importacdt.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESSIMPORT_ACDT_H #define OPENMW_ESSIMPORT_ACDT_H +#include #include #include "importscri.hpp" diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index d9639eb0dd..756770d0b5 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -1,6 +1,7 @@ #include "importcellref.hpp" #include +#include namespace ESSImport { diff --git a/apps/essimporter/importcntc.cpp b/apps/essimporter/importcntc.cpp index 41f4e50101..34c99babef 100644 --- a/apps/essimporter/importcntc.cpp +++ b/apps/essimporter/importcntc.cpp @@ -1,6 +1,7 @@ #include "importcntc.hpp" #include +#include namespace ESSImport { diff --git a/apps/essimporter/importcrec.hpp b/apps/essimporter/importcrec.hpp index 87a2d311c8..5217f4edc4 100644 --- a/apps/essimporter/importcrec.hpp +++ b/apps/essimporter/importcrec.hpp @@ -3,6 +3,7 @@ #include "importinventory.hpp" #include +#include namespace ESM { diff --git a/apps/essimporter/importdial.hpp b/apps/essimporter/importdial.hpp index 2a09af9f2a..b8b6fd536a 100644 --- a/apps/essimporter/importdial.hpp +++ b/apps/essimporter/importdial.hpp @@ -1,5 +1,8 @@ #ifndef OPENMW_ESSIMPORT_IMPORTDIAL_H #define OPENMW_ESSIMPORT_IMPORTDIAL_H + +#include + namespace ESM { class ESMReader; diff --git a/apps/essimporter/importgame.hpp b/apps/essimporter/importgame.hpp index e880166b45..276060ae4c 100644 --- a/apps/essimporter/importgame.hpp +++ b/apps/essimporter/importgame.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_ESSIMPORT_GAME_H #define OPENMW_ESSIMPORT_GAME_H +#include + namespace ESM { class ESMReader; diff --git a/apps/essimporter/importinventory.hpp b/apps/essimporter/importinventory.hpp index 9e0dcbb30a..7261e64f68 100644 --- a/apps/essimporter/importinventory.hpp +++ b/apps/essimporter/importinventory.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESSIMPORT_IMPORTINVENTORY_H #define OPENMW_ESSIMPORT_IMPORTINVENTORY_H +#include #include #include diff --git a/apps/essimporter/importklst.hpp b/apps/essimporter/importklst.hpp index 8a098ac501..9cdb2d701b 100644 --- a/apps/essimporter/importklst.hpp +++ b/apps/essimporter/importklst.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESSIMPORT_KLST_H #define OPENMW_ESSIMPORT_KLST_H +#include #include #include diff --git a/apps/essimporter/importnpcc.hpp b/apps/essimporter/importnpcc.hpp index f3532f2103..47925226e4 100644 --- a/apps/essimporter/importnpcc.hpp +++ b/apps/essimporter/importnpcc.hpp @@ -2,6 +2,7 @@ #define OPENMW_ESSIMPORT_NPCC_H #include +#include #include "importinventory.hpp" diff --git a/apps/essimporter/importplayer.hpp b/apps/essimporter/importplayer.hpp index 3602abd9c3..89957bf4b4 100644 --- a/apps/essimporter/importplayer.hpp +++ b/apps/essimporter/importplayer.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_ESSIMPORT_PLAYER_H #define OPENMW_ESSIMPORT_PLAYER_H +#include #include #include diff --git a/apps/essimporter/importproj.h b/apps/essimporter/importproj.h index 01592af3e3..a2e03b5ba3 100644 --- a/apps/essimporter/importproj.h +++ b/apps/essimporter/importproj.h @@ -3,6 +3,7 @@ #include #include +#include #include namespace ESM diff --git a/apps/essimporter/importscpt.hpp b/apps/essimporter/importscpt.hpp index a75a3e38da..af383b674c 100644 --- a/apps/essimporter/importscpt.hpp +++ b/apps/essimporter/importscpt.hpp @@ -3,6 +3,8 @@ #include "importscri.hpp" +#include + #include #include diff --git a/apps/essimporter/importscri.hpp b/apps/essimporter/importscri.hpp index 73d8942f81..0c83a4d3be 100644 --- a/apps/essimporter/importscri.hpp +++ b/apps/essimporter/importscri.hpp @@ -3,6 +3,7 @@ #include +#include #include namespace ESM diff --git a/apps/essimporter/importsplm.h b/apps/essimporter/importsplm.h index 39405db6d3..762e32d9da 100644 --- a/apps/essimporter/importsplm.h +++ b/apps/essimporter/importsplm.h @@ -2,6 +2,7 @@ #define OPENMW_ESSIMPORT_IMPORTSPLM_H #include +#include #include namespace ESM From 9f38ee82f45707ec5d9f937f6359ca8495797ab3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 17 Dec 2023 21:30:04 +0100 Subject: [PATCH 079/231] Fix misaligned address --- components/esm3/loadnpc.cpp | 34 +++++++++++++++++++++++----------- components/esm3/loadnpc.hpp | 15 --------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 8a86780fe2..4a30649372 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -154,20 +154,32 @@ namespace ESM if (mNpdtType == NPC_DEFAULT) { - esm.writeHNT("NPDT", mNpdt, 52); + esm.startSubRecord("NPDT"); + esm.writeT(mNpdt.mLevel); + esm.writeT(mNpdt.mAttributes); + esm.writeT(mNpdt.mSkills); + esm.writeT(mNpdt.mUnknown1); + esm.writeT(mNpdt.mHealth); + esm.writeT(mNpdt.mMana); + esm.writeT(mNpdt.mFatigue); + esm.writeT(mNpdt.mDisposition); + esm.writeT(mNpdt.mReputation); + esm.writeT(mNpdt.mRank); + esm.writeT(mNpdt.mUnknown2); + esm.writeT(mNpdt.mGold); + esm.endRecord("NPDT"); } else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) { - NPDTstruct12 npdt12; - npdt12.mLevel = mNpdt.mLevel; - npdt12.mDisposition = mNpdt.mDisposition; - npdt12.mReputation = mNpdt.mReputation; - npdt12.mRank = mNpdt.mRank; - npdt12.mUnknown1 = 0; - npdt12.mUnknown2 = 0; - npdt12.mUnknown3 = 0; - npdt12.mGold = mNpdt.mGold; - esm.writeHNT("NPDT", npdt12, 12); + esm.startSubRecord("NPDT"); + esm.writeT(mNpdt.mLevel); + esm.writeT(mNpdt.mDisposition); + esm.writeT(mNpdt.mReputation); + esm.writeT(mNpdt.mRank); + constexpr char padding[] = { 0, 0, 0 }; + esm.writeT(padding); + esm.writeT(mNpdt.mGold); + esm.endRecord("NPDT"); } esm.writeHNT("FLAG", ((mBloodType << 10) + mFlags)); diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index c50dd3414d..76930365c8 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -75,9 +75,6 @@ namespace ESM NPC_DEFAULT = 52 }; -#pragma pack(push) -#pragma pack(1) - struct NPDTstruct52 { int16_t mLevel; @@ -93,18 +90,6 @@ namespace ESM int32_t mGold; }; // 52 bytes - // Structure for autocalculated characters. - // This is only used for load and save operations. - struct NPDTstruct12 - { - int16_t mLevel; - // see above - unsigned char mDisposition, mReputation, mRank; - char mUnknown1, mUnknown2, mUnknown3; - int32_t mGold; - }; // 12 bytes -#pragma pack(pop) - unsigned char mNpdtType; // Worth noting when saving the struct: // Although we might read a NPDTstruct12 in, we use NPDTstruct52 internally From 77cf9284b784b5a0bbcfbd1ef1c7b7fbeaa1e086 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 18 Dec 2023 21:52:17 +0100 Subject: [PATCH 080/231] Allow ModPCCrimeLevel to clear crimes and cap bounties --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/mechanicsmanagerimp.cpp | 6 ++++-- apps/openmw/mwscript/statsextensions.cpp | 8 +++++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6b107f41..5e455532db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Bug #7380: NiZBufferProperty issue Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies + Bug #7416: Modpccrimelevel is different from vanilla Bug #7428: AutoCalc flag is not used to calculate enchantment costs Bug #7450: Evading obstacles does not work for actors missing certain animations Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e0baac0764..12282a515d 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1152,7 +1152,7 @@ namespace MWMechanics if (npcStats.getCrimeId() != -1) { // if you've paid for your crimes and I haven't noticed - if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId()) + if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId() || playerStats.getBounty() <= 0) { // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 0bb76add16..d35e812d27 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1358,7 +1358,8 @@ namespace MWMechanics if (reported) { - player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() + arg); + player.getClass().getNpcStats(player).setBounty( + std::max(0, player.getClass().getNpcStats(player).getBounty() + arg)); // If committing a crime against a faction member, expell from the faction if (!victim.isEmpty() && victim.getClass().isNpc()) @@ -1923,7 +1924,8 @@ namespace MWMechanics if (reported) { - npcStats.setBounty(npcStats.getBounty() + gmst.find("iWereWolfBounty")->mValue.getInteger()); + npcStats.setBounty( + std::max(0, npcStats.getBounty() + gmst.find("iWereWolfBounty")->mValue.getInteger())); } } } diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 745286e109..d617a02b9a 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -445,10 +445,12 @@ namespace MWScript { MWBase::World* world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - - player.getClass().getNpcStats(player).setBounty( - static_cast(runtime[0].mFloat) + player.getClass().getNpcStats(player).getBounty()); + int bounty = std::max( + 0, static_cast(runtime[0].mFloat) + player.getClass().getNpcStats(player).getBounty()); + player.getClass().getNpcStats(player).setBounty(bounty); runtime.pop(); + if (bounty == 0) + MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); } }; From 94b129cc62cf3b5a609552706a36ab6b27f1a464 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 18 Dec 2023 22:18:26 +0100 Subject: [PATCH 081/231] Stop combat when stacking a new AI package --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aisequence.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6b107f41..2b708e4969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking + Bug #7661: Player followers should stop attacking newly recruited actors Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound Bug #7679: Scene luminance value flashes when toggling shaders diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index af35be3763..1f3f4e2ea1 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -6,6 +6,8 @@ #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "actorutil.hpp" #include "aiactivate.hpp" @@ -365,7 +367,7 @@ namespace MWMechanics // Stop combat when a non-combat AI package is added if (isActualAiPackage(package.getTypeId())) - stopCombat(); + MWBase::Environment::get().getMechanicsManager()->stopCombat(actor); // We should return a wandering actor back after combat, casting or pursuit. // The same thing for actors without AI packages. From 5a6dbf87149a421638cce92577e3306b28633f3d Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 18 Dec 2023 22:28:38 +0100 Subject: [PATCH 082/231] Comments --- apps/openmw/mwphysics/mtphysics.cpp | 42 +++++++++++------------------ apps/openmw/mwphysics/ptrholder.hpp | 14 +++++----- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 9a4ec2cba2..86760a67c6 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -208,23 +208,16 @@ namespace { osg::Vec3f movement = osg::Vec3f(); auto it = actor.movement().begin(); - while (it != actor.movement().end()) - { - if (it->jump) - { - // Adjusting inertia is instant and should not be performed over time like other movement is. - it++; - continue; - } - - float start = std::max(it->simulationTimeStart, startTime); - float stop = std::min(it->simulationTimeStop, endTime); - movement += it->velocity * (stop - start); - if (std::abs(stop - it->simulationTimeStop) < 0.0001f) - it = actor.movement().erase(it); - else - it++; - } + std::erase_if(actor.movement(), [&](MWPhysics::Movement& v) { + if (v.mJump) + return false; + float start = std::max(v.mSimulationTimeStart, startTime); + float stop = std::min(v.mSimulationTimeStop, endTime); + movement += v.mVelocity * (stop - start); + if (std::abs(stop - v.mSimulationTimeStop) < 0.0001f) + return true; + return false; + }); return movement; } @@ -232,17 +225,14 @@ namespace std::optional takeInertia(MWPhysics::PtrHolder& actor, float startTime) const { std::optional inertia = std::nullopt; - auto it = actor.movement().begin(); - while (it != actor.movement().end()) - { - if (it->jump && it->simulationTimeStart >= startTime) + std::erase_if(actor.movement(), [&](MWPhysics::Movement& v) { + if (v.mJump && v.mSimulationTimeStart >= startTime) { - inertia = it->velocity; - it = actor.movement().erase(it); + inertia = v.mVelocity; + return true; } - else - it++; - } + return false; + }); return inertia; } diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index d7a6f887bb..ecc626b44c 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H -#include +#include #include #include #include @@ -16,10 +16,10 @@ namespace MWPhysics { struct Movement { - osg::Vec3f velocity = osg::Vec3f(); - float simulationTimeStart = 0.f; // The time at which this movement begun - float simulationTimeStop = 0.f; // The time at which this movement finished - bool jump = false; + osg::Vec3f mVelocity = osg::Vec3f(); + float mSimulationTimeStart = 0.f; // The time at which this movement begun + float mSimulationTimeStop = 0.f; // The time at which this movement finished + bool mJump = false; }; class PtrHolder @@ -47,7 +47,7 @@ namespace MWPhysics mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop, jump }); } - std::deque& movement() { return mMovement; } + std::list& movement() { return mMovement; } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } @@ -66,7 +66,7 @@ namespace MWPhysics protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; - std::deque mMovement; + std::list mMovement; osg::Vec3f mSimulationPosition; osg::Vec3d mPosition; osg::Vec3d mPreviousPosition; From 00b1cd8c08caeacb56fbe8d053175103a649ebfa Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Mon, 18 Dec 2023 22:50:17 +0100 Subject: [PATCH 083/231] Replace movement() with eraseMovementIf() --- apps/openmw/mwphysics/mtphysics.cpp | 5 ++--- apps/openmw/mwphysics/ptrholder.hpp | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 86760a67c6..238d00deac 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -207,8 +207,7 @@ namespace osg::Vec3f takeMovement(MWPhysics::PtrHolder& actor, float startTime, float endTime) const { osg::Vec3f movement = osg::Vec3f(); - auto it = actor.movement().begin(); - std::erase_if(actor.movement(), [&](MWPhysics::Movement& v) { + actor.eraseMovementIf([&](MWPhysics::Movement& v) { if (v.mJump) return false; float start = std::max(v.mSimulationTimeStart, startTime); @@ -225,7 +224,7 @@ namespace std::optional takeInertia(MWPhysics::PtrHolder& actor, float startTime) const { std::optional inertia = std::nullopt; - std::erase_if(actor.movement(), [&](MWPhysics::Movement& v) { + actor.eraseMovementIf([&](MWPhysics::Movement& v) { if (v.mJump && v.mSimulationTimeStart >= startTime) { inertia = v.mVelocity; diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index ecc626b44c..16c3db0691 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -47,7 +47,7 @@ namespace MWPhysics mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop, jump }); } - std::list& movement() { return mMovement; } + void eraseMovementIf(const auto& predicate) { std::erase_if(mMovement, predicate); } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } From 8d06a9950739367de8cd5549473d16d745c3c78d Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 19 Dec 2023 10:20:31 +0400 Subject: [PATCH 084/231] Register language selector properly --- apps/wizard/installationpage.cpp | 2 +- apps/wizard/languageselectionpage.cpp | 2 +- apps/wizard/mainwizard.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 2dd796ab3f..60e9f3ccf9 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -124,7 +124,7 @@ void Wizard::InstallationPage::startInstallation() mUnshield->setPath(path); // Set the right codec to use for Morrowind.ini - QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); + QString language(field(QLatin1String("installation.language")).toString()); if (language == QLatin1String("Polish")) { diff --git a/apps/wizard/languageselectionpage.cpp b/apps/wizard/languageselectionpage.cpp index 38050b1cab..7dcf642dd6 100644 --- a/apps/wizard/languageselectionpage.cpp +++ b/apps/wizard/languageselectionpage.cpp @@ -9,7 +9,7 @@ Wizard::LanguageSelectionPage::LanguageSelectionPage(QWidget* parent) setupUi(this); - registerField(QLatin1String("installation.language"), languageComboBox); + registerField(QLatin1String("installation.language"), languageComboBox, "currentData", "currentDataChanged"); } void Wizard::LanguageSelectionPage::initializePage() diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 5fd316d17a..2f1f373cfd 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -270,7 +270,7 @@ void Wizard::MainWizard::runSettingsImporter() arguments.append(QLatin1String("--encoding")); // Set encoding - QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); + QString language(field(QLatin1String("installation.language")).toString()); if (language == QLatin1String("Polish")) { arguments.append(QLatin1String("win1250")); @@ -391,7 +391,7 @@ void Wizard::MainWizard::reject() void Wizard::MainWizard::writeSettings() { // Write the encoding and language settings - QString language(field(QLatin1String("installation.language")).value()->currentData().toString()); + QString language(field(QLatin1String("installation.language")).toString()); mLauncherSettings.setLanguage(language); if (language == QLatin1String("Polish")) From 2e041073fccae6144d7d364d8ba9b30459575aec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 19 Dec 2023 15:21:08 +0400 Subject: [PATCH 085/231] Move *.ui files for different applications to different folders --- apps/launcher/CMakeLists.txt | 14 +++++++------- {files => apps/launcher}/ui/datafilespage.ui | 0 {files => apps/launcher}/ui/directorypicker.ui | 0 {files => apps/launcher}/ui/graphicspage.ui | 0 {files => apps/launcher}/ui/importpage.ui | 0 {files => apps/launcher}/ui/mainwindow.ui | 0 {files => apps/launcher}/ui/settingspage.ui | 0 apps/opencs/CMakeLists.txt | 4 ++-- {files => apps/opencs}/ui/filedialog.ui | 0 apps/wizard/CMakeLists.txt | 18 +++++++++--------- .../wizard/ui}/componentselectionpage.ui | 0 .../wizard/ui}/conclusionpage.ui | 0 .../wizard/ui}/existinginstallationpage.ui | 0 .../ui/wizard => apps/wizard/ui}/importpage.ui | 0 .../wizard/ui}/installationpage.ui | 0 .../wizard/ui}/installationtargetpage.ui | 0 .../ui/wizard => apps/wizard/ui}/intropage.ui | 0 .../wizard/ui}/languageselectionpage.ui | 0 .../wizard/ui}/methodselectionpage.ui | 0 components/CMakeLists.txt | 2 +- .../contentselector}/contentselector.ui | 0 21 files changed, 19 insertions(+), 19 deletions(-) rename {files => apps/launcher}/ui/datafilespage.ui (100%) rename {files => apps/launcher}/ui/directorypicker.ui (100%) rename {files => apps/launcher}/ui/graphicspage.ui (100%) rename {files => apps/launcher}/ui/importpage.ui (100%) rename {files => apps/launcher}/ui/mainwindow.ui (100%) rename {files => apps/launcher}/ui/settingspage.ui (100%) rename {files => apps/opencs}/ui/filedialog.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/componentselectionpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/conclusionpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/existinginstallationpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/importpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/installationpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/installationtargetpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/intropage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/languageselectionpage.ui (100%) rename {files/ui/wizard => apps/wizard/ui}/methodselectionpage.ui (100%) rename {files/ui => components/contentselector}/contentselector.ui (100%) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index daae65dc66..f81870052d 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -35,13 +35,13 @@ set(LAUNCHER_HEADER # Headers that must be pre-processed set(LAUNCHER_UI - ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui - ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui - ${CMAKE_SOURCE_DIR}/files/ui/mainwindow.ui - ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui - ${CMAKE_SOURCE_DIR}/files/ui/importpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/settingspage.ui - ${CMAKE_SOURCE_DIR}/files/ui/directorypicker.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/datafilespage.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/graphicspage.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/mainwindow.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/importpage.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/settingspage.ui + ${CMAKE_SOURCE_DIR}/apps/launcher/ui/directorypicker.ui + ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) diff --git a/files/ui/datafilespage.ui b/apps/launcher/ui/datafilespage.ui similarity index 100% rename from files/ui/datafilespage.ui rename to apps/launcher/ui/datafilespage.ui diff --git a/files/ui/directorypicker.ui b/apps/launcher/ui/directorypicker.ui similarity index 100% rename from files/ui/directorypicker.ui rename to apps/launcher/ui/directorypicker.ui diff --git a/files/ui/graphicspage.ui b/apps/launcher/ui/graphicspage.ui similarity index 100% rename from files/ui/graphicspage.ui rename to apps/launcher/ui/graphicspage.ui diff --git a/files/ui/importpage.ui b/apps/launcher/ui/importpage.ui similarity index 100% rename from files/ui/importpage.ui rename to apps/launcher/ui/importpage.ui diff --git a/files/ui/mainwindow.ui b/apps/launcher/ui/mainwindow.ui similarity index 100% rename from files/ui/mainwindow.ui rename to apps/launcher/ui/mainwindow.ui diff --git a/files/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui similarity index 100% rename from files/ui/settingspage.ui rename to apps/launcher/ui/settingspage.ui diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index b040980529..68d5502890 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -139,8 +139,8 @@ set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc ) set (OPENCS_UI - ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui - ${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui + ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui + ${CMAKE_SOURCE_DIR}/apps/opencs/ui/filedialog.ui ) source_group (openmw-cs FILES main.cpp ${OPENCS_SRC} ${OPENCS_HDR}) diff --git a/files/ui/filedialog.ui b/apps/opencs/ui/filedialog.ui similarity index 100% rename from files/ui/filedialog.ui rename to apps/opencs/ui/filedialog.ui diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 588b100ef2..943252151f 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -34,20 +34,20 @@ set(WIZARD_HEADER ) set(WIZARD_UI - ${CMAKE_SOURCE_DIR}/files/ui/wizard/componentselectionpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/conclusionpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/existinginstallationpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/importpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationtargetpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/intropage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/languageselectionpage.ui - ${CMAKE_SOURCE_DIR}/files/ui/wizard/methodselectionpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/componentselectionpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/conclusionpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/existinginstallationpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/importpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/installationtargetpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/intropage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/languageselectionpage.ui + ${CMAKE_SOURCE_DIR}/apps/wizard/ui/methodselectionpage.ui ) if (OPENMW_USE_UNSHIELD) set (WIZARD ${WIZARD} installationpage.cpp unshield/unshieldworker.cpp) set (WIZARD_HEADER ${WIZARD_HEADER} installationpage.hpp unshield/unshieldworker.hpp) - set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/files/ui/wizard/installationpage.ui) + set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/apps/wizard/ui/installationpage.ui) add_definitions(-DOPENMW_USE_UNSHIELD) endif (OPENMW_USE_UNSHIELD) diff --git a/files/ui/wizard/componentselectionpage.ui b/apps/wizard/ui/componentselectionpage.ui similarity index 100% rename from files/ui/wizard/componentselectionpage.ui rename to apps/wizard/ui/componentselectionpage.ui diff --git a/files/ui/wizard/conclusionpage.ui b/apps/wizard/ui/conclusionpage.ui similarity index 100% rename from files/ui/wizard/conclusionpage.ui rename to apps/wizard/ui/conclusionpage.ui diff --git a/files/ui/wizard/existinginstallationpage.ui b/apps/wizard/ui/existinginstallationpage.ui similarity index 100% rename from files/ui/wizard/existinginstallationpage.ui rename to apps/wizard/ui/existinginstallationpage.ui diff --git a/files/ui/wizard/importpage.ui b/apps/wizard/ui/importpage.ui similarity index 100% rename from files/ui/wizard/importpage.ui rename to apps/wizard/ui/importpage.ui diff --git a/files/ui/wizard/installationpage.ui b/apps/wizard/ui/installationpage.ui similarity index 100% rename from files/ui/wizard/installationpage.ui rename to apps/wizard/ui/installationpage.ui diff --git a/files/ui/wizard/installationtargetpage.ui b/apps/wizard/ui/installationtargetpage.ui similarity index 100% rename from files/ui/wizard/installationtargetpage.ui rename to apps/wizard/ui/installationtargetpage.ui diff --git a/files/ui/wizard/intropage.ui b/apps/wizard/ui/intropage.ui similarity index 100% rename from files/ui/wizard/intropage.ui rename to apps/wizard/ui/intropage.ui diff --git a/files/ui/wizard/languageselectionpage.ui b/apps/wizard/ui/languageselectionpage.ui similarity index 100% rename from files/ui/wizard/languageselectionpage.ui rename to apps/wizard/ui/languageselectionpage.ui diff --git a/files/ui/wizard/methodselectionpage.ui b/apps/wizard/ui/methodselectionpage.ui similarity index 100% rename from files/ui/wizard/methodselectionpage.ui rename to apps/wizard/ui/methodselectionpage.ui diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f61a5bd0b2..e762bd4905 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -490,7 +490,7 @@ else () ) endif() -set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui +set (ESM_UI ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ) if (USE_QT) diff --git a/files/ui/contentselector.ui b/components/contentselector/contentselector.ui similarity index 100% rename from files/ui/contentselector.ui rename to components/contentselector/contentselector.ui From e6690bbcc7f6dedd9f7d1fa524c1734e53076efa Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 19 Dec 2023 19:58:52 +0400 Subject: [PATCH 086/231] Use CMAKE_CURRENT_SOURCE_DIR instead of CMAKE_SOURCE_DIR --- apps/launcher/CMakeLists.txt | 12 ++++++------ apps/opencs/CMakeLists.txt | 2 +- apps/wizard/CMakeLists.txt | 18 +++++++++--------- components/CMakeLists.txt | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index f81870052d..8d2208c9df 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -35,12 +35,12 @@ set(LAUNCHER_HEADER # Headers that must be pre-processed set(LAUNCHER_UI - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/datafilespage.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/graphicspage.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/mainwindow.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/importpage.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/settingspage.ui - ${CMAKE_SOURCE_DIR}/apps/launcher/ui/directorypicker.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/datafilespage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/graphicspage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/mainwindow.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/importpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/settingspage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/directorypicker.ui ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 68d5502890..cea2b66331 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -140,7 +140,7 @@ set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc set (OPENCS_UI ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui - ${CMAKE_SOURCE_DIR}/apps/opencs/ui/filedialog.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/filedialog.ui ) source_group (openmw-cs FILES main.cpp ${OPENCS_SRC} ${OPENCS_HDR}) diff --git a/apps/wizard/CMakeLists.txt b/apps/wizard/CMakeLists.txt index 943252151f..8c459f4f9c 100644 --- a/apps/wizard/CMakeLists.txt +++ b/apps/wizard/CMakeLists.txt @@ -34,20 +34,20 @@ set(WIZARD_HEADER ) set(WIZARD_UI - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/componentselectionpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/conclusionpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/existinginstallationpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/importpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/installationtargetpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/intropage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/languageselectionpage.ui - ${CMAKE_SOURCE_DIR}/apps/wizard/ui/methodselectionpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/componentselectionpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/conclusionpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/existinginstallationpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/importpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/installationtargetpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/intropage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/languageselectionpage.ui + ${CMAKE_CURRENT_SOURCE_DIR}/ui/methodselectionpage.ui ) if (OPENMW_USE_UNSHIELD) set (WIZARD ${WIZARD} installationpage.cpp unshield/unshieldworker.cpp) set (WIZARD_HEADER ${WIZARD_HEADER} installationpage.hpp unshield/unshieldworker.hpp) - set (WIZARD_UI ${WIZARD_UI} ${CMAKE_SOURCE_DIR}/apps/wizard/ui/installationpage.ui) + set (WIZARD_UI ${WIZARD_UI} ${CMAKE_CURRENT_SOURCE_DIR}/ui/installationpage.ui) add_definitions(-DOPENMW_USE_UNSHIELD) endif (OPENMW_USE_UNSHIELD) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e762bd4905..a335f81de0 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -490,7 +490,7 @@ else () ) endif() -set (ESM_UI ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui +set (ESM_UI ${CMAKE_CURRENT_SOURCE_DIR}/contentselector/contentselector.ui ) if (USE_QT) From 8a1ca870ebf02ec9c6ffa57ee20ac3df58d9bcd9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 19 Dec 2023 21:23:10 +0100 Subject: [PATCH 087/231] Stop infighting when gaining new allies --- apps/openmw/mwmechanics/aisequence.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 1f3f4e2ea1..5d6f25ecb8 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -19,6 +19,7 @@ #include "aipursue.hpp" #include "aitravel.hpp" #include "aiwander.hpp" +#include "creaturestats.hpp" namespace MWMechanics { @@ -367,7 +368,20 @@ namespace MWMechanics // Stop combat when a non-combat AI package is added if (isActualAiPackage(package.getTypeId())) - MWBase::Environment::get().getMechanicsManager()->stopCombat(actor); + { + if (package.getTypeId() == MWMechanics::AiPackageTypeId::Follow + || package.getTypeId() == MWMechanics::AiPackageTypeId::Escort) + { + const auto& mechanicsManager = MWBase::Environment::get().getMechanicsManager(); + std::vector newAllies = mechanicsManager->getActorsSidingWith(package.getTarget()); + std::vector allies = mechanicsManager->getActorsSidingWith(actor); + for (const auto& ally : allies) + ally.getClass().getCreatureStats(ally).getAiSequence().stopCombat(newAllies); + for (const auto& ally : newAllies) + ally.getClass().getCreatureStats(ally).getAiSequence().stopCombat(allies); + } + stopCombat(); + } // We should return a wandering actor back after combat, casting or pursuit. // The same thing for actors without AI packages. From 1223d12b29bbe7d8783473d5e09cd6aaf477fe68 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 20 Dec 2023 11:56:12 +0100 Subject: [PATCH 088/231] Make ingredient order affect effect order --- CHANGELOG.md | 1 + apps/openmw/mwgui/alchemywindow.cpp | 2 +- apps/openmw/mwmechanics/alchemy.cpp | 95 ++++++++++++++---------- apps/openmw/mwmechanics/alchemy.hpp | 3 +- apps/openmw/mwmechanics/magiceffects.cpp | 5 ++ apps/openmw/mwmechanics/magiceffects.hpp | 1 + 6 files changed, 66 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b6b107f41..681c721137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -103,6 +103,7 @@ Bug #7660: Some inconsistencies regarding Invisibility breaking Bug #7665: Alchemy menu is missing the ability to deselect and choose different qualities of an apparatus Bug #7675: Successful lock spell doesn't produce a sound + Bug #7676: Incorrect magic effect order in alchemy Bug #7679: Scene luminance value flashes when toggling shaders Bug #7685: Corky sometimes doesn't follow Llovyn Andus Bug #7712: Casting doesn't support spells and enchantments with no effects diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 333722a149..de3e0c19e0 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -433,7 +433,7 @@ namespace MWGui mItemView->update(); - std::set effectIds = mAlchemy->listEffects(); + std::vector effectIds = mAlchemy->listEffects(); Widgets::SpellEffectList list; unsigned int effectIndex = 0; for (const MWMechanics::EffectKey& effectKey : effectIds) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index 5f8ffc1750..e9662f395f 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -24,45 +24,64 @@ #include "creaturestats.hpp" #include "magiceffects.hpp" +namespace +{ + constexpr size_t sNumEffects = 4; + + std::optional toKey(const ESM::Ingredient& ingredient, size_t i) + { + if (ingredient.mData.mEffectID[i] < 0) + return {}; + ESM::RefId arg = ESM::Skill::indexToRefId(ingredient.mData.mSkills[i]); + if (arg.empty()) + arg = ESM::Attribute::indexToRefId(ingredient.mData.mAttributes[i]); + return MWMechanics::EffectKey(ingredient.mData.mEffectID[i], arg); + } + + bool containsEffect(const ESM::Ingredient& ingredient, const MWMechanics::EffectKey& effect) + { + for (size_t j = 0; j < sNumEffects; ++j) + { + if (toKey(ingredient, j) == effect) + return true; + } + return false; + } +} + MWMechanics::Alchemy::Alchemy() : mValue(0) - , mPotionName("") { } -std::set MWMechanics::Alchemy::listEffects() const +std::vector MWMechanics::Alchemy::listEffects() const { - std::map effects; - - for (TIngredientsIterator iter(mIngredients.begin()); iter != mIngredients.end(); ++iter) + // We care about the order of these effects as each effect can affect the next when applied. + // The player can affect effect order by placing ingredients into different slots + std::vector effects; + for (size_t slotI = 0; slotI < mIngredients.size() - 1; ++slotI) { - if (!iter->isEmpty()) + if (mIngredients[slotI].isEmpty()) + continue; + const ESM::Ingredient* ingredient = mIngredients[slotI].get()->mBase; + for (size_t slotJ = slotI + 1; slotJ < mIngredients.size(); ++slotJ) { - const MWWorld::LiveCellRef* ingredient = iter->get(); - - std::set seenEffects; - - for (int i = 0; i < 4; ++i) - if (ingredient->mBase->mData.mEffectID[i] != -1) + if (mIngredients[slotJ].isEmpty()) + continue; + const ESM::Ingredient* ingredient2 = mIngredients[slotJ].get()->mBase; + for (size_t i = 0; i < sNumEffects; ++i) + { + if (const auto key = toKey(*ingredient, i)) { - ESM::RefId arg = ESM::Skill::indexToRefId(ingredient->mBase->mData.mSkills[i]); - if (arg.empty()) - arg = ESM::Attribute::indexToRefId(ingredient->mBase->mData.mAttributes[i]); - EffectKey key(ingredient->mBase->mData.mEffectID[i], arg); - - if (seenEffects.insert(key).second) - ++effects[key]; + if (std::ranges::find(effects, *key) != effects.end()) + continue; + if (containsEffect(*ingredient2, *key)) + effects.push_back(*key); } + } } } - - std::set effects2; - - for (std::map::const_iterator iter(effects.begin()); iter != effects.end(); ++iter) - if (iter->second > 1) - effects2.insert(iter->first); - - return effects2; + return effects; } void MWMechanics::Alchemy::applyTools(int flags, float& value) const @@ -133,7 +152,7 @@ void MWMechanics::Alchemy::updateEffects() return; // find effects - std::set effects(listEffects()); + std::vector effects = listEffects(); // general alchemy factor float x = getAlchemyFactor(); @@ -150,14 +169,14 @@ void MWMechanics::Alchemy::updateEffects() x * MWBase::Environment::get().getESMStore()->get().find("iAlchemyMod")->mValue.getFloat()); // build quantified effect list - for (std::set::const_iterator iter(effects.begin()); iter != effects.end(); ++iter) + for (const auto& effectKey : effects) { const ESM::MagicEffect* magicEffect - = MWBase::Environment::get().getESMStore()->get().find(iter->mId); + = MWBase::Environment::get().getESMStore()->get().find(effectKey.mId); if (magicEffect->mData.mBaseCost <= 0) { - const std::string os = "invalid base cost for magic effect " + std::to_string(iter->mId); + const std::string os = "invalid base cost for magic effect " + std::to_string(effectKey.mId); throw std::runtime_error(os); } @@ -198,15 +217,15 @@ void MWMechanics::Alchemy::updateEffects() if (magnitude > 0 && duration > 0) { ESM::ENAMstruct effect; - effect.mEffectID = iter->mId; + effect.mEffectID = effectKey.mId; effect.mAttribute = -1; effect.mSkill = -1; if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetSkill) - effect.mSkill = ESM::Skill::refIdToIndex(iter->mArg); + effect.mSkill = ESM::Skill::refIdToIndex(effectKey.mArg); else if (magicEffect->mData.mFlags & ESM::MagicEffect::TargetAttribute) - effect.mAttribute = ESM::Attribute::refIdToIndex(iter->mArg); + effect.mAttribute = ESM::Attribute::refIdToIndex(effectKey.mArg); effect.mRange = 0; effect.mArea = 0; @@ -241,7 +260,7 @@ const ESM::Potion* MWMechanics::Alchemy::getRecord(const ESM::Potion& toFind) co bool mismatch = false; - for (int i = 0; i < static_cast(iter->mEffects.mList.size()); ++i) + for (size_t i = 0; i < iter->mEffects.mList.size(); ++i) { const ESM::ENAMstruct& first = iter->mEffects.mList[i]; const ESM::ENAMstruct& second = mEffects[i]; @@ -578,7 +597,7 @@ MWMechanics::Alchemy::Result MWMechanics::Alchemy::createSingle() std::string MWMechanics::Alchemy::suggestPotionName() { - std::set effects = listEffects(); + std::vector effects = listEffects(); if (effects.empty()) return {}; @@ -595,11 +614,11 @@ std::vector MWMechanics::Alchemy::effectsDescription(const MWWorld: const static auto fWortChanceValue = store->get().find("fWortChanceValue")->mValue.getFloat(); const auto& data = item->mData; - for (auto i = 0; i < 4; ++i) + for (size_t i = 0; i < sNumEffects; ++i) { const auto effectID = data.mEffectID[i]; - if (alchemySkill < fWortChanceValue * (i + 1)) + if (alchemySkill < fWortChanceValue * static_cast(i + 1)) break; if (effectID != -1) diff --git a/apps/openmw/mwmechanics/alchemy.hpp b/apps/openmw/mwmechanics/alchemy.hpp index 1b76e400f5..373ca8b887 100644 --- a/apps/openmw/mwmechanics/alchemy.hpp +++ b/apps/openmw/mwmechanics/alchemy.hpp @@ -1,7 +1,6 @@ #ifndef GAME_MWMECHANICS_ALCHEMY_H #define GAME_MWMECHANICS_ALCHEMY_H -#include #include #include @@ -110,7 +109,7 @@ namespace MWMechanics void setPotionName(const std::string& name); ///< Set name of potion to create - std::set listEffects() const; + std::vector listEffects() const; ///< List all effects shared by at least two ingredients. int addIngredient(const MWWorld::Ptr& ingredient); diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index bba6e7361d..c2afef7c0d 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -64,6 +64,11 @@ namespace MWMechanics return left.mArg < right.mArg; } + bool operator==(const EffectKey& left, const EffectKey& right) + { + return left.mId == right.mId && left.mArg == right.mArg; + } + float EffectParam::getMagnitude() const { return mBase + mModifier; diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index b9831c0250..4fe5d9fd4e 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -38,6 +38,7 @@ namespace MWMechanics }; bool operator<(const EffectKey& left, const EffectKey& right); + bool operator==(const EffectKey& left, const EffectKey& right); struct EffectParam { From 66b1745520d9a794fb50db5839e4f7d10ef1372c Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 08:08:06 +0100 Subject: [PATCH 089/231] Use settings values to declare int settings --- apps/opencs/model/prefs/intsetting.cpp | 2 +- apps/opencs/model/prefs/intsetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 39 ++++++++++++++------------ apps/opencs/model/prefs/state.hpp | 3 +- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/opencs/model/prefs/intsetting.cpp b/apps/opencs/model/prefs/intsetting.cpp index 54fd13233e..a593b6f688 100644 --- a/apps/opencs/model/prefs/intsetting.cpp +++ b/apps/opencs/model/prefs/intsetting.cpp @@ -15,7 +15,7 @@ #include "state.hpp" CSMPrefs::IntSetting::IntSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mMin(0) , mMax(std::numeric_limits::max()) diff --git a/apps/opencs/model/prefs/intsetting.hpp b/apps/opencs/model/prefs/intsetting.hpp index a1ed19ffd6..e2926456aa 100644 --- a/apps/opencs/model/prefs/intsetting.hpp +++ b/apps/opencs/model/prefs/intsetting.hpp @@ -23,7 +23,7 @@ namespace CSMPrefs public: explicit IntSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); // defaults to [0, std::numeric_limits::max()] IntSetting& setRange(int min, int max); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 61d64b783e..d44c9de888 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -32,10 +32,10 @@ CSMPrefs::State* CSMPrefs::State::sThis = nullptr; void CSMPrefs::State::declare() { declareCategory("Windows"); - declareInt("default-width", "Default window width", 800) + declareInt(mValues->mWindows.mDefaultWidth, "Default window width") .setTooltip("Newly opened top-level windows will open with this width.") .setMin(80); - declareInt("default-height", "Default window height", 600) + declareInt(mValues->mWindows.mDefaultHeight, "Default window height") .setTooltip("Newly opened top-level windows will open with this height.") .setMin(80); declareBool("show-statusbar", "Show Status Bar", true) @@ -46,7 +46,7 @@ void CSMPrefs::State::declare() .setTooltip( "When a new subview is requested and a matching subview already " " exist, do not open a new subview and use the existing one instead."); - declareInt("max-subviews", "Maximum number of subviews per top-level window", 256) + declareInt(mValues->mWindows.mMaxSubviews, "Maximum number of subviews per top-level window") .setTooltip( "If the maximum number is reached and a new subview is opened " "it will be placed into a new top-level window.") @@ -56,7 +56,7 @@ void CSMPrefs::State::declare() "When a view contains only a single subview, hide the subview title " "bar and if this subview is closed also close the view (unless it is the last " "view for this document)"); - declareInt("minimum-width", "Minimum subview width", 325) + declareInt(mValues->mWindows.mMinimumWidth, "Minimum subview width") .setTooltip("Minimum width of subviews.") .setRange(50, 10000); EnumValue scrollbarOnly("Scrollbar Only", @@ -134,9 +134,9 @@ void CSMPrefs::State::declare() declareBool("ignore-base-records", "Ignore base records in verifier", false); declareCategory("Search & Replace"); - declareInt("char-before", "Characters before search string", 10) + declareInt(mValues->mSearchAndReplace.mCharBefore, "Characters before search string") .setTooltip("Maximum number of character to display in search result before the searched text"); - declareInt("char-after", "Characters after search string", 10) + declareInt(mValues->mSearchAndReplace.mCharAfter, "Characters after search string") .setTooltip("Maximum number of character to display in search result after the searched text"); declareBool("auto-delete", "Delete row from result table after a successful replace", true); @@ -147,17 +147,19 @@ void CSMPrefs::State::declare() "The current row and column numbers of the text cursor are shown at the bottom."); declareBool("wrap-lines", "Wrap Lines", false).setTooltip("Wrap lines longer than width of script editor."); declareBool("mono-font", "Use monospace font", true); - declareInt("tab-width", "Tab Width", 4).setTooltip("Number of characters for tab width").setRange(1, 10); + declareInt(mValues->mScripts.mTabWidth, "Tab Width") + .setTooltip("Number of characters for tab width") + .setRange(1, 10); EnumValue warningsNormal("Normal", "Report warnings as warning"); declareEnum("warnings", "Warning Mode", warningsNormal) .addValue("Ignore", "Do not report warning") .addValue(warningsNormal) .addValue("Strict", "Promote warning to an error"); declareBool("toolbar", "Show toolbar", true); - declareInt("compile-delay", "Delay between updating of source errors", 100) + declareInt(mValues->mScripts.mCompileDelay, "Delay between updating of source errors") .setTooltip("Delay in milliseconds") .setRange(0, 10000); - declareInt("error-height", "Initial height of the error panel", 100).setRange(100, 10000); + declareInt(mValues->mScripts.mErrorHeight, "Initial height of the error panel").setRange(100, 10000); declareBool("highlight-occurrences", "Highlight other occurrences of selected names", true); declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta")); @@ -201,12 +203,12 @@ void CSMPrefs::State::declare() declareDouble("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); declareCategory("Rendering"); - declareInt("framerate-limit", "FPS limit", 60) + declareInt(mValues->mRendering.mFramerateLimit, "FPS limit") .setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\".") .setRange(0, 10000); - declareInt("camera-fov", "Camera FOV", 90).setRange(10, 170); + declareInt(mValues->mRendering.mCameraFov, "Camera FOV").setRange(10, 170); declareBool("camera-ortho", "Orthographic projection for camera", false); - declareInt("camera-ortho-size", "Orthographic projection size parameter", 100) + declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic projection size parameter") .setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.") .setRange(10, 10000); declareDouble("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0, 1); @@ -231,7 +233,7 @@ void CSMPrefs::State::declare() declareCategory("Tooltips"); declareBool("scene", "Show Tooltips in 3D scenes", true); declareBool("scene-hide-basic", "Hide basic 3D scenes tooltips", false); - declareInt("scene-delay", "Tooltip delay in milliseconds", 500).setMin(1); + declareInt(mValues->mTooltips.mSceneDelay, "Tooltip delay in milliseconds").setMin(1); EnumValue createAndInsert("Create cell and insert"); EnumValue showAndInsert("Show cell and insert"); @@ -263,7 +265,7 @@ void CSMPrefs::State::declare() declareDouble("gridsnap-movement", "Grid snap size", 16); declareDouble("gridsnap-rotation", "Angle snap size", 15); declareDouble("gridsnap-scale", "Scale snap size", 0.25); - declareInt("distance", "Drop Distance", 50) + declareInt(mValues->mSceneEditing.mDistance, "Drop Distance") .setTooltip( "If an instance drop can not be placed against another object at the " "insert point, it will be placed by this distance from the insert point instead"); @@ -276,8 +278,8 @@ void CSMPrefs::State::declare() declareEnum("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit) .setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible.") .addValues(landeditOutsideVisibleCell); - declareInt("texturebrush-maximumsize", "Maximum texture brush size", 50).setMin(1); - declareInt("shapebrush-maximumsize", "Maximum height edit brush size", 100) + declareInt(mValues->mSceneEditing.mTexturebrushMaximumsize, "Maximum texture brush size").setMin(1); + declareInt(mValues->mSceneEditing.mShapebrushMaximumsize, "Maximum height edit brush size") .setTooltip("Setting for the slider range of brush size in terrain height editing.") .setMin(1); declareBool("landedit-post-smoothpainting", "Smooth land after painting height", false) @@ -459,12 +461,13 @@ void CSMPrefs::State::declareCategory(const std::string& key) } } -CSMPrefs::IntSetting& CSMPrefs::State::declareInt(const std::string& key, const QString& label, int default_) +CSMPrefs::IntSetting& CSMPrefs::State::declareInt(Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - CSMPrefs::IntSetting* setting = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + CSMPrefs::IntSetting* setting + = new CSMPrefs::IntSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 5f82389c82..1d44f4b71a 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -63,7 +63,8 @@ namespace CSMPrefs void declareCategory(const std::string& key); - IntSetting& declareInt(const std::string& key, const QString& label, int default_); + IntSetting& declareInt(Settings::SettingValue& value, const QString& label); + DoubleSetting& declareDouble(const std::string& key, const QString& label, double default_); BoolSetting& declareBool(const std::string& key, const QString& label, bool default_); From 3e101ab409b25a5dd475527f7971d7f9482e108c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 20 Dec 2023 12:28:34 +0100 Subject: [PATCH 090/231] Add a variadic getHNOT --- apps/essimporter/importcellref.cpp | 36 ++++++++---------------------- apps/essimporter/importplayer.cpp | 14 ++---------- components/esm3/aisequence.cpp | 7 +----- components/esm3/cellid.cpp | 14 +++--------- components/esm3/esmreader.hpp | 12 +++++++++- components/esm3/loadtes3.cpp | 13 ++++------- components/esm3/objectstate.cpp | 11 ++------- components/esm3/player.cpp | 9 ++------ 8 files changed, 34 insertions(+), 82 deletions(-) diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp index 756770d0b5..56e888d3f6 100644 --- a/apps/essimporter/importcellref.cpp +++ b/apps/essimporter/importcellref.cpp @@ -45,23 +45,14 @@ namespace ESSImport bool isDeleted = false; ESM::CellRef::loadData(esm, isDeleted); - mActorData.mHasACDT = false; - if (esm.isNextSub("ACDT")) - { - mActorData.mHasACDT = true; - esm.getHT(mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter, + mActorData.mHasACDT + = esm.getHNOT("ACDT", mActorData.mACDT.mUnknown, mActorData.mACDT.mFlags, mActorData.mACDT.mBreathMeter, mActorData.mACDT.mUnknown2, mActorData.mACDT.mDynamic, mActorData.mACDT.mUnknown3, mActorData.mACDT.mAttributes, mActorData.mACDT.mMagicEffects, mActorData.mACDT.mUnknown4, mActorData.mACDT.mGoldPool, mActorData.mACDT.mCountDown, mActorData.mACDT.mUnknown5); - } - mActorData.mHasACSC = false; - if (esm.isNextSub("ACSC")) - { - mActorData.mHasACSC = true; - esm.getHT(mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, mActorData.mACSC.mUnknown2, - mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3); - } + mActorData.mHasACSC = esm.getHNOT("ACSC", mActorData.mACSC.mUnknown1, mActorData.mACSC.mFlags, + mActorData.mACSC.mUnknown2, mActorData.mACSC.mCorpseClearCountdown, mActorData.mACSC.mUnknown3); if (esm.isNextSub("ACSL")) esm.skipHSubSize(112); @@ -127,23 +118,17 @@ namespace ESSImport } // FIXME: not all actors have this, add flag - if (esm.isNextSub("CHRD")) // npc only - esm.getHExact(mActorData.mSkills, 27 * 2 * sizeof(int)); + esm.getHNOT("CHRD", mActorData.mSkills); // npc only - if (esm.isNextSub("CRED")) // creature only - esm.getHExact(mActorData.mCombatStats, 3 * 2 * sizeof(int)); + esm.getHNOT("CRED", mActorData.mCombatStats); // creature only mActorData.mSCRI.load(esm); if (esm.isNextSub("ND3D")) esm.skipHSub(); - mActorData.mHasANIS = false; - if (esm.isNextSub("ANIS")) - { - mActorData.mHasANIS = true; - esm.getHT(mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime); - } + mActorData.mHasANIS + = esm.getHNOT("ANIS", mActorData.mANIS.mGroupIndex, mActorData.mANIS.mUnknown, mActorData.mANIS.mTime); if (esm.isNextSub("LVCR")) { @@ -161,10 +146,7 @@ namespace ESSImport // I've seen DATA *twice* on a creature record, and with the exact same content too! weird // alarmvoi0000.ess for (int i = 0; i < 2; ++i) - { - if (esm.isNextSub("DATA")) - esm.getHNT("DATA", mPos.pos, mPos.rot); - } + esm.getHNOT("DATA", mPos.pos, mPos.rot); mDeleted = 0; if (esm.isNextSub("DELE")) diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp index b9e1d4f291..f4c280541d 100644 --- a/apps/essimporter/importplayer.cpp +++ b/apps/essimporter/importplayer.cpp @@ -55,12 +55,7 @@ namespace ESSImport if (esm.isNextSub("NAM3")) esm.skipHSub(); - mHasENAM = false; - if (esm.isNextSub("ENAM")) - { - mHasENAM = true; - esm.getHT(mENAM.mCellX, mENAM.mCellY); - } + mHasENAM = esm.getHNOT("ENAM", mENAM.mCellX, mENAM.mCellY); if (esm.isNextSub("LNAM")) esm.skipHSub(); @@ -73,12 +68,7 @@ namespace ESSImport mFactions.push_back(fnam); } - mHasAADT = false; - if (esm.isNextSub("AADT")) // Attack animation data? - { - mHasAADT = true; - esm.getHT(mAADT.animGroupIndex, mAADT.mUnknown5); - } + mHasAADT = esm.getHNOT("AADT", mAADT.animGroupIndex, mAADT.mUnknown5); // Attack animation data? if (esm.isNextSub("KNAM")) esm.skipHSub(); // assigned Quick Keys, I think diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index 21973acd1d..bbd42c400e 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -15,12 +15,7 @@ namespace ESM { esm.getHNT("DATA", mData.mDistance, mData.mDuration, mData.mTimeOfDay, mData.mIdle, mData.mShouldRepeat); esm.getHNT("STAR", mDurationData.mRemainingDuration, mDurationData.unused); // was mStartTime - mStoredInitialActorPosition = false; - if (esm.isNextSub("POS_")) - { - mStoredInitialActorPosition = true; - esm.getHT(mInitialActorPosition.mValues); - } + mStoredInitialActorPosition = esm.getHNOT("POS_", mInitialActorPosition.mValues); } void AiWander::save(ESMWriter& esm) const diff --git a/components/esm3/cellid.cpp b/components/esm3/cellid.cpp index 3df1336c6c..9a5be3aada 100644 --- a/components/esm3/cellid.cpp +++ b/components/esm3/cellid.cpp @@ -11,17 +11,9 @@ namespace ESM { mWorldspace = esm.getHNString("SPAC"); - if (esm.isNextSub("CIDX")) - { - esm.getHT(mIndex.mX, mIndex.mY); - mPaged = true; - } - else - { - mPaged = false; - mIndex.mX = 0; - mIndex.mY = 0; - } + mIndex.mX = 0; + mIndex.mY = 0; + mPaged = esm.getHNOT("CIDX", mIndex.mX, mIndex.mY); } void CellId::save(ESMWriter& esm) const diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index d753023645..bafc89a74c 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -151,9 +151,19 @@ namespace ESM // Optional version of getHNT template void getHNOT(X& x, NAME name) + { + getHNOT(name, x); + } + + template + bool getHNOT(NAME name, Args&... args) { if (isNextSub(name)) - getHT(x); + { + getHT(args...); + return true; + } + return false; } // Get data of a given type/size, including subrecord header diff --git a/components/esm3/loadtes3.cpp b/components/esm3/loadtes3.cpp index 131510ce89..b6fbe76553 100644 --- a/components/esm3/loadtes3.cpp +++ b/components/esm3/loadtes3.cpp @@ -20,10 +20,8 @@ namespace ESM void Header::load(ESMReader& esm) { - if (esm.isNextSub("FORM")) - esm.getHT(mFormatVersion); - else - mFormatVersion = DefaultFormatVersion; + mFormatVersion = DefaultFormatVersion; + esm.getHNOT("FORM", mFormatVersion); if (esm.isNextSub("HEDR")) { @@ -43,11 +41,8 @@ namespace ESM mMaster.push_back(m); } - if (esm.isNextSub("GMDT")) - { - esm.getHT(mGameData.mCurrentHealth, mGameData.mMaximumHealth, mGameData.mHour, mGameData.unknown1, - mGameData.mCurrentCell.mData, mGameData.unknown2, mGameData.mPlayerName.mData); - } + esm.getHNOT("GMDT", mGameData.mCurrentHealth, mGameData.mMaximumHealth, mGameData.mHour, mGameData.unknown1, + mGameData.mCurrentCell.mData, mGameData.unknown2, mGameData.mPlayerName.mData); if (esm.isNextSub("SCRD")) { esm.getSubHeader(); diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index 6e2621df29..a46200944a 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -32,15 +32,8 @@ namespace ESM mCount = 1; esm.getHNOT(mCount, "COUN"); - if (esm.isNextSub("POS_")) - { - std::array pos; - esm.getHT(pos); - memcpy(mPosition.pos, pos.data(), sizeof(float) * 3); - memcpy(mPosition.rot, pos.data() + 3, sizeof(float) * 3); - } - else - mPosition = mRef.mPos; + mPosition = mRef.mPos; + esm.getHNOT("POS_", mPosition.pos, mPosition.rot); mFlags = 0; esm.getHNOT(mFlags, "FLAG"); diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index 901b52ce1d..aa6b59abb9 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -15,14 +15,9 @@ namespace ESM esm.getHNT("LKEP", mLastKnownExteriorPosition); - if (esm.isNextSub("MARK")) - { - mHasMark = true; - esm.getHT(mMarkedPosition.pos, mMarkedPosition.rot); + mHasMark = esm.getHNOT("MARK", mMarkedPosition.pos, mMarkedPosition.rot); + if (mHasMark) mMarkedCell = esm.getCellId(); - } - else - mHasMark = false; // Automove, no longer used. if (esm.isNextSub("AMOV")) From 532a330aac1d1eb31c5e419a840f415b53147b96 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Wed, 20 Dec 2023 13:58:43 +0100 Subject: [PATCH 091/231] mac plz --- apps/openmw/mwmechanics/alchemy.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index e9662f395f..f8e0046e19 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -73,7 +73,7 @@ std::vector MWMechanics::Alchemy::listEffects() const { if (const auto key = toKey(*ingredient, i)) { - if (std::ranges::find(effects, *key) != effects.end()) + if (std::find(effects.begin(), effects.end(), *key) != effects.end()) continue; if (containsEffect(*ingredient2, *key)) effects.push_back(*key); From 491525d17355ca9f2749286aa61b903271871929 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 20 Dec 2023 22:37:41 +0100 Subject: [PATCH 092/231] Add shebangs to bash scripts To specify used interpreter and set exit on error mode. --- CI/teal_ci.sh | 2 +- docs/source/install_luadocumentor_in_docker.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CI/teal_ci.sh b/CI/teal_ci.sh index 5ea312e88c..8117e93443 100755 --- a/CI/teal_ci.sh +++ b/CI/teal_ci.sh @@ -1,4 +1,4 @@ -set -e +#!/bin/bash -e docs/source/install_luadocumentor_in_docker.sh PATH=$PATH:~/luarocks/bin diff --git a/docs/source/install_luadocumentor_in_docker.sh b/docs/source/install_luadocumentor_in_docker.sh index a1ec253600..6d24f3e9f3 100755 --- a/docs/source/install_luadocumentor_in_docker.sh +++ b/docs/source/install_luadocumentor_in_docker.sh @@ -1,3 +1,5 @@ +#!/bin/bash -e + if [ ! -f /.dockerenv ] && [ ! -f /home/docs/omw_luadoc_docker ]; then echo 'This script installs lua-5.1, luarocks, and openmwluadocumentor to $HOME. Should be used only in docker.' exit 1 From 29e8e7ba076df8facd5b4ee925501efe94f4f23a Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Dec 2023 01:50:58 +0100 Subject: [PATCH 093/231] Update recasnagivation to c393777d26d2ff6519ac23612abf8af42678c9dd --- extern/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 6f55e4f1c6..10d75c1057 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -173,11 +173,10 @@ if(NOT OPENMW_USE_SYSTEM_RECASTNAVIGATION) set(RECASTNAVIGATION_TESTS OFF CACHE BOOL "") set(RECASTNAVIGATION_EXAMPLES OFF CACHE BOOL "") - # master on 12 Oct 2022 include(FetchContent) FetchContent_Declare(recastnavigation - URL https://github.com/recastnavigation/recastnavigation/archive/405cc095ab3a2df976a298421974a2af83843baf.zip - URL_HASH SHA512=39580ca258783ab3bda237843facc918697266e729c85065cdae1f0ed3d0ed1429a7ed08b18b926ba64402d9875a18f52904a87f43fe4fe30252f23edcfa6c70 + URL https://github.com/recastnavigation/recastnavigation/archive/c393777d26d2ff6519ac23612abf8af42678c9dd.zip + URL_HASH SHA512=48f20cee7a70c2f20f4c68bb74d5af11a1434be85294e37f5fe7b7aae820fbcdff4f35d3be286eaf6f9cbce0aed4201fcc090df409a5bd04aec5fd7c29b3ad94 SOURCE_DIR fetched/recastnavigation ) FetchContent_MakeAvailableExcludeFromAll(recastnavigation) From 78a0e0eb3b0fc65d6541946d75a5d857c5c8da67 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 21 Dec 2023 02:19:14 +0100 Subject: [PATCH 094/231] Handle and log some controller related SDL errors SDL_GameControllerNameForIndex may return nullptr indicating an error which causes a crash when passed to log. --- apps/openmw/mwinput/controllermanager.cpp | 38 ++++++++++++++++++----- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 7054f72c8f..6d82b1e226 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -34,16 +34,27 @@ namespace MWInput { if (!controllerBindingsFile.empty()) { - SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(controllerBindingsFile).c_str()); + const int result + = SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(controllerBindingsFile).c_str()); + if (result < 0) + Log(Debug::Error) << "Failed to add game controller mappings from file \"" << controllerBindingsFile + << "\": " << SDL_GetError(); } if (!userControllerBindingsFile.empty()) { - SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(userControllerBindingsFile).c_str()); + const int result + = SDL_GameControllerAddMappingsFromFile(Files::pathToUnicodeString(userControllerBindingsFile).c_str()); + if (result < 0) + Log(Debug::Error) << "Failed to add game controller mappings from user file \"" + << userControllerBindingsFile << "\": " << SDL_GetError(); } // Open all presently connected sticks - int numSticks = SDL_NumJoysticks(); + const int numSticks = SDL_NumJoysticks(); + if (numSticks < 0) + Log(Debug::Error) << "Failed to get number of joysticks: " << SDL_GetError(); + for (int i = 0; i < numSticks; i++) { if (SDL_IsGameController(i)) @@ -52,11 +63,17 @@ namespace MWInput evt.which = i; static const int fakeDeviceID = 1; ControllerManager::controllerAdded(fakeDeviceID, evt); - Log(Debug::Info) << "Detected game controller: " << SDL_GameControllerNameForIndex(i); + if (const char* name = SDL_GameControllerNameForIndex(i)) + Log(Debug::Info) << "Detected game controller: " << name; + else + Log(Debug::Warning) << "Detected game controller without a name: " << SDL_GetError(); } else { - Log(Debug::Info) << "Detected unusable controller: " << SDL_JoystickNameForIndex(i); + if (const char* name = SDL_JoystickNameForIndex(i)) + Log(Debug::Info) << "Detected unusable controller: " << name; + else + Log(Debug::Warning) << "Detected unusable controller without a name: " << SDL_GetError(); } } @@ -336,8 +353,11 @@ namespace MWInput return; if (!SDL_GameControllerHasSensor(cntrl, SDL_SENSOR_GYRO)) return; - if (SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE) < 0) + if (const int result = SDL_GameControllerSetSensorEnabled(cntrl, SDL_SENSOR_GYRO, SDL_TRUE); result < 0) + { + Log(Debug::Error) << "Failed to enable game controller sensor: " << SDL_GetError(); return; + } mGyroAvailable = true; #endif } @@ -353,7 +373,11 @@ namespace MWInput #if SDL_VERSION_ATLEAST(2, 0, 14) SDL_GameController* cntrl = mBindingsManager->getControllerOrNull(); if (cntrl && mGyroAvailable) - SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); + { + const int result = SDL_GameControllerGetSensorData(cntrl, SDL_SENSOR_GYRO, gyro, 3); + if (result < 0) + Log(Debug::Error) << "Failed to get game controller sensor data: " << SDL_GetError(); + } #endif return std::array({ gyro[0], gyro[1], gyro[2] }); } From 187f63d3d3568fd8dfc7104ddf72d35f94acb4ba Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Fri, 10 Nov 2023 08:02:53 -0800 Subject: [PATCH 095/231] support postprocess distortion --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/distortion.cpp | 28 ++++ apps/openmw/mwrender/distortion.hpp | 28 ++++ apps/openmw/mwrender/npcanimation.cpp | 9 +- apps/openmw/mwrender/pingpongcanvas.cpp | 4 + apps/openmw/mwrender/pingpongcanvas.hpp | 3 + apps/openmw/mwrender/postprocessor.cpp | 120 +++++++++++------- apps/openmw/mwrender/postprocessor.hpp | 14 +- apps/openmw/mwrender/renderbin.hpp | 3 +- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/fx/pass.cpp | 1 + components/fx/technique.hpp | 4 + components/nif/property.hpp | 4 + components/nifosg/nifloader.cpp | 4 + components/resource/scenemanager.cpp | 1 + components/sceneutil/extradata.cpp | 19 +++ components/sceneutil/extradata.hpp | 1 + .../modding/custom-shader-effects.rst | 35 +++++ files/data/CMakeLists.txt | 1 + files/data/shaders/internal_distortion.omwfx | 25 ++++ files/shaders/CMakeLists.txt | 1 + files/shaders/compatibility/bs/default.frag | 15 ++- files/shaders/compatibility/objects.frag | 17 ++- files/shaders/lib/util/distortion.glsl | 32 +++++ files/shaders/lib/util/quickstep.glsl | 4 +- 25 files changed, 311 insertions(+), 65 deletions(-) create mode 100644 apps/openmw/mwrender/distortion.cpp create mode 100644 apps/openmw/mwrender/distortion.hpp create mode 100644 files/data/shaders/internal_distortion.omwfx create mode 100644 files/shaders/lib/util/distortion.glsl diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index db44b91159..373de3683d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -24,7 +24,7 @@ add_openmw_dir (mwrender bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples - actorutil + actorutil distortion ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/distortion.cpp b/apps/openmw/mwrender/distortion.cpp new file mode 100644 index 0000000000..2ca2ace65b --- /dev/null +++ b/apps/openmw/mwrender/distortion.cpp @@ -0,0 +1,28 @@ +#include "distortion.hpp" + +#include + +namespace MWRender +{ + void DistortionCallback::drawImplementation( + osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) + { + osg::State* state = renderInfo.getState(); + size_t frameId = state->getFrameStamp()->getFrameNumber() % 2; + + mFBO[frameId]->apply(*state); + + const osg::Texture* tex + = mFBO[frameId]->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0).getTexture(); + + glViewport(0, 0, tex->getTextureWidth(), tex->getTextureHeight()); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + bin->drawImplementation(renderInfo, previous); + + tex = mOriginalFBO[frameId]->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0).getTexture(); + glViewport(0, 0, tex->getTextureWidth(), tex->getTextureHeight()); + mOriginalFBO[frameId]->apply(*state); + } +} diff --git a/apps/openmw/mwrender/distortion.hpp b/apps/openmw/mwrender/distortion.hpp new file mode 100644 index 0000000000..736f4ea6f2 --- /dev/null +++ b/apps/openmw/mwrender/distortion.hpp @@ -0,0 +1,28 @@ +#include + +#include + +namespace osg +{ + class FrameBufferObject; +} + +namespace MWRender +{ + class DistortionCallback : public osgUtil::RenderBin::DrawCallback + { + public: + void drawImplementation( + osgUtil::RenderBin* bin, osg::RenderInfo& renderInfo, osgUtil::RenderLeaf*& previous) override; + + void setFBO(const osg::ref_ptr& fbo, size_t frameId) { mFBO[frameId] = fbo; } + void setOriginalFBO(const osg::ref_ptr& fbo, size_t frameId) + { + mOriginalFBO[frameId] = fbo; + } + + private: + std::array, 2> mFBO; + std::array, 2> mOriginalFBO; + }; +} diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 469978e6eb..d1cd5fed60 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -345,11 +345,9 @@ namespace MWRender bin->drawImplementation(renderInfo, previous); auto primaryFBO = postProcessor->getPrimaryFbo(frameId); + primaryFBO->apply(*state); - if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) - postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); - else - primaryFBO->apply(*state); + postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)->apply(*state); // depth accumulation pass osg::ref_ptr restore = bin->getStateSet(); @@ -357,8 +355,7 @@ namespace MWRender bin->drawImplementation(renderInfo, previous); bin->setStateSet(restore); - if (postProcessor->getFbo(PostProcessor::FBO_OpaqueDepth, frameId)) - primaryFBO->apply(*state); + primaryFBO->apply(*state); state->checkGLErrors("after DepthClearCallback::drawImplementation"); } diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 4fecdf87f9..9c8b08adfd 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -242,6 +242,10 @@ namespace MWRender if (mTextureNormals) node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals); + if (mTextureDistortion) + node.mRootStateSet->setTextureAttribute( + PostProcessor::TextureUnits::Unit_Distortion, mTextureDistortion); + state.pushStateSet(node.mRootStateSet); state.apply(); diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index a03e3591ae..f7212a3f18 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -48,6 +48,8 @@ namespace MWRender void setTextureNormals(osg::ref_ptr tex) { mTextureNormals = tex; } + void setTextureDistortion(osg::ref_ptr tex) { mTextureDistortion = tex; } + void setCalculateAvgLum(bool enabled) { mAvgLum = enabled; } void setPostProcessing(bool enabled) { mPostprocessing = enabled; } @@ -69,6 +71,7 @@ namespace MWRender osg::ref_ptr mTextureScene; osg::ref_ptr mTextureDepth; osg::ref_ptr mTextureNormals; + osg::ref_ptr mTextureDistortion; mutable bool mDirty = false; mutable std::vector mDirtyAttachments; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 2c77981244..1aaeb460b7 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -29,7 +29,9 @@ #include "../mwgui/postprocessorhud.hpp" +#include "distortion.hpp" #include "pingpongcull.hpp" +#include "renderbin.hpp" #include "renderingmanager.hpp" #include "sky.hpp" #include "transparentpass.hpp" @@ -103,6 +105,8 @@ namespace return Stereo::createMultiviewCompatibleAttachment(texture); } + + constexpr float DistortionRatio = 0.25; } namespace MWRender @@ -118,6 +122,7 @@ namespace MWRender , mUsePostProcessing(Settings::postProcessing().mEnabled) , mSamples(Settings::video().mAntialiasing) , mPingPongCull(new PingPongCull(this)) + , mDistortionCallback(new DistortionCallback) { auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager(); @@ -141,18 +146,45 @@ namespace MWRender mHUDCamera->setCullCallback(new HUDCullCallback); mViewer->getCamera()->addCullCallback(mPingPongCull); - if (Settings::shaders().mSoftParticles || Settings::postProcessing().mTransparentPostpass) - { - mTransparentDepthPostPass - = new TransparentDepthBinCallback(shaderManager, Settings::postProcessing().mTransparentPostpass); - osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); - } + // resolves the multisampled depth buffer and optionally draws an additional depth postpass + mTransparentDepthPostPass + = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), + Settings::postProcessing().mTransparentPostpass); + osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); + + osg::ref_ptr distortionRenderBin + = new osgUtil::RenderBin(osgUtil::RenderBin::SORT_BACK_TO_FRONT); + // This is silly to have to do, but if nothing is drawn then the drawcallback is never called and the distortion + // texture will never be cleared + osg::ref_ptr dummyNodeToClear = new osg::Node; + dummyNodeToClear->setCullingActive(false); + dummyNodeToClear->getOrCreateStateSet()->setRenderBinDetails(RenderBin_Distortion, "Distortion"); + rootNode->addChild(dummyNodeToClear); + distortionRenderBin->setDrawCallback(mDistortionCallback); + distortionRenderBin->getStateSet()->setDefine("DISTORTION", "1", osg::StateAttribute::ON); + + // Give the renderbin access to the opaque depth sampler so it can write its occlusion + // Distorted geometry is drawn with ALWAYS depth function and depths writes disbled. + const int unitSoftEffect + = shaderManager.reserveGlobalTextureUnits(Shader::ShaderManager::Slot::OpaqueDepthTexture); + distortionRenderBin->getStateSet()->addUniform(new osg::Uniform("opaqueDepthTex", unitSoftEffect)); + + osgUtil::RenderBin::addRenderBinPrototype("Distortion", distortionRenderBin); + + auto defines = shaderManager.getGlobalDefines(); + defines["distorionRTRatio"] = std::to_string(DistortionRatio); + shaderManager.setGlobalDefines(defines); createObjectsForFrame(0); createObjectsForFrame(1); populateTechniqueFiles(); + auto distortion = loadTechnique("internal_distortion"); + distortion->setInternal(true); + distortion->setLocked(true); + mInternalTechniques.push_back(distortion); + osg::GraphicsContext* gc = viewer->getCamera()->getGraphicsContext(); osg::GLExtensions* ext = gc->getState()->get(); @@ -171,19 +203,6 @@ namespace MWRender else Log(Debug::Error) << "'glDisablei' unsupported, pass normals will not be available to shaders."; - if (Settings::shaders().mSoftParticles) - { - for (int i = 0; i < 2; ++i) - { - if (Stereo::getMultiview()) - mTextures[i][Tex_OpaqueDepth] = new osg::Texture2DArray; - else - mTextures[i][Tex_OpaqueDepth] = new osg::Texture2D; - mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); - mTextures[i][Tex_OpaqueDepth]->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); - } - } - mGLSLVersion = ext->glslLanguageVersion * 100; mUBO = ext->isUniformBufferObjectSupported && mGLSLVersion >= 330; mStateUpdater = new fx::StateUpdater(mUBO); @@ -281,17 +300,15 @@ namespace MWRender mCanvases[frameId]->setCalculateAvgLum(mHDR); mCanvases[frameId]->setTextureScene(getTexture(Tex_Scene, frameId)); - if (mTransparentDepthPostPass) - mCanvases[frameId]->setTextureDepth(getTexture(Tex_OpaqueDepth, frameId)); - else - mCanvases[frameId]->setTextureDepth(getTexture(Tex_Depth, frameId)); + mCanvases[frameId]->setTextureDepth(getTexture(Tex_OpaqueDepth, frameId)); + mCanvases[frameId]->setTextureDistortion(getTexture(Tex_Distortion, frameId)); - if (mTransparentDepthPostPass) - { - mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; - mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample]; - mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; - } + mTransparentDepthPostPass->mFbo[frameId] = mFbos[frameId][FBO_Primary]; + mTransparentDepthPostPass->mMsaaFbo[frameId] = mFbos[frameId][FBO_Multisample]; + mTransparentDepthPostPass->mOpaqueFbo[frameId] = mFbos[frameId][FBO_OpaqueDepth]; + + mDistortionCallback->setFBO(mFbos[frameId][FBO_Distortion], frameId); + mDistortionCallback->setOriginalFBO(mFbos[frameId][FBO_Primary], frameId); size_t frame = cv->getTraversalNumber(); @@ -441,6 +458,13 @@ namespace MWRender textures[Tex_Normal]->setSourceFormat(GL_RGB); textures[Tex_Normal]->setInternalFormat(GL_RGB); + textures[Tex_Distortion]->setSourceFormat(GL_RGB); + textures[Tex_Distortion]->setInternalFormat(GL_RGB); + + Stereo::setMultiviewCompatibleTextureSize( + textures[Tex_Distortion], width * DistortionRatio, height * DistortionRatio); + textures[Tex_Distortion]->dirtyTextureObject(); + auto setupDepth = [](osg::Texture* tex) { tex->setSourceFormat(GL_DEPTH_STENCIL_EXT); tex->setSourceType(SceneUtil::AutoDepth::depthSourceType()); @@ -448,16 +472,8 @@ namespace MWRender }; setupDepth(textures[Tex_Depth]); - - if (!mTransparentDepthPostPass) - { - textures[Tex_OpaqueDepth] = nullptr; - } - else - { - setupDepth(textures[Tex_OpaqueDepth]); - textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); - } + setupDepth(textures[Tex_OpaqueDepth]); + textures[Tex_OpaqueDepth]->setName("opaqueTexMap"); auto& fbos = mFbos[frameId]; @@ -487,6 +503,7 @@ namespace MWRender auto normalRB = createFrameBufferAttachmentFromTemplate( Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples); fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); + fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB); } auto depthRB = createFrameBufferAttachmentFromTemplate( Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples); @@ -510,12 +527,13 @@ namespace MWRender Stereo::createMultiviewCompatibleAttachment(textures[Tex_Normal])); } - if (textures[Tex_OpaqueDepth]) - { - fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; - fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, - Stereo::createMultiviewCompatibleAttachment(textures[Tex_OpaqueDepth])); - } + fbos[FBO_OpaqueDepth] = new osg::FrameBufferObject; + fbos[FBO_OpaqueDepth]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, + Stereo::createMultiviewCompatibleAttachment(textures[Tex_OpaqueDepth])); + + fbos[FBO_Distortion] = new osg::FrameBufferObject; + fbos[FBO_Distortion]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + Stereo::createMultiviewCompatibleAttachment(textures[Tex_Distortion])); #ifdef __APPLE__ if (textures[Tex_OpaqueDepth]) @@ -575,6 +593,7 @@ namespace MWRender node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastShader", Unit_LastShader)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerLastPass", Unit_LastPass)); node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDepth", Unit_Depth)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDistortion", Unit_Distortion)); if (mNormals) node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerNormals", Unit_Normals)); @@ -582,6 +601,8 @@ namespace MWRender if (technique->getHDR()) node.mRootStateSet->addUniform(new osg::Uniform("omw_EyeAdaptation", Unit_EyeAdaptation)); + node.mRootStateSet->addUniform(new osg::Uniform("omw_SamplerDistortion", Unit_Distortion)); + int texUnit = Unit_NextFree; // user-defined samplers @@ -681,7 +702,7 @@ namespace MWRender disableTechnique(technique, false); - int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); + int pos = std::min(location.value_or(mTechniques.size()) + mInternalTechniques.size(), mTechniques.size()); mTechniques.insert(mTechniques.begin() + pos, technique); dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug); @@ -747,6 +768,11 @@ namespace MWRender { mTechniques.clear(); + for (const auto& technique : mInternalTechniques) + { + mTechniques.push_back(technique); + } + for (const std::string& techniqueName : Settings::postProcessing().mChain.get()) { if (techniqueName.empty()) @@ -764,7 +790,7 @@ namespace MWRender for (const auto& technique : mTechniques) { - if (!technique || technique->getDynamic()) + if (!technique || technique->getDynamic() || technique->getInternal()) continue; chain.push_back(technique->getName()); } diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index e9f19bf6b5..2630467f95 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -50,12 +50,13 @@ namespace MWRender class PingPongCull; class PingPongCanvas; class TransparentDepthBinCallback; + class DistortionCallback; class PostProcessor : public osg::Group { public: - using FBOArray = std::array, 5>; - using TextureArray = std::array, 5>; + using FBOArray = std::array, 6>; + using TextureArray = std::array, 6>; using TechniqueList = std::vector>; enum TextureIndex @@ -64,7 +65,8 @@ namespace MWRender Tex_Scene_LDR, Tex_Depth, Tex_OpaqueDepth, - Tex_Normal + Tex_Normal, + Tex_Distortion, }; enum FBOIndex @@ -73,7 +75,8 @@ namespace MWRender FBO_Multisample, FBO_FirstPerson, FBO_OpaqueDepth, - FBO_Intercept + FBO_Intercept, + FBO_Distortion, }; enum TextureUnits @@ -83,6 +86,7 @@ namespace MWRender Unit_Depth, Unit_EyeAdaptation, Unit_Normals, + Unit_Distortion, Unit_NextFree }; @@ -223,6 +227,7 @@ namespace MWRender TechniqueList mTechniques; TechniqueList mTemplates; TechniqueList mQueuedTemplates; + TechniqueList mInternalTechniques; std::unordered_map mTechniqueFileMap; @@ -258,6 +263,7 @@ namespace MWRender osg::ref_ptr mPingPongCull; std::array, 2> mCanvases; osg::ref_ptr mTransparentDepthPostPass; + osg::ref_ptr mDistortionCallback; fx::DispatchArray mTemplateData; }; diff --git a/apps/openmw/mwrender/renderbin.hpp b/apps/openmw/mwrender/renderbin.hpp index c14f611426..6f4ae0819b 100644 --- a/apps/openmw/mwrender/renderbin.hpp +++ b/apps/openmw/mwrender/renderbin.hpp @@ -13,7 +13,8 @@ namespace MWRender RenderBin_DepthSorted = 10, // osg::StateSet::TRANSPARENT_BIN RenderBin_OcclusionQuery = 11, RenderBin_FirstPerson = 12, - RenderBin_SunGlare = 13 + RenderBin_SunGlare = 13, + RenderBin_Distortion = 14, }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c175817fa8..7df5671eec 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -502,6 +502,7 @@ namespace MWRender sceneRoot->getOrCreateStateSet()->setAttribute(defaultMat); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f)); sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); + sceneRoot->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f)); mFog = std::make_unique(); diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 76b54d55a5..cf50d20fe2 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -91,6 +91,7 @@ uniform @builtinSampler omw_SamplerLastShader; uniform @builtinSampler omw_SamplerLastPass; uniform @builtinSampler omw_SamplerDepth; uniform @builtinSampler omw_SamplerNormals; +uniform @builtinSampler omw_SamplerDistortion; uniform vec4 omw_PointLights[@pointLightCount]; uniform int omw_PointLightsCount; diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index ed356e0a37..0d17128e56 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -175,6 +175,9 @@ namespace fx void setLocked(bool locked) { mLocked = locked; } bool getLocked() const { return mLocked; } + void setInternal(bool internal) { mInternal = internal; } + bool getInternal() const { return mInternal; } + private: [[noreturn]] void error(const std::string& msg); @@ -295,6 +298,7 @@ namespace fx bool mDynamic = false; bool mLocked = false; + bool mInternal = false; }; template <> diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 7d420f1650..fbc7e8294c 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -108,6 +108,8 @@ namespace Nif enum BSShaderFlags1 { BSSFlag1_Specular = 0x00000001, + BSSFlag1_Refraction = 0x00008000, + BSSFlag1_FireRefraction = 0x00010000, BSSFlag1_Decal = 0x04000000, BSSFlag1_DepthTest = 0x80000000, }; @@ -148,6 +150,8 @@ namespace Nif bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; } bool depthTest() const { return mShaderFlags1 & BSSFlag1_DepthTest; } bool depthWrite() const { return mShaderFlags2 & BSSFlag2_DepthWrite; } + bool refraction() const { return mShaderFlags1 & BSSFlag1_Refraction; } + bool fireRefraction() const { return mShaderFlags1 & BSSFlag1_FireRefraction; } }; struct BSShaderLightingProperty : BSShaderProperty diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index c30add7f77..ea4c16e402 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2381,6 +2381,8 @@ namespace NifOsg textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + if (texprop->refraction()) + SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength); break; } case Nif::RC_BSShaderNoLightingProperty: @@ -2438,6 +2440,8 @@ namespace NifOsg if (texprop->treeAnim()) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); + if (texprop->refraction()) + SceneUtil::setupDistortion(*node, texprop->mRefractionStrength); break; } case Nif::RC_BSEffectShaderProperty: diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index b3c9eee5e7..25abcfd0d8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -649,6 +649,7 @@ namespace Resource node->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f)); node->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1, 1, 1, 1))); node->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); + node->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f)); } node->setUserValue(Misc::OsgUserValues::sFileHash, diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index 720e032a61..bd82e9abba 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -29,6 +29,19 @@ namespace SceneUtil node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); } + void setupDistortion(osg::Node& node, float distortionStrength) + { + static const osg::ref_ptr depth + = new SceneUtil::AutoDepth(osg::Depth::ALWAYS, 0, 1, false); + + osg::StateSet* stateset = node.getOrCreateStateSet(); + + stateset->setRenderBinDetails(14, "Distortion", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); + stateset->addUniform(new osg::Uniform("distortionStrength", distortionStrength)); + + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + void ProcessExtraDataVisitor::apply(osg::Node& node) { if (!mSceneMgr->getSoftParticles()) @@ -54,6 +67,12 @@ namespace SceneUtil setupSoftEffect(node, size, falloff, falloffDepth); } + else if (key == "distortion") + { + auto strength = it.second["strength"].as(0.1f); + + setupDistortion(node, strength); + } } node.setUserValue(Misc::OsgUserValues::sExtraData, std::string{}); diff --git a/components/sceneutil/extradata.hpp b/components/sceneutil/extradata.hpp index 9b1563b78a..7054ac91b3 100644 --- a/components/sceneutil/extradata.hpp +++ b/components/sceneutil/extradata.hpp @@ -16,6 +16,7 @@ namespace osg namespace SceneUtil { void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth); + void setupDistortion(osg::Node& node, float distortionStrength); class ProcessExtraDataVisitor : public osg::NodeVisitor { diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 5ea711953d..0bd1fbec85 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -54,3 +54,38 @@ Example usage. } } } + +Distortion +---------- + +This effect is used to imitate effects such as refraction and heat distortion. A common use case is to assign a normal map to the +diffuse slot to a material and add uv scrolling. The red and green channels of the texture are used to offset the final scene texture. +Blue and alpha channels are ignored. + +To use this feature the :ref:`post processing` setting must be enabled. +This setting can either be activated in the OpenMW launcher, in-game, or changed in `settings.cfg`: + +:: + + [Post Processing] + enabled = false + +Variables. + ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| Name | Description | Type | Default | ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| strength| The strength of the distortion effect. Scales linearly. | float | 0.1 | ++---------+--------------------------------------------------------------------------------------------------------+---------+---------+ + +Example usage. + +:: + + omw:data { + "shader" : { + "distortion" : { + "strength": 0.12, + } + } + } diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index dbf86cc44d..4e3354807c 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -96,6 +96,7 @@ set(BUILTIN_DATA_FILES shaders/adjustments.omwfx shaders/bloomlinear.omwfx shaders/debug.omwfx + shaders/internal_distortion.omwfx mygui/core.skin mygui/core.xml diff --git a/files/data/shaders/internal_distortion.omwfx b/files/data/shaders/internal_distortion.omwfx new file mode 100644 index 0000000000..b641bb6711 --- /dev/null +++ b/files/data/shaders/internal_distortion.omwfx @@ -0,0 +1,25 @@ +fragment main { + + omw_In vec2 omw_TexCoord; + + void main() + { + const float multiplier = 0.14; + + vec2 offset = omw_Texture2D(omw_SamplerDistortion, omw_TexCoord).rg; + offset *= multiplier; + offset = clamp(offset, vec2(-1.0), vec2(1.0)); + + float occlusionFactor = omw_Texture2D(omw_SamplerDistortion, omw_TexCoord+offset).b; + + omw_FragColor = mix(omw_GetLastShader(omw_TexCoord + offset), omw_GetLastShader(omw_TexCoord), occlusionFactor); + } +} + +technique { + description = "Internal refraction shader for OpenMW"; + version = "1.0"; + author = "OpenMW"; + passes = main; + flags = hidden; +} diff --git a/files/shaders/CMakeLists.txt b/files/shaders/CMakeLists.txt index 6ead738477..ca0c264ade 100644 --- a/files/shaders/CMakeLists.txt +++ b/files/shaders/CMakeLists.txt @@ -16,6 +16,7 @@ set(SHADER_FILES lib/particle/occlusion.glsl lib/util/quickstep.glsl lib/util/coordinates.glsl + lib/util/distortion.glsl lib/core/fragment.glsl lib/core/fragment.h.glsl lib/core/fragment_multiview.glsl diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 70d9ef7ba7..ec5f5c8978 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -1,5 +1,5 @@ #version 120 -#pragma import_defines(FORCE_OPAQUE) +#pragma import_defines(FORCE_OPAQUE, DISTORTION) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -26,6 +26,8 @@ uniform sampler2D normalMap; varying vec2 normalMapUV; #endif +uniform sampler2D opaqueDepthTex; + varying float euclideanDepth; varying float linearDepth; @@ -38,9 +40,11 @@ uniform float alphaRef; uniform float emissiveMult; uniform float specStrength; uniform bool useTreeAnim; +uniform float distortionStrength; #include "lib/light/lighting.glsl" #include "lib/material/alpha.glsl" +#include "lib/util/distortion.glsl" #include "compatibility/vertexcolors.glsl" #include "compatibility/shadows_fragment.glsl" @@ -51,6 +55,15 @@ void main() { #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV); + +#if defined(DISTORTION) && DISTORTION + vec2 screenCoords = gl_FragCoord.xy / (screenRes * @distorionRTRatio); + gl_FragData[0].a = getDiffuseColor().a; + gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, texture2D(opaqueDepthTex, screenCoords).x); + + return; +#endif + gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV); #else gl_FragData[0] = vec4(1.0); diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 80de6b0e9d..043aa266d8 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -1,5 +1,5 @@ #version 120 -#pragma import_defines(FORCE_OPAQUE) +#pragma import_defines(FORCE_OPAQUE, DISTORTION) #if @useUBO #extension GL_ARB_uniform_buffer_object : require @@ -66,6 +66,7 @@ uniform vec2 screenRes; uniform float near; uniform float far; uniform float alphaRef; +uniform float distortionStrength; #define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) @@ -91,6 +92,7 @@ varying vec4 passTangent; #include "lib/light/lighting.glsl" #include "lib/material/parallax.glsl" #include "lib/material/alpha.glsl" +#include "lib/util/distortion.glsl" #include "fog.glsl" #include "vertexcolors.glsl" @@ -100,7 +102,6 @@ varying vec4 passTangent; #if @softParticles #include "lib/particle/soft.glsl" -uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; uniform float softFalloffDepth; @@ -112,6 +113,8 @@ uniform sampler2D orthoDepthMap; varying vec3 orthoDepthMapCoord; #endif +uniform sampler2D opaqueDepthTex; + void main() { #if @particleOcclusion @@ -133,8 +136,17 @@ void main() offset = getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), height, flipY); #endif +vec2 screenCoords = gl_FragCoord.xy / screenRes; + #if @diffuseMap gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV + offset); + +#if defined(DISTORTION) && DISTORTION + gl_FragData[0].a = getDiffuseColor().a; + gl_FragData[0] = applyDistortion(gl_FragData[0], distortionStrength, gl_FragCoord.z, texture2D(opaqueDepthTex, screenCoords / @distorionRTRatio).x); + return; +#endif + #if @diffuseParallax gl_FragData[0].a = 1.0; #else @@ -234,7 +246,6 @@ void main() gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far); - vec2 screenCoords = gl_FragCoord.xy / screenRes; #if !defined(FORCE_OPAQUE) && @softParticles gl_FragData[0].a *= calcSoftParticleFade( viewVec, diff --git a/files/shaders/lib/util/distortion.glsl b/files/shaders/lib/util/distortion.glsl new file mode 100644 index 0000000000..e0ccf6f2ec --- /dev/null +++ b/files/shaders/lib/util/distortion.glsl @@ -0,0 +1,32 @@ +#ifndef LIB_UTIL_DISTORTION +#define LIB_UTIL_DISTORTION + +vec4 applyDistortion(in vec4 color, in float strength, in float pixelDepth, in float sceneDepth) +{ + vec4 distortion = color; + float invOcclusion = 1.0; + + // TODO: Investigate me. Alpha-clipping is enabled for refraction for what seems an arbitrary threshold, even when + // there are no associated NIF properties. + if (distortion.a < 0.1) + discard; + + distortion.b = 0.0; + +#if @reverseZ + if (pixelDepth < sceneDepth) +#else + if (pixelDepth > sceneDepth) +#endif + { + invOcclusion = 0.0; + distortion.b = 1.0; + } + distortion.rg = color.rg * 2.0 - 1.0; + + distortion.rg *= invOcclusion * strength; + + return distortion; +} + +#endif diff --git a/files/shaders/lib/util/quickstep.glsl b/files/shaders/lib/util/quickstep.glsl index 2baa0a7430..e505886337 100644 --- a/files/shaders/lib/util/quickstep.glsl +++ b/files/shaders/lib/util/quickstep.glsl @@ -4,8 +4,8 @@ float quickstep(float x) { x = clamp(x, 0.0, 1.0); - x = 1.0 - x*x; - x = 1.0 - x*x; + x = 1.0 - x * x; + x = 1.0 - x * x; return x; } From 995f0e48657ee96be702cad3e9c26378ab19cad5 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 22 Dec 2023 14:43:34 +0100 Subject: [PATCH 096/231] Fix unused-but-set-variable warning components/esm3/inventorystate.cpp:18:18: warning: variable 'index' set but not used [-Wunused-but-set-variable] uint32_t index = 0; ^ --- components/esm3/inventorystate.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index ded2d1c33b..4175159ad5 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -15,7 +15,6 @@ namespace ESM void InventoryState::load(ESMReader& esm) { // obsolete - uint32_t index = 0; while (esm.isNextSub("IOBJ")) { esm.skipHT(); @@ -29,8 +28,6 @@ namespace ESM continue; mItems.push_back(state); - - ++index; } uint32_t itemsCount = 0; From 4067e10f3f92c64e17520adfb55e16fa0dc30c1c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 22 Dec 2023 19:24:02 +0100 Subject: [PATCH 097/231] Use gamepad cursor speed setting --- apps/openmw/mwinput/controllermanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwinput/controllermanager.cpp b/apps/openmw/mwinput/controllermanager.cpp index 6d82b1e226..8e6496ddf1 100644 --- a/apps/openmw/mwinput/controllermanager.cpp +++ b/apps/openmw/mwinput/controllermanager.cpp @@ -94,7 +94,7 @@ namespace MWInput // We keep track of our own mouse position, so that moving the mouse while in // game mode does not move the position of the GUI cursor float uiScale = MWBase::Environment::get().getWindowManager()->getScalingFactor(); - const float gamepadCursorSpeed = Settings::input().mEnableController; + const float gamepadCursorSpeed = Settings::input().mGamepadCursorSpeed; const float xMove = xAxis * dt * 1500.0f / uiScale * gamepadCursorSpeed; const float yMove = yAxis * dt * 1500.0f / uiScale * gamepadCursorSpeed; From 099c39ae87bc7f699a0ca7627d6bbb25e310460a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 21 Dec 2023 14:48:06 +0300 Subject: [PATCH 098/231] Use fallback weather ripple settings (bug #7292) --- CHANGELOG.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 1 + apps/openmw/mwrender/sky.cpp | 7 ++++++ apps/openmw/mwrender/sky.hpp | 3 +++ apps/openmw/mwrender/skyutil.hpp | 1 + apps/openmw/mwrender/water.cpp | 30 +++++++++++++++++------ apps/openmw/mwrender/water.hpp | 5 ++-- apps/openmw/mwworld/weather.cpp | 4 +++ apps/openmw/mwworld/weather.hpp | 2 ++ components/fallback/validate.cpp | 9 ++++--- files/shaders/compatibility/water.frag | 3 ++- 11 files changed, 52 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a92188e11..e5b84b7296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Bug #7229: Error marker loading failure is not handled Bug #7243: Supporting loading external files from VFS from esm files Bug #7284: "Your weapon has no effect." message doesn't always show when the player character attempts to attack + Bug #7292: Weather settings for disabling or enabling snow and rain ripples don't work Bug #7298: Water ripples from projectiles sometimes are not spawned Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index c175817fa8..bb67345ba7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -847,6 +847,7 @@ namespace MWRender float rainIntensity = mSky->getPrecipitationAlpha(); mWater->setRainIntensity(rainIntensity); + mWater->setRainRipplesEnabled(mSky->getRainRipplesEnabled()); mWater->update(dt, paused); if (!paused) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index a38030738a..87c6d1f2e7 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -257,6 +257,7 @@ namespace MWRender , mRainMaxHeight(0.f) , mRainEntranceSpeed(1.f) , mRainMaxRaindrops(0) + , mRipples(false) , mWindSpeed(0.f) , mBaseWindSpeed(0.f) , mEnabled(true) @@ -516,6 +517,11 @@ namespace MWRender return mRainNode != nullptr; } + bool SkyManager::getRainRipplesEnabled() const + { + return mRipples; + } + float SkyManager::getPrecipitationAlpha() const { if (mEnabled && !mIsStorm && (hasRain() || mParticleNode)) @@ -630,6 +636,7 @@ namespace MWRender mRainMinHeight = weather.mRainMinHeight; mRainMaxHeight = weather.mRainMaxHeight; mRainSpeed = weather.mRainSpeed; + mRipples = weather.mRipples; mWindSpeed = weather.mWindSpeed; mBaseWindSpeed = weather.mBaseWindSpeed; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 75c6a10a50..9e4801ad7d 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -79,6 +79,8 @@ namespace MWRender bool hasRain() const; + bool getRainRipplesEnabled() const; + float getPrecipitationAlpha() const; void setRainSpeed(float speed); @@ -194,6 +196,7 @@ namespace MWRender float mRainMaxHeight; float mRainEntranceSpeed; int mRainMaxRaindrops; + bool mRipples; float mWindSpeed; float mBaseWindSpeed; diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index 1018724595..9e279fb5c2 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -77,6 +77,7 @@ namespace MWRender float mRainSpeed; float mRainEntranceSpeed; int mRainMaxRaindrops; + bool mRipples; osg::Vec3f mStormDirection; osg::Vec3f mNextStormDirection; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 85df70adfc..553bdeeaaa 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -205,21 +205,25 @@ namespace MWRender } }; - class RainIntensityUpdater : public SceneUtil::StateSetUpdater + class RainSettingsUpdater : public SceneUtil::StateSetUpdater { public: - RainIntensityUpdater() + RainSettingsUpdater() : mRainIntensity(0.f) + , mEnableRipples(false) { } void setRainIntensity(float rainIntensity) { mRainIntensity = rainIntensity; } + void setRipplesEnabled(bool enableRipples) { mEnableRipples = enableRipples; } protected: void setDefaults(osg::StateSet* stateset) override { osg::ref_ptr rainIntensityUniform = new osg::Uniform("rainIntensity", 0.0f); stateset->addUniform(rainIntensityUniform.get()); + osg::ref_ptr enableRainRipplesUniform = new osg::Uniform("enableRainRipples", false); + stateset->addUniform(enableRainRipplesUniform.get()); } void apply(osg::StateSet* stateset, osg::NodeVisitor* /*nv*/) override @@ -227,10 +231,14 @@ namespace MWRender osg::ref_ptr rainIntensityUniform = stateset->getUniform("rainIntensity"); if (rainIntensityUniform != nullptr) rainIntensityUniform->set(mRainIntensity); + osg::ref_ptr enableRainRipplesUniform = stateset->getUniform("enableRainRipples"); + if (enableRainRipplesUniform != nullptr) + enableRainRipplesUniform->set(mEnableRipples); } private: float mRainIntensity; + bool mEnableRipples; }; class Refraction : public SceneUtil::RTTNode @@ -430,7 +438,7 @@ namespace MWRender Water::Water(osg::Group* parent, osg::Group* sceneRoot, Resource::ResourceSystem* resourceSystem, osgUtil::IncrementalCompileOperation* ico) - : mRainIntensityUpdater(nullptr) + : mRainSettingsUpdater(nullptr) , mParent(parent) , mSceneRoot(sceneRoot) , mResourceSystem(resourceSystem) @@ -579,7 +587,7 @@ namespace MWRender node->setStateSet(stateset); node->setUpdateCallback(nullptr); - mRainIntensityUpdater = nullptr; + mRainSettingsUpdater = nullptr; // Add animated textures std::vector> textures; @@ -711,8 +719,8 @@ namespace MWRender normalMap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); normalMap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); - mRainIntensityUpdater = new RainIntensityUpdater(); - node->setUpdateCallback(mRainIntensityUpdater); + mRainSettingsUpdater = new RainSettingsUpdater(); + node->setUpdateCallback(mRainSettingsUpdater); mShaderWaterStateSetUpdater = new ShaderWaterStateSetUpdater(this, mReflection, mRefraction, mRipples, std::move(program), normalMap); @@ -801,8 +809,14 @@ namespace MWRender void Water::setRainIntensity(float rainIntensity) { - if (mRainIntensityUpdater) - mRainIntensityUpdater->setRainIntensity(rainIntensity); + if (mRainSettingsUpdater) + mRainSettingsUpdater->setRainIntensity(rainIntensity); + } + + void Water::setRainRipplesEnabled(bool enableRipples) + { + if (mRainSettingsUpdater) + mRainSettingsUpdater->setRipplesEnabled(enableRipples); } void Water::update(float dt, bool paused) diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 0204cb4303..d3241bf3a7 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -46,13 +46,13 @@ namespace MWRender class Refraction; class Reflection; class RippleSimulation; - class RainIntensityUpdater; + class RainSettingsUpdater; class Ripples; /// Water rendering class Water { - osg::ref_ptr mRainIntensityUpdater; + osg::ref_ptr mRainSettingsUpdater; osg::ref_ptr mParent; osg::ref_ptr mSceneRoot; @@ -113,6 +113,7 @@ namespace MWRender void changeCell(const MWWorld::CellStore* store); void setHeight(const float height); void setRainIntensity(const float rainIntensity); + void setRainRipplesEnabled(bool enableRipples); void update(float dt, bool paused); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index aa75730b40..5e2d7d3fec 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -175,6 +175,7 @@ namespace MWWorld , mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max")) , mParticleEffect(particleEffect) , mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") + , mRipples(Fallback::Map::getBool("Weather_" + name + "_Ripples")) , mStormDirection(Weather::defaultDirection()) , mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent")) , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) @@ -1129,6 +1130,7 @@ namespace MWWorld mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; + mResult.mRipples = current.mRipples; mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; @@ -1241,6 +1243,7 @@ namespace MWWorld mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; + mResult.mRipples = current.mRipples; } else { @@ -1257,6 +1260,7 @@ namespace MWWorld mResult.mRainMinHeight = other.mRainMinHeight; mResult.mRainMaxHeight = other.mRainMaxHeight; mResult.mRainMaxRaindrops = other.mRainMaxRaindrops; + mResult.mRipples = other.mRipples; } } } diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 65f926c096..d40f7030c8 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -191,6 +191,8 @@ namespace MWWorld std::string mRainEffect; + bool mRipples; + osg::Vec3f mStormDirection; float mCloudsMaximumPercent; diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 300d2b5dd1..2a6d1817c9 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -11,7 +11,10 @@ static const std::set allowedKeysInt = { "LightAttenuation_Lin "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip", "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", "Weather_Overcast_Using_Precip", "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip", - "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip" }; + "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", + "Weather_Clear_Ripples", "Weather_Cloudy_Ripples", "Weather_Foggy_Ripples", "Weather_Overcast_Ripples", + "Weather_Rain_Ripples", "Weather_Thunderstorm_Ripples", "Weather_Ashstorm_Ripples", "Weather_Blight_Ripples", + "Weather_Snow_Ripples", "Weather_Blizzard_Ripples" }; static const std::set allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB", "Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB", @@ -160,7 +163,7 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "Weather_Rain_Ambient_Sunrise_Color", "Weather_Rain_Ambient_Sunset_Color", "Weather_Rain_Cloud_Texture", "Weather_Rain_Fog_Day_Color", "Weather_Rain_Fog_Night_Color", "Weather_Rain_Fog_Sunrise_Color", "Weather_Rain_Fog_Sunset_Color", "Weather_Rain_Rain_Loop_Sound_ID", "Weather_Rain_Ripple_Radius", - "Weather_Rain_Ripples", "Weather_Rain_Ripple_Scale", "Weather_Rain_Ripple_Speed", "Weather_Rain_Ripples_Per_Drop", + "Weather_Rain_Ripple_Scale", "Weather_Rain_Ripple_Speed", "Weather_Rain_Ripples_Per_Drop", "Weather_Rain_Sky_Day_Color", "Weather_Rain_Sky_Night_Color", "Weather_Rain_Sky_Sunrise_Color", "Weather_Rain_Sky_Sunset_Color", "Weather_Rain_Sun_Day_Color", "Weather_Rain_Sun_Disc_Sunset_Color", "Weather_Rain_Sun_Night_Color", "Weather_Rain_Sun_Sunrise_Color", "Weather_Rain_Sun_Sunset_Color", @@ -168,7 +171,7 @@ static const std::set allowedKeysNonNumeric = { "Blood_Model_0 "Weather_Snow_Ambient_Sunrise_Color", "Weather_Snow_Ambient_Sunset_Color", "Weather_Snow_Cloud_Texture", "Weather_Snow_Fog_Day_Color", "Weather_Snow_Fog_Night_Color", "Weather_Snow_Fog_Sunrise_Color", "Weather_Snow_Fog_Sunset_Color", "Weather_Snow_Gravity_Scale", "Weather_Snow_High_Kill", "Weather_Snow_Low_Kill", - "Weather_Snow_Max_Snowflakes", "Weather_Snow_Ripple_Radius", "Weather_Snow_Ripples", "Weather_Snow_Ripple_Scale", + "Weather_Snow_Max_Snowflakes", "Weather_Snow_Ripple_Radius", "Weather_Snow_Ripple_Scale", "Weather_Snow_Ripple_Speed", "Weather_Snow_Ripples_Per_Flake", "Weather_Snow_Sky_Day_Color", "Weather_Snow_Sky_Night_Color", "Weather_Snow_Sky_Sunrise_Color", "Weather_Snow_Sky_Sunset_Color", "Weather_Snow_Snow_Diameter", "Weather_Snow_Snow_Entrance_Speed", "Weather_Snow_Snow_Height_Max", diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 987dab10a6..7ae1720185 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -81,6 +81,7 @@ uniform float near; uniform float far; uniform float rainIntensity; +uniform bool enableRainRipples; uniform vec2 screenRes; @@ -113,7 +114,7 @@ void main(void) vec4 rainRipple; - if (rainIntensity > 0.01) + if (rainIntensity > 0.01 && enableRainRipples) rainRipple = rainCombined(position.xy/1000.0, waterTimer) * clamp(rainIntensity, 0.0, 1.0); else rainRipple = vec4(0.0); From 9d357e0b04d09803f3ac77c5bdb699df5a3dd8af Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Dec 2023 14:23:12 +0100 Subject: [PATCH 099/231] Fix script to install luadocumentor 78577b255d19a1f4f4f539662e00357936b73c33 is a part of https://gitlab.com/ptmikheev/openmw-luadocumentor.git repo. Before doing checkout need to change the directory. --- docs/source/install_luadocumentor_in_docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/install_luadocumentor_in_docker.sh b/docs/source/install_luadocumentor_in_docker.sh index 6d24f3e9f3..fd7fcdb0e6 100755 --- a/docs/source/install_luadocumentor_in_docker.sh +++ b/docs/source/install_luadocumentor_in_docker.sh @@ -30,8 +30,8 @@ PATH=$PATH:~/luarocks/bin echo "Install openmwluadocumentor" git clone https://gitlab.com/ptmikheev/openmw-luadocumentor.git -git checkout 78577b255d19a1f4f4f539662e00357936b73c33 cd openmw-luadocumentor +git checkout 78577b255d19a1f4f4f539662e00357936b73c33 luarocks make luarocks/openmwluadocumentor-0.2.0-1.rockspec cd ~ rm -r openmw-luadocumentor From af40d7ce8077d88a50109cb8f5258c04d9c74506 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 23 Dec 2023 15:50:36 +0100 Subject: [PATCH 100/231] End pursue package if the target doens't have a bounty --- apps/openmw/mwmechanics/actors.cpp | 2 +- apps/openmw/mwmechanics/aipursue.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 12282a515d..e0baac0764 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1152,7 +1152,7 @@ namespace MWMechanics if (npcStats.getCrimeId() != -1) { // if you've paid for your crimes and I haven't noticed - if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId() || playerStats.getBounty() <= 0) + if (npcStats.getCrimeId() <= world->getPlayer().getCrimeId()) { // Calm witness down if (ptr.getClass().isClass(ptr, "Guard")) diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 2ae4ce5def..699e96cd32 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -12,6 +12,7 @@ #include "actorutil.hpp" #include "character.hpp" #include "creaturestats.hpp" +#include "npcstats.hpp" namespace MWMechanics { @@ -47,6 +48,9 @@ namespace MWMechanics if (target.getClass().getCreatureStats(target).isDead()) return true; + if (target.getClass().getNpcStats(target).getBounty() <= 0) + return true; + actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); // Set the target destination From 5c10727380006d6d8c29e5e394d1d38fe794c3f4 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:06:16 -0600 Subject: [PATCH 101/231] Feat(CS): Add definition files for selection group record type --- components/CMakeLists.txt | 2 +- components/esm3/selectiongroup.cpp | 38 ++++++++++++++++++++++++++++++ components/esm3/selectiongroup.hpp | 34 ++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 components/esm3/selectiongroup.cpp create mode 100644 components/esm3/selectiongroup.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f61a5bd0b2..ca6b644d15 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -166,7 +166,7 @@ add_component_dir (esm3 inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache - infoorder timestamp formatversion landrecorddata + infoorder timestamp formatversion landrecorddata selectiongroup ) add_component_dir (esmterrain diff --git a/components/esm3/selectiongroup.cpp b/components/esm3/selectiongroup.cpp new file mode 100644 index 0000000000..6b819a4bbc --- /dev/null +++ b/components/esm3/selectiongroup.cpp @@ -0,0 +1,38 @@ +#include "selectiongroup.hpp" + +#include "esmreader.hpp" +#include "esmwriter.hpp" + +namespace ESM +{ + void SelectionGroup::load(ESMReader& esm, bool& isDeleted) + { + + while (esm.hasMoreSubs()) + { + esm.getSubName(); + switch (esm.retSubName().toInt()) + { + case fourCC("SELC"): + mId = esm.getRefId(); + break; + case fourCC("SELI"): + selectedInstances.push_back(esm.getRefId().getRefIdString()); + break; + default: + esm.fail("Unknown subrecord"); + break; + } + } + } + + void SelectionGroup::save(ESMWriter& esm, bool isDeleted) const + { + esm.writeHNCRefId("SELC", mId); + for (std::string id : selectedInstances) + esm.writeHNCString("SELI", id); + } + + void SelectionGroup::blank() {} + +} diff --git a/components/esm3/selectiongroup.hpp b/components/esm3/selectiongroup.hpp new file mode 100644 index 0000000000..021f3c95d5 --- /dev/null +++ b/components/esm3/selectiongroup.hpp @@ -0,0 +1,34 @@ +#ifndef COMPONENTS_ESM_SELECTIONGROUP_H +#define COMPONENTS_ESM_SELECTIONGROUP_H + +#include + +#include "components/esm/defs.hpp" +#include "components/esm/refid.hpp" + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + struct SelectionGroup + { + constexpr static RecNameInts sRecordId = REC_SELG; + + static constexpr std::string_view getRecordType() { return "SelectionGroup"; } + + uint32_t mRecordFlags = 0; + + RefId mId; + + std::vector selectedInstances; + + void load(ESMReader& esm, bool& isDeleted); + void save(ESMWriter& esm, bool isDeleted = false) const; + + /// Set record to default state (does not touch the ID). + void blank(); + }; +} + +#endif From 24443e00bffdbdb16d98e5427c3ac6a668616658 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:08:34 -0600 Subject: [PATCH 102/231] Feat(CS): Implement selection groups into data model --- apps/opencs/model/doc/saving.cpp | 4 ++++ apps/opencs/model/world/columnimp.cpp | 31 +++++++++++++++++++++++++ apps/opencs/model/world/columnimp.hpp | 12 ++++++++++ apps/opencs/model/world/columns.hpp | 2 ++ apps/opencs/model/world/data.cpp | 26 +++++++++++++++++++++ apps/opencs/model/world/data.hpp | 6 +++++ apps/opencs/model/world/universalid.cpp | 1 + apps/opencs/model/world/universalid.hpp | 1 + components/esm/defs.hpp | 2 ++ 9 files changed, 85 insertions(+) diff --git a/apps/opencs/model/doc/saving.cpp b/apps/opencs/model/doc/saving.cpp index b2e4d4649a..ed785c38fe 100644 --- a/apps/opencs/model/doc/saving.cpp +++ b/apps/opencs/model/doc/saving.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "../world/data.hpp" #include "../world/idcollection.hpp" @@ -52,6 +53,9 @@ CSMDoc::Saving::Saving(Document& document, const std::filesystem::path& projectP appendStage(new WriteCollectionStage>( mDocument.getData().getScripts(), mState, CSMWorld::Scope_Project)); + appendStage(new WriteCollectionStage>( + mDocument.getData().getSelectionGroups(), mState, CSMWorld::Scope_Project)); + appendStage(new CloseSaveStage(mState)); // save content file diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index 981ec5278d..89190023c6 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -333,6 +333,37 @@ namespace CSMWorld return true; } + SelectionGroupColumn::SelectionGroupColumn() + : Column(Columns::ColumnId_SelectionGroupObjects, ColumnBase::Display_None) + { + } + + QVariant SelectionGroupColumn::get(const Record& record) const + { + QVariant data; + QStringList selectionInfo; + const std::vector& instances = record.get().selectedInstances; + + for (std::string instance : instances) + selectionInfo << QString::fromStdString(instance); + data.setValue(selectionInfo); + + return data; + } + + void SelectionGroupColumn::set(Record& record, const QVariant& data) + { + ESM::SelectionGroup record2 = record.get(); + for (const auto& item : data.toStringList()) + record2.selectedInstances.push_back(item.toStdString()); + record.setModified(record2); + } + + bool SelectionGroupColumn::isEditable() const + { + return false; + } + std::optional getSkillIndex(std::string_view value) { int index = ESM::Skill::refIdToIndex(ESM::RefId::stringRefId(value)); diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index da805d5c6d..74b81cebc1 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -2391,6 +2392,17 @@ namespace CSMWorld void set(Record& record, const QVariant& data) override; bool isEditable() const override; }; + + struct SelectionGroupColumn : public Column + { + SelectionGroupColumn(); + + QVariant get(const Record& record) const override; + + void set(Record& record, const QVariant& data) override; + + bool isEditable() const override; + }; } // This is required to access the type as a QVariant. diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index 92f41a2f20..dff8b2d7dd 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -347,6 +347,8 @@ namespace CSMWorld ColumnId_LevelledCreatureId = 315, + ColumnId_SelectionGroupObjects = 316, + // Allocated to a separate value range, so we don't get a collision should we ever need // to extend the number of use values. ColumnId_UseValue1 = 0x10000, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index e8fd138ab4..9b137a6602 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -620,6 +620,10 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mDebugProfiles.addColumn(new DescriptionColumn); mDebugProfiles.addColumn(new ScriptColumn(ScriptColumn::Type_Lines)); + mSelectionGroups.addColumn(new StringIdColumn); + mSelectionGroups.addColumn(new RecordStateColumn); + mSelectionGroups.addColumn(new SelectionGroupColumn); + mMetaData.appendBlankRecord(ESM::RefId::stringRefId("sys::meta")); mMetaData.addColumn(new StringIdColumn(true)); @@ -664,6 +668,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Textures)), UniversalId::Type_Texture); addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Videos)), UniversalId::Type_Video); addModel(new IdTable(&mMetaData), UniversalId::Type_MetaData); + addModel(new IdTable(&mSelectionGroups), UniversalId::Type_SelectionGroup); mActorAdapter = std::make_unique(*this); @@ -908,6 +913,16 @@ CSMWorld::IdCollection& CSMWorld::Data::getDebugProfiles() return mDebugProfiles; } +CSMWorld::IdCollection& CSMWorld::Data::getSelectionGroups() +{ + return mSelectionGroups; +} + +const CSMWorld::IdCollection& CSMWorld::Data::getSelectionGroups() const +{ + return mSelectionGroups; +} + const CSMWorld::IdCollection& CSMWorld::Data::getLand() const { return mLand; @@ -1369,6 +1384,17 @@ bool CSMWorld::Data::continueLoading(CSMDoc::Messages& messages) mDebugProfiles.load(*mReader, mBase); break; + case ESM::REC_SELG: + + if (!mProject) + { + unhandledRecord = true; + break; + } + + mSelectionGroups.load(*mReader, mBase); + break; + default: unhandledRecord = true; diff --git a/apps/opencs/model/world/data.hpp b/apps/opencs/model/world/data.hpp index 1b63986eac..8e01452f3b 100644 --- a/apps/opencs/model/world/data.hpp +++ b/apps/opencs/model/world/data.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -105,6 +106,7 @@ namespace CSMWorld IdCollection mBodyParts; IdCollection mMagicEffects; IdCollection mDebugProfiles; + IdCollection mSelectionGroups; IdCollection mSoundGens; IdCollection mStartScripts; NestedInfoCollection mTopicInfos; @@ -251,6 +253,10 @@ namespace CSMWorld IdCollection& getDebugProfiles(); + const IdCollection& getSelectionGroups() const; + + IdCollection& getSelectionGroups(); + const IdCollection& getLand() const; IdCollection& getLand(); diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index dec533b015..0a6b9c8e7c 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -68,6 +68,7 @@ namespace ":./resources-video" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", ":./debug-profile.png" }, + { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SelectionGroup, "Selection Groups", "" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":./run-log.png" }, { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", ":./sound-generator.png" }, diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 2d3385bcb4..6bee62cf93 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -133,6 +133,7 @@ namespace CSMWorld Type_LandTexture, Type_Pathgrids, Type_Pathgrid, + Type_SelectionGroup, Type_StartScripts, Type_StartScript, Type_Search, diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index 6254830f63..cbc70582c0 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -170,6 +170,8 @@ namespace ESM // format 1 REC_FILT = esm3Recname("FILT"), REC_DBGP = esm3Recname("DBGP"), ///< only used in project files + REC_SELG = esm3Recname("SELG"), + REC_LUAL = esm3Recname("LUAL"), // LuaScriptsCfg (only in omwgame or omwaddon) // format 16 - Lua scripts in saved games From 0ec6dcbf1fd555efb23accbb80cca718f2ea9d5f Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:09:08 -0600 Subject: [PATCH 103/231] Feat(Settings): Implement shortcuts for hiding refs & selection groups --- apps/opencs/model/prefs/state.cpp | 23 +++++++++++++++++++++++ apps/opencs/model/prefs/values.hpp | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index d44c9de888..3386530429 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -413,6 +413,29 @@ void CSMPrefs::State::declare() declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); declareShortcut("scene-duplicate", "Duplicate Instance", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); + declareShortcut("scene-clear-selection", "Clear Selection", QKeySequence(Qt::Key_Space)); + declareShortcut("scene-unhide-all", "Unhide All Objects", QKeySequence(Qt::AltModifier | Qt::Key_H)); + declareShortcut("scene-toggle-visibility", "Toggle Selection Visibility", QKeySequence(Qt::Key_H)); + declareShortcut("scene-group-1", "Select Group 1", QKeySequence(Qt::Key_1)); + declareShortcut("scene-save-1", "Save Group 1", QKeySequence(Qt::ControlModifier | Qt::Key_1)); + declareShortcut("scene-group-2", "Select Group 2", QKeySequence(Qt::Key_2)); + declareShortcut("scene-save-2", "Save Group 2", QKeySequence(Qt::ControlModifier | Qt::Key_2)); + declareShortcut("scene-group-3", "Select Group 3", QKeySequence(Qt::Key_3)); + declareShortcut("scene-save-3", "Save Group 3", QKeySequence(Qt::ControlModifier | Qt::Key_3)); + declareShortcut("scene-group-4", "Select Group 4", QKeySequence(Qt::Key_4)); + declareShortcut("scene-save-4", "Save Group 4", QKeySequence(Qt::ControlModifier | Qt::Key_4)); + declareShortcut("scene-group-5", "Selection Group 5", QKeySequence(Qt::Key_5)); + declareShortcut("scene-save-5", "Save Group 5", QKeySequence(Qt::ControlModifier | Qt::Key_5)); + declareShortcut("scene-group-6", "Selection Group 6", QKeySequence(Qt::Key_6)); + declareShortcut("scene-save-6", "Save Group 6", QKeySequence(Qt::ControlModifier | Qt::Key_6)); + declareShortcut("scene-group-7", "Selection Group 7", QKeySequence(Qt::Key_7)); + declareShortcut("scene-save-7", "Save Group 7", QKeySequence(Qt::ControlModifier | Qt::Key_7)); + declareShortcut("scene-group-8", "Selection Group 8", QKeySequence(Qt::Key_8)); + declareShortcut("scene-save-8", "Save Group 8", QKeySequence(Qt::ControlModifier | Qt::Key_8)); + declareShortcut("scene-group-9", "Selection Group 9", QKeySequence(Qt::Key_9)); + declareShortcut("scene-save-9", "Save Group 9", QKeySequence(Qt::ControlModifier | Qt::Key_9)); + declareShortcut("scene-group-0", "Selection Group 10", QKeySequence(Qt::Key_0)); + declareShortcut("scene-save-0", "Save Group 10", QKeySequence(Qt::ControlModifier | Qt::Key_0)); declareSubcategory("1st/Free Camera"); declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W)); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 4683258e57..80d2f8f182 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -477,6 +477,29 @@ namespace CSMPrefs Settings::SettingValue mSceneEditAbort{ mIndex, sName, "scene-edit-abort", "Escape" }; Settings::SettingValue mSceneFocusToolbar{ mIndex, sName, "scene-focus-toolbar", "T" }; Settings::SettingValue mSceneRenderStats{ mIndex, sName, "scene-render-stats", "F3" }; + Settings::SettingValue mSceneClearSelection{ mIndex, sName, "scene-clear-selection", "Space" }; + Settings::SettingValue mSceneUnhideAll{ mIndex, sName, "scene-unhide-all", "Alt+H" }; + Settings::SettingValue mSceneToggleHidden{ mIndex, sName, "scene-toggle-visibility", "H" }; + Settings::SettingValue mSceneSelectGroup1{ mIndex, sName, "scene-group-1", "1" }; + Settings::SettingValue mSceneSaveGroup1{ mIndex, sName, "scene-save-1", "Ctrl+1" }; + Settings::SettingValue mSceneSelectGroup2{ mIndex, sName, "scene-group-2", "2" }; + Settings::SettingValue mSceneSaveGroup2{ mIndex, sName, "scene-save-2", "Ctrl+2" }; + Settings::SettingValue mSceneSelectGroup3{ mIndex, sName, "scene-group-3", "3" }; + Settings::SettingValue mSceneSaveGroup3{ mIndex, sName, "scene-save-3", "Ctrl+3" }; + Settings::SettingValue mSceneSelectGroup4{ mIndex, sName, "scene-group-4", "4" }; + Settings::SettingValue mSceneSaveGroup4{ mIndex, sName, "scene-save-4", "Ctrl+4" }; + Settings::SettingValue mSceneSelectGroup5{ mIndex, sName, "scene-group-5", "5" }; + Settings::SettingValue mSceneSaveGroup5{ mIndex, sName, "scene-save-5", "Ctrl+5" }; + Settings::SettingValue mSceneSelectGroup6{ mIndex, sName, "scene-group-6", "6" }; + Settings::SettingValue mSceneSaveGroup6{ mIndex, sName, "scene-save-6", "Ctrl+6" }; + Settings::SettingValue mSceneSelectGroup7{ mIndex, sName, "scene-group-7", "7" }; + Settings::SettingValue mSceneSaveGroup7{ mIndex, sName, "scene-save-7", "Ctrl+7" }; + Settings::SettingValue mSceneSelectGroup8{ mIndex, sName, "scene-group-8", "8" }; + Settings::SettingValue mSceneSaveGroup8{ mIndex, sName, "scene-save-8", "Ctrl+8" }; + Settings::SettingValue mSceneSelectGroup9{ mIndex, sName, "scene-group-9", "9" }; + Settings::SettingValue mSceneSaveGroup9{ mIndex, sName, "scene-save-9", "Ctrl+9" }; + Settings::SettingValue mSceneSelectGroup10{ mIndex, sName, "scene-group-0", "0" }; + Settings::SettingValue mSceneSaveGroup10{ mIndex, sName, "scene-save-0", "Ctrl+0" }; Settings::SettingValue mFreeForward{ mIndex, sName, "free-forward", "W" }; Settings::SettingValue mFreeBackward{ mIndex, sName, "free-backward", "S" }; Settings::SettingValue mFreeLeft{ mIndex, sName, "free-left", "A" }; From cf098033b9bdfe041df87c80f9b555846cdc684c Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:09:32 -0600 Subject: [PATCH 104/231] Feat(Mask.hpp): Add mask for hidden objects --- apps/opencs/view/render/mask.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/view/render/mask.hpp b/apps/opencs/view/render/mask.hpp index 818be8b228..7f767e19ac 100644 --- a/apps/opencs/view/render/mask.hpp +++ b/apps/opencs/view/render/mask.hpp @@ -11,6 +11,7 @@ namespace CSVRender enum Mask : unsigned int { // elements that are part of the actual scene + Mask_Hidden = 0x0, Mask_Reference = 0x2, Mask_Pathgrid = 0x4, Mask_Water = 0x8, From 9d155afc15508fcccaec055f52c208d1845651ad Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:10:55 -0600 Subject: [PATCH 105/231] Feat(worldspacewidget.hpp): Add virtual decs for selection functions --- apps/opencs/view/render/worldspacewidget.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 442f4922f0..b7391c1691 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -201,6 +201,12 @@ namespace CSVRender virtual std::vector> getSelection(unsigned int elementMask) const = 0; + virtual void selectGroup(const std::vector) const = 0; + + virtual void hideGroup(const std::vector) const = 0; + + virtual void unhideAll() const = 0; + virtual std::vector> getEdited(unsigned int elementMask) const = 0; virtual void setSubMode(int subMode, unsigned int elementMask) = 0; @@ -300,6 +306,8 @@ namespace CSVRender void speedMode(bool activate); + void toggleHiddenInstances(); + protected slots: void elementSelectionChanged(); From 94eadd436d7b9710f476b14d60bb8a9b1e2c532f Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:11:23 -0600 Subject: [PATCH 106/231] Feat(worldspacewidget.cpp): Implement shortcut for visibility switching & unhiding all instances --- apps/opencs/view/render/worldspacewidget.cpp | 24 ++++++++++++++++++++ apps/opencs/view/render/worldspacewidget.hpp | 2 -- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 6911f5f043..3c7dc34efb 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -50,6 +50,7 @@ #include "cameracontroller.hpp" #include "instancemode.hpp" +#include "mask.hpp" #include "object.hpp" #include "pathgridmode.hpp" @@ -135,6 +136,12 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); connect(abortShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::abortDrag); + connect(new CSMPrefs::Shortcut("scene-toggle-visibility", this), qOverload<>(&CSMPrefs::Shortcut::activated), this, + &WorldspaceWidget::toggleHiddenInstances); + + connect(new CSMPrefs::Shortcut("scene-unhide-all", this), qOverload<>(&CSMPrefs::Shortcut::activated), this, + &WorldspaceWidget::unhideAll); + mInConstructor = false; } @@ -740,6 +747,23 @@ void CSVRender::WorldspaceWidget::speedMode(bool activate) mSpeedMode = activate; } +void CSVRender::WorldspaceWidget::toggleHiddenInstances() +{ + const std::vector> selection = getSelection(Mask_Reference); + + if (selection.empty()) + return; + + const CSVRender::ObjectTag* firstSelection = dynamic_cast(selection.begin()->get()); + + const CSVRender::Mask firstMask + = firstSelection->mObject->getRootNode()->getNodeMask() == Mask_Hidden ? Mask_Reference : Mask_Hidden; + + for (const auto& object : selection) + if (const auto objectTag = dynamic_cast(object.get())) + objectTag->mObject->getRootNode()->setNodeMask(firstMask); +} + void CSVRender::WorldspaceWidget::handleInteraction(InteractionType type, bool activate) { if (activate) diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index b7391c1691..a6d87440f1 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -203,8 +203,6 @@ namespace CSVRender virtual void selectGroup(const std::vector) const = 0; - virtual void hideGroup(const std::vector) const = 0; - virtual void unhideAll() const = 0; virtual std::vector> getEdited(unsigned int elementMask) const = 0; From 8edc1484183d10e948bfe9577bc3e3b7571dd2d9 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:20:35 -0600 Subject: [PATCH 107/231] Feat(CS): Implement select/unhide functions into interior & exterior worldspace widgets --- apps/opencs/view/render/pagedworldspacewidget.cpp | 12 ++++++++++++ apps/opencs/view/render/pagedworldspacewidget.hpp | 4 ++++ apps/opencs/view/render/unpagedworldspacewidget.cpp | 10 ++++++++++ apps/opencs/view/render/unpagedworldspacewidget.hpp | 4 ++++ 4 files changed, 30 insertions(+) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 00d519ecc8..fab706e3ca 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -875,6 +875,18 @@ std::vector> CSVRender::PagedWorldspaceWidget:: return result; } +void CSVRender::PagedWorldspaceWidget::selectGroup(std::vector group) const +{ + for (const auto& [_, cell] : mCells) + cell->selectFromGroup(group); +} + +void CSVRender::PagedWorldspaceWidget::unhideAll() const +{ + for (const auto& [_, cell] : mCells) + cell->unhideAll(); +} + std::vector> CSVRender::PagedWorldspaceWidget::getEdited( unsigned int elementMask) const { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 9ba8911c7e..3d2ab97e89 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -163,6 +163,10 @@ namespace CSVRender std::vector> getSelection(unsigned int elementMask) const override; + void selectGroup(const std::vector group) const override; + + void unhideAll() const override; + std::vector> getEdited(unsigned int elementMask) const override; void setSubMode(int subMode, unsigned int elementMask) override; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index fee608b200..ea99294c28 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -199,6 +199,16 @@ std::vector> CSVRender::UnpagedWorldspaceWidget return mCell->getSelection(elementMask); } +void CSVRender::UnpagedWorldspaceWidget::selectGroup(const std::vector group) const +{ + mCell->selectFromGroup(group); +} + +void CSVRender::UnpagedWorldspaceWidget::unhideAll() const +{ + mCell->unhideAll(); +} + std::vector> CSVRender::UnpagedWorldspaceWidget::getEdited( unsigned int elementMask) const { diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 10446354e9..22a0475fe2 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -93,6 +93,10 @@ namespace CSVRender std::vector> getSelection(unsigned int elementMask) const override; + void selectGroup(const std::vector group) const override; + + void unhideAll() const override; + std::vector> getEdited(unsigned int elementMask) const override; void setSubMode(int subMode, unsigned int elementMask) override; From f287914f1eb256a2f5e12aa24af62c559cdff13a Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:22:36 -0600 Subject: [PATCH 108/231] Feat(cell.cpp): Add select/unhide functions in cell.cpp --- apps/opencs/view/render/cell.cpp | 19 +++++++++++++++++++ apps/opencs/view/render/cell.hpp | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index c74782f2d1..a1f3c0c41f 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -612,6 +612,25 @@ osg::ref_ptr CSVRender::Cell::getSnapTarget(unsigned int ele return result; } +void CSVRender::Cell::selectFromGroup(const std::vector group) +{ + for (const auto& [_, object] : mObjects) + if (auto found = std::ranges::find_if(group.begin(), group.end(), + [&object](const std::string& id) { return object->getReferenceId() == id; }); + found != group.end()) + object->setSelected(true, osg::Vec4f(1, 0, 1, 1)); +} + +void CSVRender::Cell::unhideAll() +{ + for (const auto& [_, object] : mObjects) + { + osg::ref_ptr rootNode = object->getRootNode(); + if (rootNode->getNodeMask() == Mask_Hidden) + rootNode->setNodeMask(Mask_Reference); + } +} + std::vector> CSVRender::Cell::getSelection(unsigned int elementMask) const { std::vector> result; diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index cf50604c29..52e01c631a 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -148,6 +148,10 @@ namespace CSVRender // already selected void selectAllWithSameParentId(int elementMask); + void selectFromGroup(const std::vector group); + + void unhideAll(); + void handleSelectDrag(Object* object, DragMode dragMode); void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode); From 23e75bed8f7bd327e095b2127c770f5b03d4fa88 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:22:56 -0600 Subject: [PATCH 109/231] Feat(object.cpp): Make object outline an optional argument when selecting it --- apps/opencs/view/render/object.cpp | 5 ++--- apps/opencs/view/render/object.hpp | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 7782ce36c9..0e114ac06e 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include @@ -485,7 +484,7 @@ CSVRender::Object::~Object() mParentNode->removeChild(mRootNode); } -void CSVRender::Object::setSelected(bool selected) +void CSVRender::Object::setSelected(bool selected, osg::Vec4f color) { mSelected = selected; @@ -499,7 +498,7 @@ void CSVRender::Object::setSelected(bool selected) mRootNode->removeChild(mBaseNode); if (selected) { - mOutline->setWireframeColor(osg::Vec4f(1, 1, 1, 1)); + mOutline->setWireframeColor(color); mOutline->addChild(mBaseNode); mRootNode->addChild(mOutline); } diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 436c410c84..9b18b99561 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -138,7 +139,7 @@ namespace CSVRender ~Object(); /// Mark the object as selected, selected objects show an outline effect - void setSelected(bool selected); + void setSelected(bool selected, osg::Vec4f color = osg::Vec4f(1, 1, 1, 1)); bool getSelected() const; From a7f8ee11064c833a52ac11ffedde44c3aed65546 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:23:57 -0600 Subject: [PATCH 110/231] Feat(instancemode.cpp): Implement save/load selection group functions --- apps/opencs/view/render/instancemode.cpp | 76 ++++++++++++++++++++++++ apps/opencs/view/render/instancemode.hpp | 5 ++ 2 files changed, 81 insertions(+) diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 7332d9c84b..2d79c912f6 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -186,6 +186,71 @@ osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, con return mousePlanePoint; } +void CSVRender::InstanceMode::saveSelectionGroup(const int group) +{ + QStringList strings; + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QVariant selectionObjects; + CSMWorld::CommandMacro macro(undoStack, "Replace Selection Group"); + std::string groupName = "project::" + std::to_string(group); + + const auto& selection = getWorldspaceWidget().getSelection(Mask_Reference); + const int selectionObjectsIndex + = mSelectionGroups->findColumnIndex(CSMWorld::Columns::ColumnId_SelectionGroupObjects); + + if (dynamic_cast(&getWorldspaceWidget())) + groupName += "-ext"; + else + groupName += "-" + getWorldspaceWidget().getCellId(osg::Vec3f(0, 0, 0)); + + CSMWorld::CreateCommand* newGroup = new CSMWorld::CreateCommand(*mSelectionGroups, groupName); + + newGroup->setType(CSMWorld::UniversalId::Type_SelectionGroup); + + for (const auto& object : selection) + if (const CSVRender::ObjectTag* objectTag = dynamic_cast(object.get())) + strings << QString::fromStdString(objectTag->mObject->getReferenceId()); + + selectionObjects.setValue(strings); + + newGroup->addValue(selectionObjectsIndex, selectionObjects); + + if (mSelectionGroups->getModelIndex(groupName, 0).row() != -1) + macro.push(new CSMWorld::DeleteCommand(*mSelectionGroups, groupName)); + + macro.push(newGroup); + + getWorldspaceWidget().clearSelection(Mask_Reference); +} + +void CSVRender::InstanceMode::getSelectionGroup(const int group) +{ + std::string groupName = "project::" + std::to_string(group); + std::vector targets; + + const auto& selection = getWorldspaceWidget().getSelection(Mask_Reference); + const int selectionObjectsIndex + = mSelectionGroups->findColumnIndex(CSMWorld::Columns::ColumnId_SelectionGroupObjects); + + if (dynamic_cast(&getWorldspaceWidget())) + groupName += "-ext"; + else + groupName += "-" + getWorldspaceWidget().getCellId(osg::Vec3f(0, 0, 0)); + + const QModelIndex groupSearch = mSelectionGroups->getModelIndex(groupName, selectionObjectsIndex); + + if (groupSearch.row() == -1) + return; + + for (const QString& target : groupSearch.data().toStringList()) + targets.push_back(target.toStdString()); + + if (!selection.empty()) + getWorldspaceWidget().clearSelection(Mask_Reference); + + getWorldspaceWidget().selectGroup(targets); +} + CSVRender::InstanceMode::InstanceMode( WorldspaceWidget* worldspaceWidget, osg::ref_ptr parentNode, QWidget* parent) : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, @@ -199,6 +264,9 @@ CSVRender::InstanceMode::InstanceMode( , mUnitScaleDist(1) , mParentNode(std::move(parentNode)) { + mSelectionGroups = dynamic_cast( + worldspaceWidget->getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_SelectionGroup)); + connect(this, &InstanceMode::requestFocus, worldspaceWidget, &WorldspaceWidget::requestFocus); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget); @@ -229,6 +297,14 @@ CSVRender::InstanceMode::InstanceMode( = new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget); connect(dropToTerrainLevelShortcut2, qOverload<>(&CSMPrefs::Shortcut::activated), this, &InstanceMode::dropSelectedInstancesToTerrainSeparately); + + for (short i = 0; i <= 9; i++) + { + connect(new CSMPrefs::Shortcut("scene-group-" + std::to_string(i), worldspaceWidget), + qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, i] { this->getSelectionGroup(i); }); + connect(new CSMPrefs::Shortcut("scene-save-" + std::to_string(i), worldspaceWidget), + qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, i] { this->saveSelectionGroup(i); }); + } } void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar) diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 917fde301a..4e0172759a 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -14,6 +14,8 @@ #include "editmode.hpp" #include "instancedragmodes.hpp" +#include +#include class QDragEnterEvent; class QDropEvent; @@ -60,6 +62,7 @@ namespace CSVRender osg::ref_ptr mParentNode; osg::Vec3 mDragStart; std::vector mObjectsAtDragStart; + CSMWorld::IdTable* mSelectionGroups; int getSubModeFromId(const std::string& id) const; @@ -133,6 +136,8 @@ namespace CSVRender void subModeChanged(const std::string& id); void deleteSelectedInstances(); void cloneSelectedInstances(); + void getSelectionGroup(const int group); + void saveSelectionGroup(const int group); void dropSelectedInstancesToCollision(); void dropSelectedInstancesToTerrain(); void dropSelectedInstancesToCollisionSeparately(); From 33ce7782e9d8cb3d353cca4812b9efa0f816c567 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 22 Dec 2023 17:27:50 -0600 Subject: [PATCH 111/231] Feat(worldspacewidget.cpp): Add shortcut to clear selection --- apps/opencs/view/render/worldspacewidget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 3c7dc34efb..da02c1e179 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -142,6 +142,9 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge connect(new CSMPrefs::Shortcut("scene-unhide-all", this), qOverload<>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::unhideAll); + connect(new CSMPrefs::Shortcut("scene-clear-selection", this), qOverload<>(&CSMPrefs::Shortcut::activated), this, + [this] { this->clearSelection(Mask_Reference); }); + mInConstructor = false; } From 25f3e09da9a5551ceefe88d768fc16e88ad50080 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sat, 23 Dec 2023 13:29:06 -0600 Subject: [PATCH 112/231] Fix(CS): Correct build issues on some compilers --- apps/opencs/view/render/cell.cpp | 13 +++++++++---- apps/opencs/view/render/instancemode.hpp | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index a1f3c0c41f..b0c8425ad3 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -615,10 +615,15 @@ osg::ref_ptr CSVRender::Cell::getSnapTarget(unsigned int ele void CSVRender::Cell::selectFromGroup(const std::vector group) { for (const auto& [_, object] : mObjects) - if (auto found = std::ranges::find_if(group.begin(), group.end(), - [&object](const std::string& id) { return object->getReferenceId() == id; }); - found != group.end()) - object->setSelected(true, osg::Vec4f(1, 0, 1, 1)); + { + for (const auto& objectName : group) + { + if (objectName == object->getReferenceId()) + { + object->setSelected(true, osg::Vec4f(1, 0, 1, 1)); + } + } + } } void CSVRender::Cell::unhideAll() diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index 4e0172759a..9267823e22 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -14,8 +14,8 @@ #include "editmode.hpp" #include "instancedragmodes.hpp" +#include #include -#include class QDragEnterEvent; class QDropEvent; From 0cf55d36172ad676d49ece4368277a73a32d74dc Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Dec 2023 18:37:48 +0100 Subject: [PATCH 113/231] Use RecastGlobalAllocator for Detour --- .../detournavigator/recastglobalallocator.hpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/components/detournavigator/recastglobalallocator.hpp b/components/detournavigator/recastglobalallocator.hpp index 956f050a72..f69805eb97 100644 --- a/components/detournavigator/recastglobalallocator.hpp +++ b/components/detournavigator/recastglobalallocator.hpp @@ -3,6 +3,7 @@ #include "recasttempallocator.hpp" +#include #include #include @@ -14,10 +15,14 @@ namespace DetourNavigator public: static void init() { instance(); } - static void* alloc(size_t size, rcAllocHint hint) + static void* recastAlloc(size_t size, rcAllocHint hint) { return alloc(size, hint == RC_ALLOC_TEMP); } + + static void* detourAlloc(size_t size, dtAllocHint hint) { return alloc(size, hint == DT_ALLOC_TEMP); } + + static void* alloc(size_t size, bool temp) { void* result = nullptr; - if (rcLikely(hint == RC_ALLOC_TEMP)) + if (rcLikely(temp)) result = tempAllocator().alloc(size); if (rcUnlikely(!result)) result = allocPerm(size); @@ -38,7 +43,11 @@ namespace DetourNavigator } private: - RecastGlobalAllocator() { rcAllocSetCustom(&RecastGlobalAllocator::alloc, &RecastGlobalAllocator::free); } + RecastGlobalAllocator() + { + rcAllocSetCustom(&RecastGlobalAllocator::recastAlloc, &RecastGlobalAllocator::free); + dtAllocSetCustom(&RecastGlobalAllocator::detourAlloc, &RecastGlobalAllocator::free); + } static RecastGlobalAllocator& instance() { From 329500b0876ac1b31facbc267eebc46186bbd4bc Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 23 Dec 2023 18:53:38 +0100 Subject: [PATCH 114/231] Remove redundant return --- components/detournavigator/recasttempallocator.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/components/detournavigator/recasttempallocator.hpp b/components/detournavigator/recasttempallocator.hpp index bb23dc8494..08db5f9fca 100644 --- a/components/detournavigator/recasttempallocator.hpp +++ b/components/detournavigator/recasttempallocator.hpp @@ -52,7 +52,6 @@ namespace DetourNavigator mTop = mPrev; mPrev = getTempPtrPrev(mTop); } - return; } private: From edaac852d16bf03ff8e0a6154fb6a4e221f8af76 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 09:26:59 +0100 Subject: [PATCH 115/231] Use settings values to declare bool settings --- apps/opencs/model/prefs/boolsetting.cpp | 2 +- apps/opencs/model/prefs/boolsetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 59 +++++++++++++------------ apps/opencs/model/prefs/state.hpp | 2 +- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/apps/opencs/model/prefs/boolsetting.cpp b/apps/opencs/model/prefs/boolsetting.cpp index c7520c415d..44262e2012 100644 --- a/apps/opencs/model/prefs/boolsetting.cpp +++ b/apps/opencs/model/prefs/boolsetting.cpp @@ -11,7 +11,7 @@ #include "state.hpp" CSMPrefs::BoolSetting::BoolSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { diff --git a/apps/opencs/model/prefs/boolsetting.hpp b/apps/opencs/model/prefs/boolsetting.hpp index fd67019a78..edabf85058 100644 --- a/apps/opencs/model/prefs/boolsetting.hpp +++ b/apps/opencs/model/prefs/boolsetting.hpp @@ -21,7 +21,7 @@ namespace CSMPrefs public: explicit BoolSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); BoolSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index d44c9de888..1c922ee8d2 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -38,11 +38,11 @@ void CSMPrefs::State::declare() declareInt(mValues->mWindows.mDefaultHeight, "Default window height") .setTooltip("Newly opened top-level windows will open with this height.") .setMin(80); - declareBool("show-statusbar", "Show Status Bar", true) + declareBool(mValues->mWindows.mShowStatusbar, "Show Status Bar") .setTooltip( "If a newly open top level window is showing status bars or not. " " Note that this does not affect existing windows."); - declareBool("reuse", "Reuse Subviews", true) + declareBool(mValues->mWindows.mReuse, "Reuse Subviews") .setTooltip( "When a new subview is requested and a matching subview already " " exist, do not open a new subview and use the existing one instead."); @@ -51,7 +51,7 @@ void CSMPrefs::State::declare() "If the maximum number is reached and a new subview is opened " "it will be placed into a new top-level window.") .setRange(1, 256); - declareBool("hide-subview", "Hide single subview", false) + declareBool(mValues->mWindows.mHideSubview, "Hide single subview") .setTooltip( "When a view contains only a single subview, hide the subview title " "bar and if this subview is closed also close the view (unless it is the last " @@ -67,7 +67,7 @@ void CSMPrefs::State::declare() .addValue("Grow Only", "The view window grows as subviews are added. No scrollbars.") .addValue("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further."); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - declareBool("grow-limit", "Grow Limit Screen", false) + declareBool(mValues->mWindows.mGrowLimit, "Grow Limit Screen") .setTooltip( "When \"Grow then Scroll\" option is selected, the window size grows to" " the width of the virtual desktop. \nIf this option is selected the the window growth" @@ -103,20 +103,21 @@ void CSMPrefs::State::declare() .addValue(jumpAndSelect) .addValue("Jump Only", "Scroll new record into view") .addValue("No Jump", "No special action"); - declareBool("extended-config", "Manually specify affected record types for an extended delete/revert", false) + declareBool( + mValues->mIdTables.mExtendedConfig, "Manually specify affected record types for an extended delete/revert") .setTooltip( "Delete and revert commands have an extended form that also affects " "associated records.\n\n" "If this option is enabled, types of affected records are selected " "manually before a command execution.\nOtherwise, all associated " "records are deleted/reverted immediately."); - declareBool("subview-new-window", "Open Record in new window", false) + declareBool(mValues->mIdTables.mSubviewNewWindow, "Open Record in new window") .setTooltip( "When editing a record, open the view in a new window," " rather than docked in the main view."); declareCategory("ID Dialogues"); - declareBool("toolbar", "Show toolbar", true); + declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar"); declareCategory("Reports"); EnumValue actionNone("None"); @@ -131,22 +132,23 @@ void CSMPrefs::State::declare() declareEnum("double-s", "Shift Double Click", actionRemove).addValues(reportValues); declareEnum("double-c", "Control Double Click", actionEditAndRemove).addValues(reportValues); declareEnum("double-sc", "Shift Control Double Click", actionNone).addValues(reportValues); - declareBool("ignore-base-records", "Ignore base records in verifier", false); + declareBool(mValues->mReports.mIgnoreBaseRecords, "Ignore base records in verifier"); declareCategory("Search & Replace"); declareInt(mValues->mSearchAndReplace.mCharBefore, "Characters before search string") .setTooltip("Maximum number of character to display in search result before the searched text"); declareInt(mValues->mSearchAndReplace.mCharAfter, "Characters after search string") .setTooltip("Maximum number of character to display in search result after the searched text"); - declareBool("auto-delete", "Delete row from result table after a successful replace", true); + declareBool(mValues->mSearchAndReplace.mAutoDelete, "Delete row from result table after a successful replace"); declareCategory("Scripts"); - declareBool("show-linenum", "Show Line Numbers", true) + declareBool(mValues->mScripts.mShowLinenum, "Show Line Numbers") .setTooltip( "Show line numbers to the left of the script editor window." "The current row and column numbers of the text cursor are shown at the bottom."); - declareBool("wrap-lines", "Wrap Lines", false).setTooltip("Wrap lines longer than width of script editor."); - declareBool("mono-font", "Use monospace font", true); + declareBool(mValues->mScripts.mWrapLines, "Wrap Lines") + .setTooltip("Wrap lines longer than width of script editor."); + declareBool(mValues->mScripts.mMonoFont, "Use monospace font"); declareInt(mValues->mScripts.mTabWidth, "Tab Width") .setTooltip("Number of characters for tab width") .setRange(1, 10); @@ -155,12 +157,12 @@ void CSMPrefs::State::declare() .addValue("Ignore", "Do not report warning") .addValue(warningsNormal) .addValue("Strict", "Promote warning to an error"); - declareBool("toolbar", "Show toolbar", true); + declareBool(mValues->mScripts.mToolbar, "Show toolbar"); declareInt(mValues->mScripts.mCompileDelay, "Delay between updating of source errors") .setTooltip("Delay in milliseconds") .setRange(0, 10000); declareInt(mValues->mScripts.mErrorHeight, "Initial height of the error panel").setRange(100, 10000); - declareBool("highlight-occurrences", "Highlight other occurrences of selected names", true); + declareBool(mValues->mScripts.mHighlightOccurrences, "Highlight other occurrences of selected names"); declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta")); declareColour("colour-float", "Highlight Colour: Float Literals", QColor("magenta")); @@ -171,7 +173,7 @@ void CSMPrefs::State::declare() declareColour("colour-id", "Highlight Colour: IDs", QColor("blue")); declareCategory("General Input"); - declareBool("cycle", "Cyclic next/previous", false) + declareBool(mValues->mGeneralInput.mCycle, "Cyclic next/previous") .setTooltip( "When using next/previous functions at the last/first item of a " "list go to the first/last item"); @@ -182,19 +184,19 @@ void CSMPrefs::State::declare() declareDouble("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); declareDouble("p-navi-free-sensitivity", "Free Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); - declareBool("p-navi-free-invert", "Invert Free Camera Mouse Input", false); + declareBool(mValues->mSceneInput.mPNaviFreeInvert, "Invert Free Camera Mouse Input"); declareDouble("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); declareDouble("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); declareDouble("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); declareDouble("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); - declareBool("p-navi-orbit-invert", "Invert Orbit Camera Mouse Input", false); + declareBool(mValues->mSceneInput.mPNaviOrbitInvert, "Invert Orbit Camera Mouse Input"); declareDouble("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28); declareDouble("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4) .setRange(0.001, 1000.0); - declareBool("navi-orbit-const-roll", "Keep camera roll constant for orbital camera", true); + declareBool(mValues->mSceneInput.mNaviOrbitConstRoll, "Keep camera roll constant for orbital camera"); - declareBool("context-select", "Context Sensitive Selection", false); + declareBool(mValues->mSceneInput.mContextSelect, "Context Sensitive Selection"); declareDouble("drag-factor", "Mouse sensitivity during drag operations", 1.0).setRange(0.001, 100.0); declareDouble("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0).setRange(0.001, 100.0); declareDouble("drag-shift-factor", "Shift-acceleration factor during drag operations", 4.0) @@ -207,12 +209,12 @@ void CSMPrefs::State::declare() .setTooltip("Framerate limit in 3D preview windows. Zero value means \"unlimited\".") .setRange(0, 10000); declareInt(mValues->mRendering.mCameraFov, "Camera FOV").setRange(10, 170); - declareBool("camera-ortho", "Orthographic projection for camera", false); + declareBool(mValues->mRendering.mCameraOrtho, "Orthographic projection for camera"); declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic projection size parameter") .setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.") .setRange(10, 10000); declareDouble("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0, 1); - declareBool("scene-use-gradient", "Use Gradient Background", true); + declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background"); declareColour("scene-day-background-colour", "Day Background Colour", QColor(110, 120, 128, 255)); declareColour("scene-day-gradient-colour", "Day Gradient Colour", QColor(47, 51, 51, 255)) .setTooltip( @@ -228,11 +230,11 @@ void CSMPrefs::State::declare() .setTooltip( "Sets the gradient color to use in conjunction with the night background color. Ignored if " "the gradient option is disabled."); - declareBool("scene-day-night-switch-nodes", "Use Day/Night Switch Nodes", true); + declareBool(mValues->mRendering.mSceneDayNightSwitchNodes, "Use Day/Night Switch Nodes"); declareCategory("Tooltips"); - declareBool("scene", "Show Tooltips in 3D scenes", true); - declareBool("scene-hide-basic", "Hide basic 3D scenes tooltips", false); + declareBool(mValues->mTooltips.mScene, "Show Tooltips in 3D scenes"); + declareBool(mValues->mTooltips.mSceneHideBasic, "Hide basic 3D scenes tooltips"); declareInt(mValues->mTooltips.mSceneDelay, "Tooltip delay in milliseconds").setMin(1); EnumValue createAndInsert("Create cell and insert"); @@ -282,7 +284,7 @@ void CSMPrefs::State::declare() declareInt(mValues->mSceneEditing.mShapebrushMaximumsize, "Maximum height edit brush size") .setTooltip("Setting for the slider range of brush size in terrain height editing.") .setMin(1); - declareBool("landedit-post-smoothpainting", "Smooth land after painting height", false) + declareBool(mValues->mSceneEditing.mLandeditPostSmoothpainting, "Smooth land after painting height") .setTooltip("Raise and lower tools will leave bumpy finish without this option"); declareDouble("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25) .setTooltip( @@ -290,7 +292,7 @@ void CSMPrefs::State::declare() "Negative values may be used to roughen instead of smooth.") .setMin(-1) .setMax(1); - declareBool("open-list-view", "Open displays list view", false) + declareBool(mValues->mSceneEditing.mOpenListView, "Open displays list view") .setTooltip( "When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); @@ -487,12 +489,13 @@ CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, return *setting; } -CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(const std::string& key, const QString& label, bool default_) +CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - CSMPrefs::BoolSetting* setting = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + CSMPrefs::BoolSetting* setting + = new CSMPrefs::BoolSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 1d44f4b71a..a5fd5b0936 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -67,7 +67,7 @@ namespace CSMPrefs DoubleSetting& declareDouble(const std::string& key, const QString& label, double default_); - BoolSetting& declareBool(const std::string& key, const QString& label, bool default_); + BoolSetting& declareBool(Settings::SettingValue& value, const QString& label); EnumSetting& declareEnum(const std::string& key, const QString& label, EnumValue default_); From 81f7149f42d9452fd4af0f5e754ca4361073496c Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Dec 2023 19:00:16 +0400 Subject: [PATCH 116/231] Use a multiplication sign for custom resolution --- apps/launcher/ui/graphicspage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/ui/graphicspage.ui b/apps/launcher/ui/graphicspage.ui index 70ab1f0728..c0e2b0be06 100644 --- a/apps/launcher/ui/graphicspage.ui +++ b/apps/launcher/ui/graphicspage.ui @@ -107,7 +107,7 @@ - x + × From 1ca2a0ef66a89cae6627161761c0376cf69b775b Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Dec 2023 14:10:27 +0400 Subject: [PATCH 117/231] Store generated UI by pointer to avoid redundant processing --- apps/launcher/CMakeLists.txt | 1 - apps/opencs/CMakeLists.txt | 1 - .../contentselector/view/contentselector.cpp | 94 ++++++++++++------- .../contentselector/view/contentselector.hpp | 27 ++++-- 4 files changed, 81 insertions(+), 42 deletions(-) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 8d2208c9df..aa3970efdb 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -41,7 +41,6 @@ set(LAUNCHER_UI ${CMAKE_CURRENT_SOURCE_DIR}/ui/importpage.ui ${CMAKE_CURRENT_SOURCE_DIR}/ui/settingspage.ui ${CMAKE_CURRENT_SOURCE_DIR}/ui/directorypicker.ui - ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ) source_group(launcher FILES ${LAUNCHER} ${LAUNCHER_HEADER}) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index cea2b66331..70efd06090 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -139,7 +139,6 @@ set (OPENCS_RES ${CMAKE_SOURCE_DIR}/files/opencs/resources.qrc ) set (OPENCS_UI - ${CMAKE_SOURCE_DIR}/components/contentselector/contentselector.ui ${CMAKE_CURRENT_SOURCE_DIR}/ui/filedialog.ui ) diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 62d476b944..3f75b82487 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -1,5 +1,7 @@ #include "contentselector.hpp" +#include "ui_contentselector.h" + #include #include @@ -9,14 +11,15 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget* parent, bool showOMWScripts) : QObject(parent) + , ui(std::make_unique()) { - ui.setupUi(parent); - ui.addonView->setDragDropMode(QAbstractItemView::InternalMove); + ui->setupUi(parent); + ui->addonView->setDragDropMode(QAbstractItemView::InternalMove); if (!showOMWScripts) { - ui.languageComboBox->setHidden(true); - ui.refreshButton->setHidden(true); + ui->languageComboBox->setHidden(true); + ui->refreshButton->setHidden(true); } buildContentModel(showOMWScripts); @@ -24,21 +27,23 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget* parent, bool show buildAddonView(); } +ContentSelectorView::ContentSelector::~ContentSelector() = default; + void ContentSelectorView::ContentSelector::buildContentModel(bool showOMWScripts) { - QIcon warningIcon(ui.addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); + QIcon warningIcon(ui->addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon, showOMWScripts); } void ContentSelectorView::ContentSelector::buildGameFileView() { - ui.gameFileView->addItem(tr("")); - ui.gameFileView->setVisible(true); + ui->gameFileView->addItem(tr("")); + ui->gameFileView->setVisible(true); - connect(ui.gameFileView, qOverload(&ComboBox::currentIndexChanged), this, + connect(ui->gameFileView, qOverload(&ComboBox::currentIndexChanged), this, &ContentSelector::slotCurrentGameFileIndexChanged); - ui.gameFileView->setCurrentIndex(0); + ui->gameFileView->setCurrentIndex(0); } class AddOnProxyModel : public QSortFilterProxyModel @@ -60,9 +65,34 @@ public: } }; +bool ContentSelectorView::ContentSelector::isGamefileSelected() const +{ + return ui->gameFileView->currentIndex() > 0; +} + +QWidget* ContentSelectorView::ContentSelector::uiWidget() const +{ + return ui->contentGroupBox; +} + +QComboBox* ContentSelectorView::ContentSelector::languageBox() const +{ + return ui->languageComboBox; +} + +QToolButton* ContentSelectorView::ContentSelector::refreshButton() const +{ + return ui->refreshButton; +} + +QLineEdit* ContentSelectorView::ContentSelector::searchFilter() const +{ + return ui->searchFilter; +} + void ContentSelectorView::ContentSelector::buildAddonView() { - ui.addonView->setVisible(true); + ui->addonView->setVisible(true); mAddonProxyModel = new AddOnProxyModel(this); mAddonProxyModel->setFilterRegularExpression(searchFilter()->text()); @@ -70,12 +100,12 @@ void ContentSelectorView::ContentSelector::buildAddonView() mAddonProxyModel->setDynamicSortFilter(true); mAddonProxyModel->setSourceModel(mContentModel); - connect(ui.searchFilter, &QLineEdit::textEdited, mAddonProxyModel, &QSortFilterProxyModel::setFilterWildcard); - connect(ui.searchFilter, &QLineEdit::textEdited, this, &ContentSelector::slotSearchFilterTextChanged); + connect(ui->searchFilter, &QLineEdit::textEdited, mAddonProxyModel, &QSortFilterProxyModel::setFilterWildcard); + connect(ui->searchFilter, &QLineEdit::textEdited, this, &ContentSelector::slotSearchFilterTextChanged); - ui.addonView->setModel(mAddonProxyModel); + ui->addonView->setModel(mAddonProxyModel); - connect(ui.addonView, &QTableView::activated, this, &ContentSelector::slotAddonTableItemActivated); + connect(ui->addonView, &QTableView::activated, this, &ContentSelector::slotAddonTableItemActivated); connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this, &ContentSelector::signalAddonDataChanged); buildContextMenu(); @@ -83,10 +113,10 @@ void ContentSelectorView::ContentSelector::buildAddonView() void ContentSelectorView::ContentSelector::buildContextMenu() { - ui.addonView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui.addonView, &QTableView::customContextMenuRequested, this, &ContentSelector::slotShowContextMenu); + ui->addonView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->addonView, &QTableView::customContextMenuRequested, this, &ContentSelector::slotShowContextMenu); - mContextMenu = new QMenu(ui.addonView); + mContextMenu = new QMenu(ui->addonView); mContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems())); mContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems())); mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths())); @@ -116,7 +146,7 @@ void ContentSelectorView::ContentSelector::setGameFile(const QString& filename) if (!filename.isEmpty()) { const ContentSelectorModel::EsmFile* file = mContentModel->item(filename); - index = ui.gameFileView->findText(file->fileName()); + index = ui->gameFileView->findText(file->fileName()); // verify that the current index is also checked in the model if (!mContentModel->setCheckState(filename, true)) @@ -126,7 +156,7 @@ void ContentSelectorView::ContentSelector::setGameFile(const QString& filename) } } - ui.gameFileView->setCurrentIndex(index); + ui->gameFileView->setCurrentIndex(index); } void ContentSelectorView::ContentSelector::clearCheckStates() @@ -143,7 +173,7 @@ void ContentSelectorView::ContentSelector::setContentList(const QStringList& lis { if (list.isEmpty()) { - slotCurrentGameFileIndexChanged(ui.gameFileView->currentIndex()); + slotCurrentGameFileIndexChanged(ui->gameFileView->currentIndex()); } else mContentModel->setContentList(list); @@ -164,14 +194,14 @@ void ContentSelectorView::ContentSelector::addFiles(const QString& path, bool ne // add any game files to the combo box for (const QString& gameFileName : mContentModel->gameFiles()) { - if (ui.gameFileView->findText(gameFileName) == -1) + if (ui->gameFileView->findText(gameFileName) == -1) { - ui.gameFileView->addItem(gameFileName); + ui->gameFileView->addItem(gameFileName); } } - if (ui.gameFileView->currentIndex() != 0) - ui.gameFileView->setCurrentIndex(0); + if (ui->gameFileView->currentIndex() != 0) + ui->gameFileView->setCurrentIndex(0); mContentModel->uncheckAll(); mContentModel->checkForLoadOrderErrors(); @@ -194,10 +224,10 @@ void ContentSelectorView::ContentSelector::clearFiles() QString ContentSelectorView::ContentSelector::currentFile() const { - QModelIndex currentIdx = ui.addonView->currentIndex(); + QModelIndex currentIdx = ui->addonView->currentIndex(); - if (!currentIdx.isValid() && ui.gameFileView->currentIndex() > 0) - return ui.gameFileView->currentText(); + if (!currentIdx.isValid() && ui->gameFileView->currentIndex() > 0) + return ui->gameFileView->currentText(); QModelIndex idx = mContentModel->index(mAddonProxyModel->mapToSource(currentIdx).row(), 0, QModelIndex()); return mContentModel->data(idx, Qt::DisplayRole).toString(); @@ -225,7 +255,7 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i void ContentSelectorView::ContentSelector::setGameFileSelected(int index, bool selected) { - QString fileName = ui.gameFileView->itemText(index); + QString fileName = ui->gameFileView->itemText(index); const ContentSelectorModel::EsmFile* file = mContentModel->item(fileName); if (file != nullptr) { @@ -253,14 +283,14 @@ void ContentSelectorView::ContentSelector::slotAddonTableItemActivated(const QMo void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos) { - QPoint globalPos = ui.addonView->viewport()->mapToGlobal(pos); + QPoint globalPos = ui->addonView->viewport()->mapToGlobal(pos); mContextMenu->exec(globalPos); } void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked) { Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked; - for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes()) + for (const QModelIndex& index : ui->addonView->selectionModel()->selectedIndexes()) { QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index); if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() != checkState) @@ -284,7 +314,7 @@ void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths() { QClipboard* clipboard = QApplication::clipboard(); QString filepaths; - for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes()) + for (const QModelIndex& index : ui->addonView->selectionModel()->selectedIndexes()) { int row = mAddonProxyModel->mapToSource(index).row(); const ContentSelectorModel::EsmFile* file = mContentModel->item(row); @@ -299,5 +329,5 @@ void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths() void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QString& newText) { - ui.addonView->setDragEnabled(newText.isEmpty()); + ui->addonView->setDragEnabled(newText.isEmpty()); } diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 5919d2e516..48377acb86 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -1,13 +1,22 @@ #ifndef CONTENTSELECTOR_HPP #define CONTENTSELECTOR_HPP -#include +#include + +#include +#include +#include +#include -#include "ui_contentselector.h" #include class QSortFilterProxyModel; +namespace Ui +{ + class ContentSelector; +} + namespace ContentSelectorView { class ContentSelector : public QObject @@ -23,6 +32,8 @@ namespace ContentSelectorView public: explicit ContentSelector(QWidget* parent = nullptr, bool showOMWScripts = false); + ~ContentSelector() override; + QString currentFile() const; void addFiles(const QString& path, bool newfiles = false); @@ -39,18 +50,18 @@ namespace ContentSelectorView void setGameFile(const QString& filename = QString("")); - bool isGamefileSelected() const { return ui.gameFileView->currentIndex() > 0; } + bool isGamefileSelected() const; - QWidget* uiWidget() const { return ui.contentGroupBox; } + QWidget* uiWidget() const; - QComboBox* languageBox() const { return ui.languageComboBox; } + QComboBox* languageBox() const; - QToolButton* refreshButton() const { return ui.refreshButton; } + QToolButton* refreshButton() const; - QLineEdit* searchFilter() const { return ui.searchFilter; } + QLineEdit* searchFilter() const; private: - Ui::ContentSelector ui; + std::unique_ptr ui; void buildContentModel(bool showOMWScripts); void buildGameFileView(); From 01e2e56f9777eb85f474b96a8139c78136047b59 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sun, 24 Dec 2023 17:55:49 +0000 Subject: [PATCH 118/231] Add game-independent VFS directory to CS' VFS This should have been like this all along - all the other applications that use the game's VFS do this. --- apps/opencs/editor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index c21fc12a05..05f90b96f3 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -200,6 +200,8 @@ std::pair> CS::Editor::readConfig dataDirs.insert(dataDirs.end(), dataLocal.begin(), dataLocal.end()); + dataDirs.insert(dataDirs.begin(), mResources / "vfs"); + // iterate the data directories and add them to the file dialog for loading mFileDialog.addFiles(dataDirs); From 03a7643301ad7f607233f597f89cf5bd844a8b66 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 22 Dec 2023 00:58:29 +0100 Subject: [PATCH 119/231] Add option to show timeseries delta graph to osg_stats.py To see spikes in a single frame and correlate them with frame duration. --- scripts/osg_stats.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index d8dab2ad1a..79f0a8a0b9 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -27,6 +27,8 @@ import termtables help='Show a graph for given metric over time.') @click.option('--commulative_timeseries', type=str, multiple=True, help='Show a graph for commulative sum of a given metric over time.') +@click.option('--timeseries_delta', type=str, multiple=True, + help='Show a graph for delta between neighbouring frames of a given metric over time.') @click.option('--hist', type=str, multiple=True, help='Show a histogram for all values of given metric.') @click.option('--hist_ratio', nargs=2, type=str, multiple=True, @@ -47,6 +49,8 @@ import termtables help='Add a graph to timeseries for a sum per frame of all given timeseries metrics.') @click.option('--commulative_timeseries_sum', is_flag=True, help='Add a graph to timeseries for a sum per frame of all given commulative timeseries.') +@click.option('--timeseries_delta_sum', is_flag=True, + help='Add a graph to timeseries for a sum per frame of all given timeseries delta.') @click.option('--stats_sum', is_flag=True, help='Add a row to stats table for a sum per frame of all given stats metrics.') @click.option('--begin_frame', type=int, default=0, @@ -69,7 +73,8 @@ import termtables def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plot, stats, precision, timeseries_sum, stats_sum, begin_frame, end_frame, path, commulative_timeseries, commulative_timeseries_sum, frame_number_name, - hist_threshold, threshold_name, threshold_value, show_common_path_prefix, stats_sort_by): + hist_threshold, threshold_name, threshold_value, show_common_path_prefix, stats_sort_by, + timeseries_delta, timeseries_delta_sum): sources = {v: list(read_data(v)) for v in path} if path else {'stdin': list(read_data(None))} if not show_common_path_prefix and len(sources) > 1: longest_common_prefix = os.path.commonprefix(list(sources.keys())) @@ -92,6 +97,9 @@ def main(print_keys, regexp_match, timeseries, hist, hist_ratio, stdev_hist, plo if commulative_timeseries: draw_commulative_timeseries(sources=frames, keys=matching_keys(commulative_timeseries), add_sum=commulative_timeseries_sum, begin_frame=begin_frame, end_frame=end_frame) + if timeseries_delta: + draw_timeseries_delta(sources=frames, keys=matching_keys(timeseries_delta), add_sum=timeseries_delta_sum, + begin_frame=begin_frame, end_frame=end_frame) if hist: draw_hists(sources=frames, keys=matching_keys(hist)) if hist_ratio: @@ -186,6 +194,20 @@ def draw_commulative_timeseries(sources, keys, add_sum, begin_frame, end_frame): fig.canvas.manager.set_window_title('commulative_timeseries') +def draw_timeseries_delta(sources, keys, add_sum, begin_frame, end_frame): + fig, ax = matplotlib.pyplot.subplots() + x = numpy.array(range(begin_frame + 1, end_frame)) + for name, frames in sources.items(): + for key in keys: + ax.plot(x, numpy.diff(frames[key]), label=f'{key}:{name}') + if add_sum: + ax.plot(x, numpy.diff(numpy.sum(list(frames[k] for k in keys), axis=0)), label=f'sum:{name}', + linestyle='--') + ax.grid(True) + ax.legend() + fig.canvas.manager.set_window_title('timeseries_delta') + + def draw_hists(sources, keys): fig, ax = matplotlib.pyplot.subplots() bins = numpy.linspace( From 26ffde0c048ae7f0e529adaa232407e8fb945992 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Dec 2023 14:21:28 +0100 Subject: [PATCH 120/231] Reduce ccache size for gcc to 3G To avoid having jobs being unable to generate the cache like https://gitlab.com/OpenMW/openmw/-/jobs/5766643208: Creating cache Ubuntu_GCC.ubuntu_22.04.v1-3-non_protected... apt-cache/: found 1082 matching artifact files and directories ccache/: found 7443 matching artifact files and directories FATAL: write ../../../cache/OpenMW/openmw/Ubuntu_GCC.ubuntu_22.04.v1-3-non_protected/archive_991882895: no space left on device Failed to create cache --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 44655466ea..63f5bfb45e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -142,7 +142,7 @@ Ubuntu_GCC: variables: CC: gcc CXX: g++ - CCACHE_SIZE: 4G + CCACHE_SIZE: 3G # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. timeout: 2h @@ -193,7 +193,7 @@ Ubuntu_GCC_Debug: variables: CC: gcc CXX: g++ - CCACHE_SIZE: 4G + CCACHE_SIZE: 3G CMAKE_BUILD_TYPE: Debug CMAKE_CXX_FLAGS_DEBUG: -O0 # When CCache doesn't exist (e.g. first build on a fork), build takes more than 1h, which is the default for forks. From c6b7dfc23a494f244114ddf64fb3e22bc3cb969b Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 22:55:11 +0100 Subject: [PATCH 121/231] Convert to float to compute stdev --- scripts/osg_stats.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 79f0a8a0b9..8c504dcad7 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -350,7 +350,7 @@ def make_stats(source, key, values, precision): sum=fixed_float(sum(values), precision), mean=fixed_float(statistics.mean(values), precision), median=fixed_float(statistics.median(values), precision), - stdev=fixed_float(statistics.stdev(values), precision), + stdev=fixed_float(statistics.stdev(float(v) for v in values), precision), q95=fixed_float(numpy.quantile(values, 0.95), precision), ) From 60940e7561696b3526f5e3db5447b75105a70efb Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 22:09:42 +0100 Subject: [PATCH 122/231] Fill absent values with previous present Due to OSG stats reporting usage and implementation for some attributes values are missing on loading screens. --- scripts/osg_stats.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/osg_stats.py b/scripts/osg_stats.py index 8c504dcad7..3cdd0febae 100755 --- a/scripts/osg_stats.py +++ b/scripts/osg_stats.py @@ -143,7 +143,7 @@ def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): end_frame = min(end_frame, max(v[-1][frame_number_name] for v in sources.values()) + 1) for name in sources.keys(): for key in keys: - result[name][key] = [0] * (end_frame - begin_frame) + result[name][key] = [None] * (end_frame - begin_frame) for name, frames in sources.items(): for frame in frames: number = frame[frame_number_name] @@ -154,7 +154,14 @@ def collect_per_frame(sources, keys, begin_frame, end_frame, frame_number_name): result[name][key][index] = frame[key] for name in result.keys(): for key in keys: - result[name][key] = numpy.array(result[name][key]) + prev = 0.0 + values = result[name][key] + for i in range(len(values)): + if values[i] is not None: + prev = values[i] + else: + values[i] = prev + result[name][key] = numpy.array(values) return result, begin_frame, end_frame From f2c284688bc6a2a375b0641bcb8cf79abc69656f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 25 Dec 2023 11:29:36 +0400 Subject: [PATCH 123/231] Make content selector tooltip localizable --- components/contentselector/model/esmfile.cpp | 7 ------- components/contentselector/model/esmfile.hpp | 11 +++++++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 741ed173a2..75a0adb45e 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -4,13 +4,6 @@ #include int ContentSelectorModel::EsmFile::sPropertyCount = 7; -QString ContentSelectorModel::EsmFile::sToolTip = QString( - "Author: %1
\ - Version: %2
\ - Modified: %3
\ - Path:
%4
\ -
Description:
%5
\ -
Dependencies: %6
"); ContentSelectorModel::EsmFile::EsmFile(const QString& fileName, ModelItem* parent) : ModelItem(parent) diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 1ffaa8fe72..5a04ec8b38 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -59,7 +59,7 @@ namespace ContentSelectorModel QString description() const { return mDescription; } QString toolTip() const { - return sToolTip.arg(mAuthor) + return mTooltipTemlate.arg(mAuthor) .arg(mVersion) .arg(mModified.toString(Qt::ISODate)) .arg(mPath) @@ -72,9 +72,16 @@ namespace ContentSelectorModel public: static int sPropertyCount; - static QString sToolTip; private: + QString mTooltipTemlate = tr( + "Author: %1
" + "Version: %2
" + "Modified: %3
" + "Path:
%4
" + "
Description:
%5
" + "
Dependencies: %6
"); + QString mFileName; QString mAuthor; QDateTime mModified; From e5af4322c7bc3bfc28b9f0e2ad9678687a9956bd Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 21:25:07 +0100 Subject: [PATCH 124/231] Add flag to print all openmw output in integration tests --- scripts/integration_tests.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/integration_tests.py b/scripts/integration_tests.py index da2b9aa7fe..41c6c3a5a2 100755 --- a/scripts/integration_tests.py +++ b/scripts/integration_tests.py @@ -13,6 +13,7 @@ parser.add_argument("--omw", type=str, default="openmw", help="path to openmw bi parser.add_argument( "--workdir", type=str, default="integration_tests_output", help="directory for temporary files and logs" ) +parser.add_argument("--verbose", action='store_true', help="print all openmw output") args = parser.parse_args() example_suite_dir = Path(args.example_suite).resolve() @@ -78,7 +79,10 @@ def runTest(name): ) as process: quit_requested = False for line in process.stdout: - stdout_lines.append(line) + if args.verbose: + sys.stdout.write(line) + else: + stdout_lines.append(line) words = line.split(" ") if len(words) > 1 and words[1] == "E]": print(line, end="") @@ -102,7 +106,7 @@ def runTest(name): exit_ok = False if os.path.exists(config_dir / "openmw.log"): shutil.copyfile(config_dir / "openmw.log", work_dir / f"{name}.{time_str}.log") - if not exit_ok: + if not exit_ok and not args.verbose: sys.stdout.writelines(stdout_lines) if test_success and exit_ok: print(f"{name} succeeded") From 8fe0832b382ac2eac6be0301b8e44bedfa714c78 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 21:26:18 +0100 Subject: [PATCH 125/231] Replace check for normalized distance =~ value by distance > 0 We just need to make sure player moved, for how long is not important. To avoid failures like: https://gitlab.com/OpenMW/openmw/-/jobs/5815281969: TEST playerForwardRunning FAILED [string "test.lua"]:80: [string "player.lua"]:44: Normalized forward runned distance: 0.782032 ~= 1.000000 --- scripts/data/integration_tests/test_lua_api/player.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/data/integration_tests/test_lua_api/player.lua b/scripts/data/integration_tests/test_lua_api/player.lua index 41022828f9..544bf5adc0 100644 --- a/scripts/data/integration_tests/test_lua_api/player.lua +++ b/scripts/data/integration_tests/test_lua_api/player.lua @@ -40,8 +40,7 @@ testing.registerLocalTest('playerForwardRunning', coroutine.yield() end local direction, distance = (self.position - startPos):normalize() - local normalizedDistance = distance / types.Actor.runSpeed(self) - testing.expectEqualWithDelta(normalizedDistance, 1, 0.2, 'Normalized forward runned distance') + testing.expectGreaterThan(distance, 0, 'Run forward, distance') testing.expectEqualWithDelta(direction.x, 0, 0.1, 'Run forward, X coord') testing.expectEqualWithDelta(direction.y, 1, 0.1, 'Run forward, Y coord') end) @@ -59,8 +58,7 @@ testing.registerLocalTest('playerDiagonalWalking', coroutine.yield() end local direction, distance = (self.position - startPos):normalize() - local normalizedDistance = distance / types.Actor.walkSpeed(self) - testing.expectEqualWithDelta(normalizedDistance, 1, 0.2, 'Normalized diagonally walked distance') + testing.expectGreaterThan(distance, 0, 'Walk diagonally, distance') testing.expectEqualWithDelta(direction.x, -0.707, 0.1, 'Walk diagonally, X coord') testing.expectEqualWithDelta(direction.y, -0.707, 0.1, 'Walk diagonally, Y coord') end) From 3363616f56dbf9a2c31467a6056183ee5169fd9d Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 17 Dec 2023 15:22:00 +0100 Subject: [PATCH 126/231] Remove redundant startsWith function --- components/vfs/manager.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index bfc001e4f2..ec8939f5dc 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -70,21 +70,13 @@ namespace VFS return found->second->getPath(); } - namespace - { - bool startsWith(std::string_view text, std::string_view start) - { - return text.rfind(start, 0) == 0; - } - } - Manager::RecursiveDirectoryRange Manager::getRecursiveDirectoryIterator(std::string_view path) const { if (path.empty()) return { mIndex.begin(), mIndex.end() }; std::string normalized = Path::normalizeFilename(path); const auto it = mIndex.lower_bound(normalized); - if (it == mIndex.end() || !startsWith(it->first, normalized)) + if (it == mIndex.end() || !it->first.starts_with(normalized)) return { it, it }; ++normalized.back(); return { it, mIndex.lower_bound(normalized) }; From 0d8dc5aabc8a62bf6a6733dd87afac708c63eb24 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 17 Dec 2023 15:20:48 +0100 Subject: [PATCH 127/231] Use string_view for VFS lookups --- apps/openmw_test_suite/testing_util.hpp | 4 ++-- components/vfs/archive.hpp | 7 +++++-- components/vfs/bsaarchive.hpp | 4 ++-- components/vfs/filesystemarchive.cpp | 10 ++++------ components/vfs/filesystemarchive.hpp | 8 +++----- components/vfs/manager.cpp | 6 +++--- components/vfs/manager.hpp | 8 ++++---- 7 files changed, 23 insertions(+), 24 deletions(-) diff --git a/apps/openmw_test_suite/testing_util.hpp b/apps/openmw_test_suite/testing_util.hpp index 89fb7f9e73..0c941053a7 100644 --- a/apps/openmw_test_suite/testing_util.hpp +++ b/apps/openmw_test_suite/testing_util.hpp @@ -57,13 +57,13 @@ namespace TestingOpenMW { } - void listResources(std::map& out) override + void listResources(VFS::FileMap& out) override { for (const auto& [key, value] : mFiles) out.emplace(VFS::Path::normalizeFilename(key), value); } - bool contains(const std::string& file) const override { return mFiles.count(file) != 0; } + bool contains(std::string_view file) const override { return mFiles.contains(file); } std::string getDescription() const override { return "TestData"; } }; diff --git a/components/vfs/archive.hpp b/components/vfs/archive.hpp index e377e8c5b6..79c876b391 100644 --- a/components/vfs/archive.hpp +++ b/components/vfs/archive.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -19,16 +20,18 @@ namespace VFS virtual std::filesystem::path getPath() = 0; }; + using FileMap = std::map>; + class Archive { public: virtual ~Archive() = default; /// List all resources contained in this archive. - virtual void listResources(std::map& out) = 0; + virtual void listResources(FileMap& out) = 0; /// True if this archive contains the provided normalized file. - virtual bool contains(const std::string& file) const = 0; + virtual bool contains(std::string_view file) const = 0; virtual std::string getDescription() const = 0; }; diff --git a/components/vfs/bsaarchive.hpp b/components/vfs/bsaarchive.hpp index 517c95e273..29098db45d 100644 --- a/components/vfs/bsaarchive.hpp +++ b/components/vfs/bsaarchive.hpp @@ -48,7 +48,7 @@ namespace VFS virtual ~BsaArchive() {} - void listResources(std::map& out) override + void listResources(FileMap& out) override { for (auto& resource : mResources) { @@ -59,7 +59,7 @@ namespace VFS } } - bool contains(const std::string& file) const override + bool contains(std::string_view file) const override { for (const auto& it : mResources) { diff --git a/components/vfs/filesystemarchive.cpp b/components/vfs/filesystemarchive.cpp index 96f5e87f5e..7d88dd9cc0 100644 --- a/components/vfs/filesystemarchive.cpp +++ b/components/vfs/filesystemarchive.cpp @@ -17,7 +17,7 @@ namespace VFS { } - void FileSystemArchive::listResources(std::map& out) + void FileSystemArchive::listResources(FileMap& out) { if (!mBuiltIndex) { @@ -51,14 +51,12 @@ namespace VFS } else { - for (index::iterator it = mIndex.begin(); it != mIndex.end(); ++it) - { - out[it->first] = &it->second; - } + for (auto& [k, v] : mIndex) + out[k] = &v; } } - bool FileSystemArchive::contains(const std::string& file) const + bool FileSystemArchive::contains(std::string_view file) const { return mIndex.find(file) != mIndex.end(); } diff --git a/components/vfs/filesystemarchive.hpp b/components/vfs/filesystemarchive.hpp index 39d711b327..e31ef9bd30 100644 --- a/components/vfs/filesystemarchive.hpp +++ b/components/vfs/filesystemarchive.hpp @@ -27,16 +27,14 @@ namespace VFS public: FileSystemArchive(const std::filesystem::path& path); - void listResources(std::map& out) override; + void listResources(FileMap& out) override; - bool contains(const std::string& file) const override; + bool contains(std::string_view file) const override; std::string getDescription() const override; private: - typedef std::map index; - index mIndex; - + std::map> mIndex; bool mBuiltIndex; std::filesystem::path mPath; }; diff --git a/components/vfs/manager.cpp b/components/vfs/manager.cpp index ec8939f5dc..cc231847f5 100644 --- a/components/vfs/manager.cpp +++ b/components/vfs/manager.cpp @@ -35,11 +35,11 @@ namespace VFS return getNormalized(Path::normalizeFilename(name)); } - Files::IStreamPtr Manager::getNormalized(const std::string& normalizedName) const + Files::IStreamPtr Manager::getNormalized(std::string_view normalizedName) const { - std::map::const_iterator found = mIndex.find(normalizedName); + const auto found = mIndex.find(normalizedName); if (found == mIndex.end()) - throw std::runtime_error("Resource '" + normalizedName + "' not found"); + throw std::runtime_error("Resource '" + std::string(normalizedName) + "' not found"); return found->second->open(); } diff --git a/components/vfs/manager.hpp b/components/vfs/manager.hpp index db38e4b240..76405aae2c 100644 --- a/components/vfs/manager.hpp +++ b/components/vfs/manager.hpp @@ -41,7 +41,7 @@ namespace VFS class RecursiveDirectoryIterator { public: - RecursiveDirectoryIterator(std::map::const_iterator it) + RecursiveDirectoryIterator(FileMap::const_iterator it) : mIt(it) { } @@ -55,7 +55,7 @@ namespace VFS } private: - std::map::const_iterator mIt; + FileMap::const_iterator mIt; }; using RecursiveDirectoryRange = IteratorPair; @@ -83,7 +83,7 @@ namespace VFS /// Retrieve a file by name (name is already normalized). /// @note Throws an exception if the file can not be found. /// @note May be called from any thread once the index has been built. - Files::IStreamPtr getNormalized(const std::string& normalizedName) const; + Files::IStreamPtr getNormalized(std::string_view normalizedName) const; std::string getArchive(std::string_view name) const; @@ -101,7 +101,7 @@ namespace VFS private: std::vector> mArchives; - std::map mIndex; + FileMap mIndex; }; } From 71e33cf8b2441a6d008f8106e407852417431935 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Dec 2023 00:28:47 +0100 Subject: [PATCH 128/231] Add unit tests for GenericObjectCache --- apps/openmw_test_suite/CMakeLists.txt | 2 + .../resource/testobjectcache.cpp | 308 ++++++++++++++++++ components/resource/objectcache.hpp | 2 +- 3 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 apps/openmw_test_suite/resource/testobjectcache.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 4f93319c96..203c4ac2db 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -94,6 +94,8 @@ file(GLOB UNITTEST_SRC_FILES nifosg/testnifloader.cpp esmterrain/testgridsampling.cpp + + resource/testobjectcache.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp new file mode 100644 index 0000000000..5b7741025a --- /dev/null +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -0,0 +1,308 @@ +#include + +#include +#include + +#include + +namespace Resource +{ + namespace + { + using namespace ::testing; + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheShouldReturnNullptrByDefault) + { + osg::ref_ptr> cache(new GenericObjectCache); + EXPECT_EQ(cache->getRefFromObjectCache(42), nullptr); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheOrNoneShouldReturnNulloptByDefault) + { + osg::ref_ptr> cache(new GenericObjectCache); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(42), std::nullopt); + } + + struct Object : osg::Object + { + Object() = default; + + Object(const Object& other, const osg::CopyOp& copyOp = osg::CopyOp()) + : osg::Object(other, copyOp) + { + } + + META_Object(ResourceTest, Object) + }; + + TEST(ResourceGenericObjectCacheTest, shouldStoreValues) + { + osg::ref_ptr> cache(new GenericObjectCache); + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + EXPECT_EQ(cache->getRefFromObjectCache(key), value); + } + + TEST(ResourceGenericObjectCacheTest, shouldStoreNullptrValues) + { + osg::ref_ptr> cache(new GenericObjectCache); + const int key = 42; + cache->addEntryToObjectCache(key, nullptr); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(nullptr)); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldExtendLifetimeForItemsWithZeroTimestamp) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value, 0); + value = nullptr; + + const double referenceTime = 1000; + const double expiryDelay = 1; + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldReplaceExistingItemByKey) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const int key = 42; + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + cache->addEntryToObjectCache(key, value1); + ASSERT_EQ(cache->getRefFromObjectCache(key), value1); + cache->addEntryToObjectCache(key, value2); + EXPECT_EQ(cache->getRefFromObjectCache(key), value2); + } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldMarkLifetime) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + cache->addEntryToObjectCache(key, nullptr, referenceTime + expiryDelay); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + 2 * expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime + expiryDelay); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldRemoveExpiredItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 1; + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + value = nullptr; + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldKeepExternallyReferencedItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 1; + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(value)); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldKeepNotExpiredItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + value = nullptr; + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldKeepNotExpiredNullptrItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + cache->addEntryToObjectCache(key, nullptr); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheOrNoneShouldNotExtendItemLifetime) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + cache->addEntryToObjectCache(key, nullptr); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, lowerBoundShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + cache->addEntryToObjectCache("a", nullptr); + cache->addEntryToObjectCache("c", nullptr); + EXPECT_THAT(cache->lowerBound(std::string_view("b")), Optional(Pair("c", _))); + } + + TEST(ResourceGenericObjectCacheTest, shouldSupportRemovingItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + ASSERT_EQ(cache->getRefFromObjectCache(key), value); + cache->removeFromObjectCache(key); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, clearShouldRemoveAllItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const int key1 = 42; + const int key2 = 13; + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + cache->addEntryToObjectCache(key1, value1); + cache->addEntryToObjectCache(key2, value2); + + ASSERT_EQ(cache->getRefFromObjectCache(key1), value1); + ASSERT_EQ(cache->getRefFromObjectCache(key2), value2); + + cache->clear(); + + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key1), std::nullopt); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key2), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, callShouldIterateOverAllItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + osg::ref_ptr value3(new Object); + cache->addEntryToObjectCache(1, value1); + cache->addEntryToObjectCache(2, value2); + cache->addEntryToObjectCache(3, value3); + + std::vector> actual; + cache->call([&](int key, osg::Object* value) { actual.emplace_back(key, value); }); + + EXPECT_THAT(actual, ElementsAre(Pair(1, value1.get()), Pair(2, value2.get()), Pair(3, value3.get()))); + } + + TEST(ResourceGenericObjectCacheTest, getCacheSizeShouldReturnNumberOrAddedItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + cache->addEntryToObjectCache(13, value1); + cache->addEntryToObjectCache(42, value2); + + EXPECT_EQ(cache->getCacheSize(), 2); + } + + TEST(ResourceGenericObjectCacheTest, lowerBoundShouldReturnFirstNotLessThatGivenKey) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + osg::ref_ptr value3(new Object); + cache->addEntryToObjectCache(1, value1); + cache->addEntryToObjectCache(2, value2); + cache->addEntryToObjectCache(4, value3); + + EXPECT_THAT(cache->lowerBound(3), Optional(Pair(4, value3))); + } + + TEST(ResourceGenericObjectCacheTest, lowerBoundShouldReturnNulloptWhenKeyIsGreaterThanAnyOther) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + osg::ref_ptr value3(new Object); + cache->addEntryToObjectCache(1, value1); + cache->addEntryToObjectCache(2, value2); + cache->addEntryToObjectCache(3, value3); + + EXPECT_EQ(cache->lowerBound(4), std::nullopt); + } + } +} diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 881729ffc4..f8a5843395 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -180,7 +180,7 @@ namespace Resource /** call operator()(KeyType, osg::Object*) for each object in the cache. */ template - void call(Functor& f) + void call(Functor&& f) { std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) From ffffb427f5102ad69355b901e38dc746c33bc13e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 24 Sep 2023 21:58:10 +0400 Subject: [PATCH 129/231] Implement crime disposition modifier (bug 4683) --- CHANGELOG.md | 1 + apps/openmw/mwgui/dialogue.cpp | 11 ++ apps/openmw/mwmechanics/actors.cpp | 3 + .../mwmechanics/mechanicsmanagerimp.cpp | 124 ++++++++++++++---- apps/openmw/mwmechanics/npcstats.cpp | 18 +++ apps/openmw/mwmechanics/npcstats.hpp | 5 + apps/openmw/mwscript/miscextensions.cpp | 6 +- components/esm3/formatversion.hpp | 2 +- components/esm3/npcstats.cpp | 7 + components/esm3/npcstats.hpp | 1 + 10 files changed, 149 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b84b7296..8d8d6f8ccf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Bug #4382: Sound output device does not change when it should Bug #4508: Can't stack enchantment buffs from different instances of the same self-cast generic magic apparel Bug #4610: Casting a Bound Weapon spell cancels the casting animation by equipping the weapon prematurely + Bug #4683: Disposition decrease when player commits crime is not implemented properly Bug #4742: Actors with wander never stop walking after Loopgroup Walkforward Bug #4743: PlayGroup doesn't play non-looping animations correctly Bug #4754: Stack of ammunition cannot be equipped partially diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index 79673463ef..0e44b8c03e 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -23,9 +23,11 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/npcstats.hpp" #include "bookpage.hpp" #include "textcolours.hpp" @@ -736,6 +738,15 @@ namespace MWGui bool dispositionVisible = false; if (!mPtr.isEmpty() && mPtr.getClass().isNpc()) { + // If actor was a witness to a crime which was payed off, + // restore original disposition immediately. + MWMechanics::NpcStats& npcStats = mPtr.getClass().getNpcStats(mPtr); + if (npcStats.getCrimeId() != -1 && npcStats.getCrimeDispositionModifier() != 0) + { + if (npcStats.getCrimeId() <= MWBase::Environment::get().getWorld()->getPlayer().getCrimeId()) + npcStats.setCrimeDispositionModifier(0); + } + dispositionVisible = true; mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition( diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e0baac0764..afb7ec3e8e 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1164,6 +1164,9 @@ namespace MWMechanics creatureStats.setAlarmed(false); creatureStats.setAiSetting(AiSetting::Fight, ptr.getClass().getBaseFightRating(ptr)); + // Restore original disposition + npcStats.setCrimeDispositionModifier(0); + // Update witness crime id npcStats.setCrimeId(-1); } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8a64af1cbd..3a253d9dc3 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -473,8 +473,8 @@ namespace MWMechanics int MechanicsManager::getDerivedDisposition(const MWWorld::Ptr& ptr, bool clamp) { - const MWMechanics::NpcStats& npcSkill = ptr.getClass().getNpcStats(ptr); - float x = static_cast(npcSkill.getBaseDisposition()); + const MWMechanics::NpcStats& npcStats = ptr.getClass().getNpcStats(ptr); + float x = static_cast(npcStats.getBaseDisposition() + npcStats.getCrimeDispositionModifier()); MWWorld::LiveCellRef* npc = ptr.get(); MWWorld::Ptr playerPtr = getPlayer(); @@ -1287,62 +1287,134 @@ namespace MWMechanics if (!canReportCrime(actor, victim, playerFollowers)) continue; - if (reported && actor.getClass().isClass(actor, "guard")) + NpcStats& observerStats = actor.getClass().getNpcStats(actor); + + int alarm = observerStats.getAiSetting(AiSetting::Alarm).getBase(); + float alarmTerm = 0.01f * alarm; + + bool isActorVictim = actor == victim; + float dispTerm = isActorVictim ? dispVictim : disp; + + bool isActorGuard = actor.getClass().isClass(actor, "guard"); + + int currentDisposition = getDerivedDisposition(actor); + + bool isPermanent = false; + bool applyOnlyIfHostile = false; + int dispositionModifier = 0; + // Murdering and trespassing seem to do not affect disposition + if (type == OT_Theft) + { + dispositionModifier = static_cast(dispTerm * alarmTerm); + } + else if (type == OT_Pickpocket) + { + if (alarm >= 100 && isActorGuard) + dispositionModifier = static_cast(dispTerm); + else if (isActorVictim && isActorGuard) + { + isPermanent = true; + dispositionModifier = static_cast(dispTerm * alarmTerm); + } + else if (isActorVictim) + { + isPermanent = true; + dispositionModifier = static_cast(dispTerm); + } + } + else if (type == OT_Assault) + { + if (isActorVictim && !isActorGuard) + { + isPermanent = true; + dispositionModifier = static_cast(dispTerm); + } + else if (alarm >= 100) + dispositionModifier = static_cast(dispTerm); + else if (isActorVictim && isActorGuard) + { + isPermanent = true; + dispositionModifier = static_cast(dispTerm * alarmTerm); + } + else + { + applyOnlyIfHostile = true; + dispositionModifier = static_cast(dispTerm * alarmTerm); + } + } + + bool setCrimeId = false; + if (isPermanent && dispositionModifier != 0 && !applyOnlyIfHostile) + { + setCrimeId = true; + dispositionModifier = std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition); + int baseDisposition = observerStats.getBaseDisposition(); + observerStats.setBaseDisposition(baseDisposition + dispositionModifier); + } + else if (dispositionModifier != 0 && !applyOnlyIfHostile) + { + setCrimeId = true; + dispositionModifier = std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition); + observerStats.modCrimeDispositionModifier(dispositionModifier); + } + + if (isActorGuard && alarm >= 100) { // Mark as Alarmed for dialogue - actor.getClass().getCreatureStats(actor).setAlarmed(true); + observerStats.setAlarmed(true); - // Set the crime ID, which we will use to calm down participants - // once the bounty has been paid. - actor.getClass().getNpcStats(actor).setCrimeId(id); + setCrimeId = true; - if (!actor.getClass().getCreatureStats(actor).getAiSequence().isInPursuit()) + if (!observerStats.getAiSequence().isInPursuit()) { - actor.getClass().getCreatureStats(actor).getAiSequence().stack(AiPursue(player), actor); + observerStats.getAiSequence().stack(AiPursue(player), actor); } } else { - float dispTerm = (actor == victim) ? dispVictim : disp; - - float alarmTerm - = 0.01f * actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Alarm).getBase(); - if (type == OT_Pickpocket && alarmTerm <= 0) + // If Alarm is 0, treat it like 100 to calculate a Fight modifier for a victim of pickpocketing. + // Observers which do not try to arrest player do not care about pickpocketing at all. + if (type == OT_Pickpocket && isActorVictim && alarmTerm == 0.0) alarmTerm = 1.0; + else if (type == OT_Pickpocket && !isActorVictim) + alarmTerm = 0.0; - if (actor != victim) - dispTerm *= alarmTerm; - - float fightTerm = static_cast((actor == victim) ? fightVictim : fight); + float fightTerm = static_cast(isActorVictim ? fightVictim : fight); fightTerm += getFightDispositionBias(dispTerm); fightTerm += getFightDistanceBias(actor, player); fightTerm *= alarmTerm; - const int observerFightRating - = actor.getClass().getCreatureStats(actor).getAiSetting(AiSetting::Fight).getBase(); + const int observerFightRating = observerStats.getAiSetting(AiSetting::Fight).getBase(); if (observerFightRating + fightTerm > 100) fightTerm = static_cast(100 - observerFightRating); fightTerm = std::max(0.f, fightTerm); if (observerFightRating + fightTerm >= 100) { + if (dispositionModifier != 0 && applyOnlyIfHostile) + { + dispositionModifier + = std::clamp(dispositionModifier, -currentDisposition, 100 - currentDisposition); + observerStats.modCrimeDispositionModifier(dispositionModifier); + } + startCombat(actor, player); - NpcStats& observerStats = actor.getClass().getNpcStats(actor); // Apply aggression value to the base Fight rating, so that the actor can continue fighting // after a Calm spell wears off observerStats.setAiSetting(AiSetting::Fight, observerFightRating + static_cast(fightTerm)); - observerStats.setBaseDisposition(observerStats.getBaseDisposition() + static_cast(dispTerm)); - - // Set the crime ID, which we will use to calm down participants - // once the bounty has been paid. - observerStats.setCrimeId(id); + setCrimeId = true; // Mark as Alarmed for dialogue observerStats.setAlarmed(true); } } + + // Set the crime ID, which we will use to calm down participants + // once the bounty has been paid and restore their disposition to player character. + if (setCrimeId) + observerStats.setCrimeId(id); } if (reported) diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index b9df650fc3..808059fccd 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -20,6 +20,7 @@ MWMechanics::NpcStats::NpcStats() : mDisposition(0) + , mCrimeDispositionModifier(0) , mReputation(0) , mCrimeId(-1) , mBounty(0) @@ -43,6 +44,21 @@ void MWMechanics::NpcStats::setBaseDisposition(int disposition) mDisposition = disposition; } +int MWMechanics::NpcStats::getCrimeDispositionModifier() const +{ + return mCrimeDispositionModifier; +} + +void MWMechanics::NpcStats::setCrimeDispositionModifier(int value) +{ + mCrimeDispositionModifier = value; +} + +void MWMechanics::NpcStats::modCrimeDispositionModifier(int value) +{ + mCrimeDispositionModifier += value; +} + const MWMechanics::SkillValue& MWMechanics::NpcStats::getSkill(ESM::RefId id) const { auto it = mSkills.find(id); @@ -464,6 +480,7 @@ void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const state.mFactions[iter->first].mRank = iter->second; state.mDisposition = mDisposition; + state.mCrimeDispositionModifier = mCrimeDispositionModifier; for (const auto& [id, value] : mSkills) { @@ -528,6 +545,7 @@ void MWMechanics::NpcStats::readState(const ESM::NpcStats& state) } mDisposition = state.mDisposition; + mCrimeDispositionModifier = state.mCrimeDispositionModifier; for (size_t i = 0; i < state.mSkills.size(); ++i) { diff --git a/apps/openmw/mwmechanics/npcstats.hpp b/apps/openmw/mwmechanics/npcstats.hpp index b6a655e84f..7113ee6207 100644 --- a/apps/openmw/mwmechanics/npcstats.hpp +++ b/apps/openmw/mwmechanics/npcstats.hpp @@ -22,6 +22,7 @@ namespace MWMechanics class NpcStats : public CreatureStats { int mDisposition; + int mCrimeDispositionModifier; std::map mSkills; // SkillValue.mProgress used by the player only int mReputation; @@ -54,6 +55,10 @@ namespace MWMechanics int getBaseDisposition() const; void setBaseDisposition(int disposition); + int getCrimeDispositionModifier() const; + void setCrimeDispositionModifier(int value); + void modCrimeDispositionModifier(int value); + int getReputation() const; void setReputation(int reputation); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 687b512106..b8dc047737 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -1346,8 +1346,10 @@ namespace MWScript { MWWorld::Ptr player = MWMechanics::getPlayer(); player.getClass().getNpcStats(player).setBounty(0); - MWBase::Environment::get().getWorld()->confiscateStolenItems(player); - MWBase::Environment::get().getWorld()->getPlayer().recordCrimeId(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + world->confiscateStolenItems(player); + world->getPlayer().recordCrimeId(); + world->getPlayer().setDrawState(MWMechanics::DrawState::Nothing); } }; diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 949cdadc38..b460c15247 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -24,7 +24,7 @@ namespace ESM inline constexpr FormatVersion MaxNameIsRefIdOnlyFormatVersion = 25; inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 29; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 30; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; diff --git a/components/esm3/npcstats.cpp b/components/esm3/npcstats.cpp index dc221a5b43..371f506fb4 100644 --- a/components/esm3/npcstats.cpp +++ b/components/esm3/npcstats.cpp @@ -37,6 +37,9 @@ namespace ESM mDisposition = 0; esm.getHNOT(mDisposition, "DISP"); + mCrimeDispositionModifier = 0; + esm.getHNOT(mCrimeDispositionModifier, "DISM"); + const bool intFallback = esm.getFormatVersion() <= MaxIntFallbackFormatVersion; for (auto& skill : mSkills) skill.load(esm, intFallback); @@ -94,6 +97,9 @@ namespace ESM if (mDisposition) esm.writeHNT("DISP", mDisposition); + if (mCrimeDispositionModifier) + esm.writeHNT("DISM", mCrimeDispositionModifier); + for (const auto& skill : mSkills) skill.save(esm); @@ -141,6 +147,7 @@ namespace ESM { mIsWerewolf = false; mDisposition = 0; + mCrimeDispositionModifier = 0; mBounty = 0; mReputation = 0; mWerewolfKills = 0; diff --git a/components/esm3/npcstats.hpp b/components/esm3/npcstats.hpp index 425a62162b..b539602e06 100644 --- a/components/esm3/npcstats.hpp +++ b/components/esm3/npcstats.hpp @@ -33,6 +33,7 @@ namespace ESM std::map mFactions; int32_t mDisposition; + int32_t mCrimeDispositionModifier; std::array, ESM::Skill::Length> mSkills; int32_t mBounty; int32_t mReputation; From 145f7b5672235f2ce17e4953a9677b93a0c0e820 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 11:35:03 +0100 Subject: [PATCH 130/231] Avoid using osg::ref_ptr when reference is enough --- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/effectmanager.cpp | 4 +- apps/openmw/mwrender/util.cpp | 61 +++++++++++------------ apps/openmw/mwrender/util.hpp | 9 ++-- apps/openmw/mwworld/projectilemanager.cpp | 2 +- 5 files changed, 37 insertions(+), 41 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 5b0e1f82bd..7fa8e43c37 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1575,7 +1575,7 @@ namespace MWRender // Notify that this animation has attached magic effects mHasMagicEffects = true; - overrideFirstRootTexture(texture, mResourceSystem, node); + overrideFirstRootTexture(texture, mResourceSystem, *node); } void Animation::removeEffect(int effectId) diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index e5b8431c84..83a4091402 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -52,9 +52,9 @@ namespace MWRender node->accept(assignVisitor); if (isMagicVFX) - overrideFirstRootTexture(textureOverride, mResourceSystem, node); + overrideFirstRootTexture(textureOverride, mResourceSystem, *node); else - overrideTexture(textureOverride, mResourceSystem, node); + overrideTexture(textureOverride, mResourceSystem, *node); mParentNode->addChild(trans); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index cd03784e8c..f1a1177f85 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -11,41 +11,40 @@ namespace MWRender { - - class TextureOverrideVisitor : public osg::NodeVisitor + namespace { - public: - TextureOverrideVisitor(std::string_view texture, Resource::ResourceSystem* resourcesystem) - : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) - , mTexture(texture) - , mResourcesystem(resourcesystem) + class TextureOverrideVisitor : public osg::NodeVisitor { - } - - void apply(osg::Node& node) override - { - int index = 0; - osg::ref_ptr nodePtr(&node); - if (node.getUserValue("overrideFx", index)) + public: + TextureOverrideVisitor(std::string_view texture, Resource::ResourceSystem* resourcesystem) + : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) + , mTexture(texture) + , mResourcesystem(resourcesystem) { - if (index == 1) - overrideTexture(mTexture, mResourcesystem, std::move(nodePtr)); } - traverse(node); - } - std::string_view mTexture; - Resource::ResourceSystem* mResourcesystem; - }; - void overrideFirstRootTexture( - std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node) - { - TextureOverrideVisitor overrideVisitor(texture, resourceSystem); - node->accept(overrideVisitor); + void apply(osg::Node& node) override + { + int index = 0; + if (node.getUserValue("overrideFx", index)) + { + if (index == 1) + overrideTexture(mTexture, mResourcesystem, node); + } + traverse(node); + } + std::string_view mTexture; + Resource::ResourceSystem* mResourcesystem; + }; } - void overrideTexture( - std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node) + void overrideFirstRootTexture(std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::Node& node) + { + TextureOverrideVisitor overrideVisitor(texture, resourceSystem); + node.accept(overrideVisitor); + } + + void overrideTexture(std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::Node& node) { if (texture.empty()) return; @@ -58,14 +57,14 @@ namespace MWRender tex->setName("diffuseMap"); osg::ref_ptr stateset; - if (node->getStateSet()) - stateset = new osg::StateSet(*node->getStateSet(), osg::CopyOp::SHALLOW_COPY); + if (const osg::StateSet* const src = node.getStateSet()) + stateset = new osg::StateSet(*src, osg::CopyOp::SHALLOW_COPY); else stateset = new osg::StateSet; stateset->setTextureAttribute(0, tex, osg::StateAttribute::OVERRIDE); - node->setStateSet(stateset); + node.setStateSet(stateset); } bool shouldAddMSAAIntermediateTarget() diff --git a/apps/openmw/mwrender/util.hpp b/apps/openmw/mwrender/util.hpp index 64edaf8e18..fc43680d67 100644 --- a/apps/openmw/mwrender/util.hpp +++ b/apps/openmw/mwrender/util.hpp @@ -2,9 +2,8 @@ #define OPENMW_MWRENDER_UTIL_H #include -#include -#include +#include namespace osg { @@ -21,11 +20,9 @@ namespace MWRender // Overrides the texture of nodes in the mesh that had the same NiTexturingProperty as the first NiTexturingProperty // of the .NIF file's root node, if it had a NiTexturingProperty. Used for applying "particle textures" to magic // effects. - void overrideFirstRootTexture( - std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); + void overrideFirstRootTexture(std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::Node& node); - void overrideTexture( - std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::ref_ptr node); + void overrideTexture(std::string_view texture, Resource::ResourceSystem* resourceSystem, osg::Node& node); // Node callback to entirely skip the traversal. class NoTraverseCallback : public osg::NodeCallback diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index ed7914b89c..fdb9d92741 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -255,7 +255,7 @@ namespace MWWorld SceneUtil::AssignControllerSourcesVisitor assignVisitor(state.mEffectAnimationTime); state.mNode->accept(assignVisitor); - MWRender::overrideFirstRootTexture(texture, mResourceSystem, std::move(projectile)); + MWRender::overrideFirstRootTexture(texture, mResourceSystem, *projectile); } void ProjectileManager::update(State& state, float duration) From 81a483fc7f194417c158e10bb14397cf5950bb49 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 13:00:30 +0100 Subject: [PATCH 131/231] Remove unused vfs argument from correctMeshPath --- apps/navmeshtool/worldspacedata.cpp | 2 +- apps/openmw/mwclass/activator.cpp | 3 +-- apps/openmw/mwclass/classmodel.hpp | 5 +--- apps/openmw/mwclass/creature.cpp | 4 +--- apps/openmw/mwclass/esm4base.hpp | 2 ++ apps/openmw/mwclass/esm4npc.cpp | 3 +-- apps/openmw/mwclass/npc.cpp | 12 ++++------ apps/openmw/mwlua/types/activator.cpp | 6 ++--- apps/openmw/mwlua/types/apparatus.cpp | 4 ++-- apps/openmw/mwlua/types/armor.cpp | 5 ++-- apps/openmw/mwlua/types/book.cpp | 5 ++-- apps/openmw/mwlua/types/clothing.cpp | 5 ++-- apps/openmw/mwlua/types/container.cpp | 9 ++----- apps/openmw/mwlua/types/creature.cpp | 7 ++---- apps/openmw/mwlua/types/door.cpp | 14 ++++------- apps/openmw/mwlua/types/ingredient.cpp | 4 ++-- apps/openmw/mwlua/types/light.cpp | 5 ++-- apps/openmw/mwlua/types/lockpick.cpp | 5 ++-- apps/openmw/mwlua/types/misc.cpp | 4 ++-- apps/openmw/mwlua/types/potion.cpp | 5 ++-- apps/openmw/mwlua/types/probe.cpp | 5 ++-- apps/openmw/mwlua/types/repair.cpp | 7 ++---- apps/openmw/mwlua/types/static.cpp | 7 ++---- apps/openmw/mwlua/types/terminal.cpp | 9 ++----- apps/openmw/mwlua/types/weapon.cpp | 5 ++-- apps/openmw/mwmechanics/activespells.cpp | 5 +--- apps/openmw/mwmechanics/actors.cpp | 11 ++------- apps/openmw/mwmechanics/character.cpp | 13 ++++------ apps/openmw/mwmechanics/spellcasting.cpp | 25 ++++++++----------- apps/openmw/mwmechanics/spelleffects.cpp | 12 +++------- apps/openmw/mwmechanics/summoning.cpp | 5 +--- apps/openmw/mwrender/actoranimation.cpp | 3 +-- apps/openmw/mwrender/esm4npcanimation.cpp | 4 ++-- apps/openmw/mwrender/npcanimation.cpp | 29 ++++++++--------------- apps/openmw/mwrender/objectpaging.cpp | 2 +- apps/openmw/mwworld/groundcoverstore.cpp | 6 ++--- apps/openmw/mwworld/projectilemanager.cpp | 7 ++---- apps/openmw/mwworld/worldimp.cpp | 5 ++-- components/misc/resourcehelpers.cpp | 2 +- components/misc/resourcehelpers.hpp | 2 +- 40 files changed, 93 insertions(+), 180 deletions(-) diff --git a/apps/navmeshtool/worldspacedata.cpp b/apps/navmeshtool/worldspacedata.cpp index 7f579f8fde..0b3a1202d0 100644 --- a/apps/navmeshtool/worldspacedata.cpp +++ b/apps/navmeshtool/worldspacedata.cpp @@ -131,7 +131,7 @@ namespace NavMeshTool osg::ref_ptr shape = [&] { try { - return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model, &vfs)); + return bulletShapeManager.getShape(Misc::ResourceHelpers::correctMeshPath(model)); } catch (const std::exception& e) { diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index fc6cfadb55..a0ee260260 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -145,12 +145,11 @@ namespace MWClass = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const ESM::RefId* creatureId = nullptr; - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const ESM::Creature& iter : store.get()) { if (!iter.mModel.empty() - && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(iter.mModel, vfs))) + && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(iter.mModel))) { creatureId = !iter.mOriginal.empty() ? &iter.mOriginal : &iter.mId; break; diff --git a/apps/openmw/mwclass/classmodel.hpp b/apps/openmw/mwclass/classmodel.hpp index 5d1019ee1d..65c2f87a14 100644 --- a/apps/openmw/mwclass/classmodel.hpp +++ b/apps/openmw/mwclass/classmodel.hpp @@ -1,8 +1,6 @@ #ifndef OPENMW_MWCLASS_CLASSMODEL_H #define OPENMW_MWCLASS_CLASSMODEL_H -#include "../mwbase/environment.hpp" - #include "../mwworld/livecellref.hpp" #include "../mwworld/ptr.hpp" @@ -19,8 +17,7 @@ namespace MWClass const MWWorld::LiveCellRef* ref = ptr.get(); if (!ref->mBase->mModel.empty()) - return Misc::ResourceHelpers::correctMeshPath( - ref->mBase->mModel, MWBase::Environment::get().getResourceSystem()->getVFS()); + return Misc::ResourceHelpers::correctMeshPath(ref->mBase->mModel); return {}; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index ed601a9255..c5ce954baa 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -644,12 +644,10 @@ namespace MWClass const std::string model = getModel(ptr); if (!model.empty()) { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const ESM::Creature& creature : store.get()) { if (creature.mId != ourId && creature.mOriginal != ourId && !creature.mModel.empty() - && Misc::StringUtils::ciEqual( - model, Misc::ResourceHelpers::correctMeshPath(creature.mModel, vfs))) + && Misc::StringUtils::ciEqual(model, Misc::ResourceHelpers::correctMeshPath(creature.mModel))) { const ESM::RefId& fallbackId = !creature.mOriginal.empty() ? creature.mOriginal : creature.mId; sound = store.get().begin(); diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index 9c02af963d..7059ae07cb 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -6,6 +6,8 @@ #include #include +#include "../mwbase/environment.hpp" + #include "../mwgui/tooltips.hpp" #include "../mwworld/cellstore.hpp" diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 638144eb66..71ac0e317c 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -175,8 +175,7 @@ namespace MWClass model = data.mTraits->mModel; else model = data.mIsFemale ? data.mRace->mModelFemale : data.mRace->mModelMale; - const VFS::Manager* vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - return Misc::ResourceHelpers::correctMeshPath(model, vfs); + return Misc::ResourceHelpers::correctMeshPath(model); } std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 7c6f16b06f..cbb1f4b307 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -431,24 +431,22 @@ namespace MWClass models.push_back(Settings::models().mXbaseanimfemale); models.push_back(Settings::models().mXbaseanim); - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - if (!npc->mBase->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(npc->mBase->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(npc->mBase->mModel)); if (!npc->mBase->mHead.empty()) { const ESM::BodyPart* head = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHead); if (head) - models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(head->mModel)); } if (!npc->mBase->mHair.empty()) { const ESM::BodyPart* hair = MWBase::Environment::get().getESMStore()->get().search(npc->mBase->mHair); if (hair) - models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(hair->mModel)); } bool female = (npc->mBase->mFlags & ESM::NPC::Female); @@ -487,7 +485,7 @@ namespace MWClass const ESM::BodyPart* part = MWBase::Environment::get().getESMStore()->get().search(partname); if (part && !part->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); } } } @@ -501,7 +499,7 @@ namespace MWClass { const ESM::BodyPart* part = *it; if (part && !part->mModel.empty()) - models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel, vfs)); + models.push_back(Misc::ResourceHelpers::correctMeshPath(part->mModel)); } } } diff --git a/apps/openmw/mwlua/types/activator.cpp b/apps/openmw/mwlua/types/activator.cpp index a9edc1493f..5ffae0ccd7 100644 --- a/apps/openmw/mwlua/types/activator.cpp +++ b/apps/openmw/mwlua/types/activator.cpp @@ -43,8 +43,6 @@ namespace MWLua { void addActivatorBindings(sol::table activator, const Context& context) { - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - activator["createRecordDraft"] = tableToActivator; addRecordFunctionBinding(activator, context); @@ -54,8 +52,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Activator& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Activator& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Activator& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/apparatus.cpp b/apps/openmw/mwlua/types/apparatus.cpp index 10bdbcdd29..05eae8b2aa 100644 --- a/apps/openmw/mwlua/types/apparatus.cpp +++ b/apps/openmw/mwlua/types/apparatus.cpp @@ -38,8 +38,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Apparatus& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Apparatus& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Apparatus& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/armor.cpp b/apps/openmw/mwlua/types/armor.cpp index 4808107ebf..b0df533327 100644 --- a/apps/openmw/mwlua/types/armor.cpp +++ b/apps/openmw/mwlua/types/armor.cpp @@ -95,9 +95,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Armor& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Armor& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Armor& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index 4fe2f9d071..dfdbcff1ca 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -104,9 +104,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Book& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property([](const ESM::Book& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Book& rec) -> std::string { diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp index 7f4d6e7002..74b03148cb 100644 --- a/apps/openmw/mwlua/types/clothing.cpp +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -90,9 +90,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Clothing& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Clothing& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Clothing& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Clothing& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp index 25a1c1adce..ac2e75dea4 100644 --- a/apps/openmw/mwlua/types/container.cpp +++ b/apps/openmw/mwlua/types/container.cpp @@ -5,10 +5,7 @@ #include #include -#include -#include #include -#include namespace sol { @@ -42,8 +39,6 @@ namespace MWLua }; container["capacity"] = container["getCapacity"]; // for compatibility; should be removed later - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(container, context); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Container"); @@ -53,8 +48,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Container& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Container& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Container& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index 332a9b9b14..54a3c37750 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -29,8 +29,6 @@ namespace MWLua { "Humanoid", ESM::Creature::Humanoid }, })); - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(creature, context); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Creature"); @@ -39,9 +37,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Creature& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Creature& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Creature& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Creature& rec) -> std::string { return rec.mScript.serializeText(); }); record["baseCreature"] = sol::readonly_property( diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index 5a2cfc8aee..a4185434c3 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -55,8 +55,6 @@ namespace MWLua return sol::make_object(lua, LCell{ &cell }); }; - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(door, context); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Door"); @@ -65,9 +63,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Door& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property([](const ESM::Door& rec) -> std::string { return rec.mScript.serializeText(); }); record["openSound"] = sol::readonly_property( @@ -95,8 +92,6 @@ namespace MWLua return sol::make_object(lua, LCell{ &cell }); }; - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(door, context, "ESM4Door"); sol::usertype record = context.mLua->sol().new_usertype("ESM4_Door"); @@ -106,9 +101,8 @@ namespace MWLua record["id"] = sol::readonly_property( [](const ESM4::Door& rec) -> std::string { return ESM::RefId(rec.mId).serializeText(); }); record["name"] = sol::readonly_property([](const ESM4::Door& rec) -> std::string { return rec.mFullName; }); - record["model"] = sol::readonly_property([vfs](const ESM4::Door& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM4::Door& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["isAutomatic"] = sol::readonly_property( [](const ESM4::Door& rec) -> bool { return rec.mDoorFlags & ESM4::Door::Flag_AutomaticDoor; }); } diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index 31791a19ea..72b9f27263 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -33,8 +33,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Ingredient& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Ingredient& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Ingredient& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/light.cpp b/apps/openmw/mwlua/types/light.cpp index 347bb61641..d2cf195219 100644 --- a/apps/openmw/mwlua/types/light.cpp +++ b/apps/openmw/mwlua/types/light.cpp @@ -31,9 +31,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Light& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Light& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Light& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Light& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/lockpick.cpp b/apps/openmw/mwlua/types/lockpick.cpp index 786471461a..6de1f7f670 100644 --- a/apps/openmw/mwlua/types/lockpick.cpp +++ b/apps/openmw/mwlua/types/lockpick.cpp @@ -31,9 +31,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Lockpick& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Lockpick& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Lockpick& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Lockpick& rec) -> std::string { diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp index d359534638..9e6b2d6ae5 100644 --- a/apps/openmw/mwlua/types/misc.cpp +++ b/apps/openmw/mwlua/types/misc.cpp @@ -83,8 +83,8 @@ namespace MWLua record["id"] = sol::readonly_property( [](const ESM::Miscellaneous& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Miscellaneous& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM::Miscellaneous& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property( [](const ESM::Miscellaneous& rec) -> std::string { return rec.mScript.serializeText(); }); diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index 022af56b02..33302a3d34 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -73,9 +73,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Potion& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Potion& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Potion& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwlua/types/probe.cpp b/apps/openmw/mwlua/types/probe.cpp index 668e58c98c..6a3784b41a 100644 --- a/apps/openmw/mwlua/types/probe.cpp +++ b/apps/openmw/mwlua/types/probe.cpp @@ -31,9 +31,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Probe& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property([](const ESM::Probe& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Probe& rec) -> std::string { diff --git a/apps/openmw/mwlua/types/repair.cpp b/apps/openmw/mwlua/types/repair.cpp index 75d0a17c49..5e97e8c787 100644 --- a/apps/openmw/mwlua/types/repair.cpp +++ b/apps/openmw/mwlua/types/repair.cpp @@ -6,8 +6,6 @@ #include #include -#include -#include namespace sol { @@ -31,9 +29,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Repair& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["mwscript"] = sol::readonly_property([](const ESM::Repair& rec) -> std::string { return rec.mScript.serializeText(); }); record["icon"] = sol::readonly_property([vfs](const ESM::Repair& rec) -> std::string { diff --git a/apps/openmw/mwlua/types/static.cpp b/apps/openmw/mwlua/types/static.cpp index 76dac4fa00..960218ca68 100644 --- a/apps/openmw/mwlua/types/static.cpp +++ b/apps/openmw/mwlua/types/static.cpp @@ -21,8 +21,6 @@ namespace MWLua { void addStaticBindings(sol::table stat, const Context& context) { - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(stat, context); sol::usertype record = context.mLua->sol().new_usertype("ESM3_Static"); @@ -30,8 +28,7 @@ namespace MWLua = [](const ESM::Static& rec) -> std::string { return "ESM3_Static[" + rec.mId.toDebugString() + "]"; }; record["id"] = sol::readonly_property([](const ESM::Static& rec) -> std::string { return rec.mId.serializeText(); }); - record["model"] = sol::readonly_property([vfs](const ESM::Static& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Static& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); } } diff --git a/apps/openmw/mwlua/types/terminal.cpp b/apps/openmw/mwlua/types/terminal.cpp index b0f8e3be0b..02a9465b91 100644 --- a/apps/openmw/mwlua/types/terminal.cpp +++ b/apps/openmw/mwlua/types/terminal.cpp @@ -6,8 +6,6 @@ #include #include -#include "apps/openmw/mwworld/esmstore.hpp" - namespace sol { template <> @@ -21,9 +19,6 @@ namespace MWLua void addESM4TerminalBindings(sol::table term, const Context& context) { - - auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - addRecordFunctionBinding(term, context, "ESM4Terminal"); sol::usertype record = context.mLua->sol().new_usertype("ESM4_Terminal"); @@ -38,8 +33,8 @@ namespace MWLua record["resultText"] = sol::readonly_property([](const ESM4::Terminal& rec) -> std::string { return rec.mResultText; }); record["name"] = sol::readonly_property([](const ESM4::Terminal& rec) -> std::string { return rec.mFullName; }); - record["model"] = sol::readonly_property([vfs](const ESM4::Terminal& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); + record["model"] = sol::readonly_property([](const ESM4::Terminal& rec) -> std::string { + return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); } } diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index 993f1ecc95..de9b2efb95 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -129,9 +129,8 @@ namespace MWLua record["id"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mId.serializeText(); }); record["name"] = sol::readonly_property([](const ESM::Weapon& rec) -> std::string { return rec.mName; }); - record["model"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { - return Misc::ResourceHelpers::correctMeshPath(rec.mModel, vfs); - }); + record["model"] = sol::readonly_property( + [](const ESM::Weapon& rec) -> std::string { return Misc::ResourceHelpers::correctMeshPath(rec.mModel); }); record["icon"] = sol::readonly_property([vfs](const ESM::Weapon& rec) -> std::string { return Misc::ResourceHelpers::correctIconPath(rec.mIcon, vfs); }); diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index decbae765b..727bf95ed0 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -321,11 +321,8 @@ namespace MWMechanics ESM::RefId::stringRefId("VFX_Reflect")); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(ptr); if (animation && !reflectStatic->mModel.empty()) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - animation->addEffect(Misc::ResourceHelpers::correctMeshPath(reflectStatic->mModel, vfs), + animation->addEffect(Misc::ResourceHelpers::correctMeshPath(reflectStatic->mModel), ESM::MagicEffect::Reflect, false); - } caster.getClass().getCreatureStats(caster).getActiveSpells().addSpell(*reflected); } if (removedSpell) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index e0baac0764..76226df2ee 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -211,11 +211,8 @@ namespace const ESM::Static* const fx = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Soul_Trap")); if (fx != nullptr) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), "", + world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", creature.getRefData().getPosition().asVec3()); - } MWBase::Environment::get().getSoundManager()->playSound3D( creature.getRefData().getPosition().asVec3(), ESM::RefId::stringRefId("conjuration hit"), 1.f, 1.f); @@ -1825,12 +1822,8 @@ namespace MWMechanics const ESM::Static* fx = MWBase::Environment::get().getESMStore()->get().search( ESM::RefId::stringRefId("VFX_Summon_End")); if (fx) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); MWBase::Environment::get().getWorld()->spawnEffect( - Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), "", - ptr.getRefData().getPosition().asVec3()); - } + Misc::ResourceHelpers::correctMeshPath(fx->mModel), "", ptr.getRefData().getPosition().asVec3()); // Remove the summoned creature's summoned creatures as well MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2f39bb3eef..58a5dfbba4 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1615,18 +1615,13 @@ namespace MWMechanics const ESM::Static* castStatic = world->getStore().get().find(ESM::RefId::stringRefId("VFX_Hands")); - const VFS::Manager* const vfs - = MWBase::Environment::get().getResourceSystem()->getVFS(); - if (mAnimation->getNode("Bip01 L Hand")) - mAnimation->addEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, - "Bip01 L Hand", effect->mParticle); + mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), + -1, false, "Bip01 L Hand", effect->mParticle); if (mAnimation->getNode("Bip01 R Hand")) - mAnimation->addEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), -1, false, - "Bip01 R Hand", effect->mParticle); + mAnimation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), + -1, false, "Bip01 R Hand", effect->mParticle); } // first effect used for casting animation const ESM::ENAMstruct& firstEffect = effects->front(); diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index 52e371b6e9..e4e07b162f 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -41,7 +41,6 @@ namespace MWMechanics const ESM::EffectList& effects, const MWWorld::Ptr& ignore, ESM::RangeType rangeType) const { const auto world = MWBase::Environment::get().getWorld(); - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); std::map> toApply; int index = -1; for (const ESM::ENAMstruct& effectInfo : effects.mList) @@ -75,12 +74,12 @@ namespace MWMechanics { if (effectInfo.mRange == ESM::RT_Target) world->spawnEffect( - Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture, mHitPosition, 1.0f); + Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, 1.0f); continue; } else - world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel, vfs), texture, - mHitPosition, static_cast(effectInfo.mArea * 2)); + world->spawnEffect(Misc::ResourceHelpers::correctMeshPath(areaStatic->mModel), texture, mHitPosition, + static_cast(effectInfo.mArea * 2)); // Play explosion sound (make sure to use NoTrack, since we will delete the projectile now) { @@ -532,7 +531,6 @@ namespace MWMechanics { const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); std::vector addedEffects; - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); for (const ESM::ENAMstruct& effectData : effects) { @@ -547,15 +545,15 @@ namespace MWMechanics // check if the effect was already added if (std::find(addedEffects.begin(), addedEffects.end(), - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs)) + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel)) != addedEffects.end()) continue; MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(mCaster); if (animation) { - animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mIndex, - false, {}, effect->mParticle); + animation->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), effect->mIndex, false, + {}, effect->mParticle); } else { @@ -585,13 +583,13 @@ namespace MWMechanics } scale = std::max(scale, 1.f); MWBase::Environment::get().getWorld()->spawnEffect( - Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), effect->mParticle, pos, scale); + Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), effect->mParticle, pos, scale); } if (animation && !mCaster.getClass().isActor()) animation->addSpellCastGlow(effect); - addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs)); + addedEffects.push_back(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel)); MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); if (!effect->mCastSound.empty()) @@ -628,11 +626,8 @@ namespace MWMechanics { // Don't play particle VFX unless the effect is new or it should be looping. if (playNonLooping || loop) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel, vfs), magicEffect.mIndex, - loop, {}, magicEffect.mParticle); - } + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(castStatic->mModel), magicEffect.mIndex, loop, + {}, magicEffect.mParticle); } } } diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index db9ec3e588..334bdf8839 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -285,11 +285,8 @@ namespace const ESM::Static* absorbStatic = esmStore.get().find(ESM::RefId::stringRefId("VFX_Absorb")); MWRender::Animation* animation = MWBase::Environment::get().getWorld()->getAnimation(target); if (animation && !absorbStatic->mModel.empty()) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - animation->addEffect(Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel, vfs), - ESM::MagicEffect::SpellAbsorption, false); - } + animation->addEffect( + Misc::ResourceHelpers::correctMeshPath(absorbStatic->mModel), ESM::MagicEffect::SpellAbsorption, false); const ESM::Spell* spell = esmStore.get().search(spellId); int spellCost = 0; if (spell) @@ -461,10 +458,7 @@ namespace MWMechanics const ESM::Static* fx = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_end")); if (fx) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1); - } + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), -1); } } else if (caster == getPlayer()) diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp index 9b641e5e5c..85a8d971a9 100644 --- a/apps/openmw/mwmechanics/summoning.cpp +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -105,10 +105,7 @@ namespace MWMechanics const ESM::Static* fx = world->getStore().get().search(ESM::RefId::stringRefId("VFX_Summon_Start")); if (fx) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel, vfs), -1, false); - } + anim->addEffect(Misc::ResourceHelpers::correctMeshPath(fx->mModel), -1, false); } } catch (std::exception& e) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index 600ae6f0ed..fa316fe649 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -130,8 +130,7 @@ namespace MWRender if (bodypart == nullptr || bodypart->mData.mType != ESM::BodyPart::MT_Armor) return std::string(); if (!bodypart->mModel.empty()) - return Misc::ResourceHelpers::correctMeshPath( - bodypart->mModel, MWBase::Environment::get().getResourceSystem()->getVFS()); + return Misc::ResourceHelpers::correctMeshPath(bodypart->mModel); } } } diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 3ea8f829ce..550959787e 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -12,8 +12,8 @@ #include #include +#include "../mwbase/environment.hpp" #include "../mwclass/esm4npc.hpp" -#include "../mwworld/customdata.hpp" #include "../mwworld/esmstore.hpp" namespace MWRender @@ -51,7 +51,7 @@ namespace MWRender if (model.empty()) return; mResourceSystem->getSceneManager()->getInstance( - Misc::ResourceHelpers::correctMeshPath(model, mResourceSystem->getVFS()), mObjectRoot.get()); + Misc::ResourceHelpers::correctMeshPath(model), mObjectRoot.get()); } template diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d1cd5fed60..01bdcec665 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -48,7 +48,7 @@ namespace { - std::string getVampireHead(const ESM::RefId& race, bool female, const VFS::Manager& vfs) + std::string getVampireHead(const ESM::RefId& race, bool female) { static std::map, const ESM::BodyPart*> sVampireMapping; @@ -78,7 +78,7 @@ namespace const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; if (!bodyPart) return std::string(); - return Misc::ResourceHelpers::correctMeshPath(bodyPart->mModel, &vfs); + return Misc::ResourceHelpers::correctMeshPath(bodyPart->mModel); } } @@ -467,7 +467,7 @@ namespace MWRender { const ESM::BodyPart* bp = store.get().search(headName); if (bp) - mHeadModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS()); + mHeadModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel); else Log(Debug::Warning) << "Warning: Failed to load body part '" << headName << "'"; } @@ -476,12 +476,12 @@ namespace MWRender { const ESM::BodyPart* bp = store.get().search(hairName); if (bp) - mHairModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel, mResourceSystem->getVFS()); + mHairModel = Misc::ResourceHelpers::correctMeshPath(bp->mModel); else Log(Debug::Warning) << "Warning: Failed to load body part '" << hairName << "'"; } - const std::string vampireHead = getVampireHead(mNpc->mRace, isFemale, *mResourceSystem->getVFS()); + const std::string vampireHead = getVampireHead(mNpc->mRace, isFemale); if (!isWerewolf && isVampire && !vampireHead.empty()) mHeadModel = vampireHead; @@ -494,8 +494,7 @@ namespace MWRender std::string smodel = defaultSkeleton; if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) smodel = Misc::ResourceHelpers::correctActorModelPath( - Misc::ResourceHelpers::correctMeshPath(mNpc->mModel, mResourceSystem->getVFS()), - mResourceSystem->getVFS()); + Misc::ResourceHelpers::correctMeshPath(mNpc->mModel), mResourceSystem->getVFS()); setObjectRoot(smodel, true, true, false); @@ -646,9 +645,8 @@ namespace MWRender if (store != inv.end() && (part = *store).getType() == ESM::Light::sRecordId) { const ESM::Light* light = part.get()->mBase; - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); addOrReplaceIndividualPart(ESM::PRT_Shield, MWWorld::InventoryStore::Slot_CarriedLeft, 1, - Misc::ResourceHelpers::correctMeshPath(light->mModel, vfs), false, nullptr, true); + Misc::ResourceHelpers::correctMeshPath(light->mModel), false, nullptr, true); if (mObjectParts[ESM::PRT_Shield]) addExtraLight(mObjectParts[ESM::PRT_Shield]->getNode()->asGroup(), SceneUtil::LightCommon(*light)); } @@ -666,13 +664,9 @@ namespace MWRender { if (mPartPriorities[part] < 1) { - const ESM::BodyPart* bodypart = parts[part]; - if (bodypart) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + if (const ESM::BodyPart* bodypart = parts[part]) addOrReplaceIndividualPart(static_cast(part), -1, 1, - Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs)); - } + Misc::ResourceHelpers::correctMeshPath(bodypart->mModel)); } } @@ -901,11 +895,8 @@ namespace MWRender } if (bodypart) - { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); addOrReplaceIndividualPart(static_cast(part.mPart), group, priority, - Misc::ResourceHelpers::correctMeshPath(bodypart->mModel, vfs), enchantedGlow, glowColor); - } + Misc::ResourceHelpers::correctMeshPath(bodypart->mModel), enchantedGlow, glowColor); else reserveIndividualPart((ESM::PartReferenceType)part.mPart, group, priority); } diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 99ebb94647..f1bccc13c9 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -623,7 +623,7 @@ namespace MWRender std::string model = getModel(type, ref.mRefID, store); if (model.empty()) continue; - model = Misc::ResourceHelpers::correctMeshPath(model, mSceneManager->getVFS()); + model = Misc::ResourceHelpers::correctMeshPath(model); if (activeGrid && type != ESM::REC_STAT) { diff --git a/apps/openmw/mwworld/groundcoverstore.cpp b/apps/openmw/mwworld/groundcoverstore.cpp index 85b9376f0d..17bf72b5d3 100644 --- a/apps/openmw/mwworld/groundcoverstore.cpp +++ b/apps/openmw/mwworld/groundcoverstore.cpp @@ -26,8 +26,6 @@ namespace MWWorld const ::EsmLoader::EsmData content = ::EsmLoader::loadEsmData(query, groundcoverFiles, fileCollections, readers, encoder, listener); - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - static constexpr std::string_view prefix = "grass\\"; for (const ESM::Static& stat : statics) { @@ -35,7 +33,7 @@ namespace MWWorld std::replace(model.begin(), model.end(), '/', '\\'); if (!model.starts_with(prefix)) continue; - mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model, vfs); + mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model); } for (const ESM::Static& stat : content.mStatics) @@ -44,7 +42,7 @@ namespace MWWorld std::replace(model.begin(), model.end(), '/', '\\'); if (!model.starts_with(prefix)) continue; - mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model, vfs); + mMeshCache[stat.mId] = Misc::ResourceHelpers::correctMeshPath(model); } for (const ESM::Cell& cell : content.mCells) diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index ed7914b89c..58048cdccf 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -210,8 +210,6 @@ namespace MWWorld if (state.mIdMagic.size() > 1) { - const VFS::Manager* const vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); - for (size_t iter = 1; iter != state.mIdMagic.size(); ++iter) { std::ostringstream nodeName; @@ -223,7 +221,7 @@ namespace MWWorld attachTo->accept(findVisitor); if (findVisitor.mFoundNode) mResourceSystem->getSceneManager()->getInstance( - Misc::ResourceHelpers::correctMeshPath(weapon->mModel, vfs), findVisitor.mFoundNode); + Misc::ResourceHelpers::correctMeshPath(weapon->mModel), findVisitor.mFoundNode); } } @@ -331,8 +329,7 @@ namespace MWWorld if (state.mIdMagic.size() > 1) { model = Misc::ResourceHelpers::correctMeshPath( - MWBase::Environment::get().getESMStore()->get().find(state.mIdMagic[1])->mModel, - MWBase::Environment::get().getResourceSystem()->getVFS()); + MWBase::Environment::get().getESMStore()->get().find(state.mIdMagic[1])->mModel); } state.mProjectileId = mPhysics->addProjectile(caster, pos, model, true); state.mToDelete = false; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index b6b7de24b4..e42f75eb97 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3632,9 +3632,8 @@ namespace MWWorld if (texture.empty()) texture = Fallback::Map::getString("Blood_Texture_0"); - std::string model = Misc::ResourceHelpers::correctMeshPath( - std::string{ Fallback::Map::getString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))) }, // [0, 2] - mResourceSystem->getVFS()); + std::string model = Misc::ResourceHelpers::correctMeshPath(std::string{ + Fallback::Map::getString("Blood_Model_" + std::to_string(Misc::Rng::rollDice(3))) } /*[0, 2]*/); mRendering->spawnEffect(model, texture, worldPosition, 1.0f, false); } diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index ce552df4f7..aa0e0dec7d 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -159,7 +159,7 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP return mdlname; } -std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath, const VFS::Manager* vfs) +std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath) { std::string res = "meshes\\"; res.append(resPath); diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 478569ed14..f2b576813b 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -33,7 +33,7 @@ namespace Misc std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs); // Adds "meshes\\". - std::string correctMeshPath(std::string_view resPath, const VFS::Manager* vfs); + std::string correctMeshPath(std::string_view resPath); // Adds "sound\\". std::string correctSoundPath(const std::string& resPath); From f80ba4e28c1926b3be3177fba39dc9bd71b2d3de Mon Sep 17 00:00:00 2001 From: jvoisin Date: Tue, 26 Dec 2023 13:54:23 +0000 Subject: [PATCH 132/231] Apply 1 suggestion(s) to 1 file(s) --- apps/opencs/model/world/columnimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index 89190023c6..215e4c3dfc 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -344,7 +344,7 @@ namespace CSMWorld QStringList selectionInfo; const std::vector& instances = record.get().selectedInstances; - for (std::string instance : instances) + for (const std::string& instance : instances) selectionInfo << QString::fromStdString(instance); data.setValue(selectionInfo); From 8f85c9194dd92c5f49dcd11fff16be2c88d5d2c5 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Tue, 26 Dec 2023 09:15:50 -0800 Subject: [PATCH 133/231] lua - add bindings to get frame duration --- apps/openmw/mwlua/luabindings.cpp | 1 + files/lua_api/openmw/core.lua | 5 +++++ files/lua_api/openmw/world.lua | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index a50459502b..6331bc5466 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -86,6 +86,7 @@ namespace MWLua api["getRealTime"] = []() { return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); }; + api["getRealFrameDuration"] = []() { return MWBase::Environment::get().getFrameDuration(); }; if (!global) return; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 18898b5002..f42f51b9b3 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -55,6 +55,11 @@ -- @function [parent=#core] getRealTime -- @return #number +--- +-- Frame duration in seconds +-- @function [parent=#core] getRealFrameDuration +-- @return #number + --- -- Get a GMST setting from content files. -- @function [parent=#core] getGMST diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 5baa624c5d..404b744eb8 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -111,6 +111,11 @@ -- @function [parent=#world] setGameTimeScale -- @param #number ratio +--- +-- Frame duration in seconds +-- @function [parent=#world] getRealFrameDuration +-- @return #number + --- -- Whether the world is paused (onUpdate doesn't work when the world is paused). -- @function [parent=#world] isWorldPaused From 1b594d874e769460fb59ab28b6822d453b6af386 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 26 Dec 2023 21:53:49 +0300 Subject: [PATCH 134/231] Use modified value for governing attribute training limit (#7742) --- CHANGELOG.md | 1 + apps/openmw/mwgui/trainingwindow.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b84b7296..3cd31a2856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ Bug #7712: Casting doesn't support spells and enchantments with no effects Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves + Bug #7742: Governing attribute training limit should use the modified attribute Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index c915619a1a..5395f6db1c 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -167,7 +167,7 @@ namespace MWGui // You can not train a skill above its governing attribute if (pcStats.getSkill(skill->mId).getBase() - >= pcStats.getAttribute(ESM::Attribute::indexToRefId(skill->mData.mAttribute)).getBase()) + >= pcStats.getAttribute(ESM::Attribute::indexToRefId(skill->mData.mAttribute)).getModified()) { MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage17}"); return; From 18345973615c89955332d93adb0b605b25b2d752 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Dec 2023 00:49:43 +0300 Subject: [PATCH 135/231] Move friendly fire logic to onHit --- apps/openmw/mwclass/creature.cpp | 19 ++++++++++----- apps/openmw/mwclass/npc.cpp | 6 +++-- apps/openmw/mwmechanics/combat.cpp | 23 +++++++++++++++++++ apps/openmw/mwmechanics/combat.hpp | 2 ++ .../mwmechanics/mechanicsmanagerimp.cpp | 18 +-------------- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index ed601a9255..8b80b9336b 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -353,16 +353,23 @@ namespace MWClass { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + // Self defense + bool setOnPcHitMe = true; + // NOTE: 'object' and/or 'attacker' may be empty. if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) + { stats.setAttacked(true); - // Self defense - bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits. - - // No retaliation for totally static creatures (they have no movement or attacks anyway) - if (isMobile(ptr) && !attacker.isEmpty()) - setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + // No retaliation for totally static creatures (they have no movement or attacks anyway) + if (isMobile(ptr)) + { + if (MWMechanics::friendlyHit(attacker, ptr, true)) + setOnPcHitMe = false; + else + setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + } + } // Attacker and target store each other as hitattemptactor if they have no one stored yet if (!attacker.isEmpty() && attacker.getClass().isActor()) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 7c6f16b06f..0295d3600f 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -682,14 +682,16 @@ namespace MWClass MWMechanics::CreatureStats& stats = getCreatureStats(ptr); bool wasDead = stats.isDead(); - // Note OnPcHitMe is not set for friendly hits. bool setOnPcHitMe = true; // NOTE: 'object' and/or 'attacker' may be empty. if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) { stats.setAttacked(true); - setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); + if (MWMechanics::friendlyHit(attacker, ptr, true)) + setOnPcHitMe = false; + else + setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } // Attacker and target store each other as hitattemptactor if they have no one stored yet diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 02279859b5..3ce55f8f6c 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -10,6 +10,7 @@ #include #include +#include "../mwbase/dialoguemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -649,4 +650,26 @@ namespace MWMechanics return std::make_pair(result, hitPos); } + + bool friendlyHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& target, bool complain) + { + const MWWorld::Ptr& player = getPlayer(); + if (attacker != player) + return false; + + std::set followersAttacker; + MWBase::Environment::get().getMechanicsManager()->getActorsSidingWith(attacker, followersAttacker); + if (followersAttacker.find(target) == followersAttacker.end()) + return false; + + MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target); + statsTarget.friendlyHit(); + if (statsTarget.getFriendlyHits() >= 4) + return false; + + if (complain) + MWBase::Environment::get().getDialogueManager()->say(target, ESM::RefId::stringRefId("hit")); + return true; + } + } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 515d2e406c..92033c7e77 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -66,6 +66,8 @@ namespace MWMechanics // Similarly cursed hit target selection std::pair getHitContact(const MWWorld::Ptr& actor, float reach); + + bool friendlyHit(const MWWorld::Ptr& attacker, const MWWorld::Ptr& target, bool complain); } #endif diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 8a64af1cbd..49f176c6a6 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1394,26 +1394,10 @@ namespace MWMechanics if (target == player || !attacker.getClass().isActor()) return false; - MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target); - if (attacker == player) - { - std::set followersAttacker; - getActorsSidingWith(attacker, followersAttacker); - if (followersAttacker.find(target) != followersAttacker.end()) - { - statsTarget.friendlyHit(); - - if (statsTarget.getFriendlyHits() < 4) - { - MWBase::Environment::get().getDialogueManager()->say(target, ESM::RefId::stringRefId("hit")); - return false; - } - } - } - if (canCommitCrimeAgainst(target, attacker)) commitCrime(attacker, target, MWBase::MechanicsManager::OT_Assault); + MWMechanics::CreatureStats& statsTarget = target.getClass().getCreatureStats(target); AiSequence& seq = statsTarget.getAiSequence(); if (!attacker.isEmpty() From 6a16686107f6bb5b60aed6f1ac43eae0c0e078d4 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 09:32:18 +0100 Subject: [PATCH 136/231] Use settings values to declare string settings --- apps/opencs/model/prefs/state.cpp | 13 ++++++------- apps/opencs/model/prefs/state.hpp | 2 +- apps/opencs/model/prefs/stringsetting.cpp | 2 +- apps/opencs/model/prefs/stringsetting.hpp | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 1c922ee8d2..9b7475dea8 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -440,13 +440,12 @@ void CSMPrefs::State::declare() declareShortcut("script-editor-uncomment", "Uncomment Selection", QKeySequence()); declareCategory("Models"); - declareString("baseanim", "base animations", "meshes/base_anim.nif") - .setTooltip("3rd person base model with textkeys-data"); - declareString("baseanimkna", "base animations, kna", "meshes/base_animkna.nif") + declareString(mValues->mModels.mBaseanim, "base animations").setTooltip("3rd person base model with textkeys-data"); + declareString(mValues->mModels.mBaseanimkna, "base animations, kna") .setTooltip("3rd person beast race base model with textkeys-data"); - declareString("baseanimfemale", "base animations, female", "meshes/base_anim_female.nif") + declareString(mValues->mModels.mBaseanimfemale, "base animations, female") .setTooltip("3rd person female base model with textkeys-data"); - declareString("wolfskin", "base animations, wolf", "meshes/wolf/skin.nif").setTooltip("3rd person werewolf skin"); + declareString(mValues->mModels.mWolfskin, "base animations, wolf").setTooltip("3rd person werewolf skin"); } void CSMPrefs::State::declareCategory(const std::string& key) @@ -547,13 +546,13 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( } CSMPrefs::StringSetting& CSMPrefs::State::declareString( - const std::string& key, const QString& label, const std::string& default_) + Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); CSMPrefs::StringSetting* setting - = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::StringSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index a5fd5b0936..62db966fcd 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -75,7 +75,7 @@ namespace CSMPrefs ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); - StringSetting& declareString(const std::string& key, const QString& label, const std::string& default_); + StringSetting& declareString(Settings::SettingValue& value, const QString& label); ModifierSetting& declareModifier(Settings::SettingValue& value, const QString& label); diff --git a/apps/opencs/model/prefs/stringsetting.cpp b/apps/opencs/model/prefs/stringsetting.cpp index 56c46bb6af..10bd8cb558 100644 --- a/apps/opencs/model/prefs/stringsetting.cpp +++ b/apps/opencs/model/prefs/stringsetting.cpp @@ -12,7 +12,7 @@ #include "state.hpp" CSMPrefs::StringSetting::StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { diff --git a/apps/opencs/model/prefs/stringsetting.hpp b/apps/opencs/model/prefs/stringsetting.hpp index 25e1a362ff..0a7d2a4935 100644 --- a/apps/opencs/model/prefs/stringsetting.hpp +++ b/apps/opencs/model/prefs/stringsetting.hpp @@ -24,7 +24,7 @@ namespace CSMPrefs public: explicit StringSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); StringSetting& setTooltip(const std::string& tooltip); From 13c8e04b272472e8f57743c1596e77421c573c6c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 24 Dec 2023 17:20:57 +0100 Subject: [PATCH 137/231] Make traits and base data optional for ESM4 NPC Fallout 3 is not fully supported and it causes failures to load NPCs. Log errors and make sure there is no nullptr dereference. --- apps/openmw/mwclass/esm4npc.cpp | 47 +++++++++++++++-------- apps/openmw/mwrender/esm4npcanimation.cpp | 33 ++++++++-------- apps/openmw/mwrender/esm4npcanimation.hpp | 9 ++++- 3 files changed, 53 insertions(+), 36 deletions(-) diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 638144eb66..eca33d0701 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -88,26 +88,34 @@ namespace MWClass auto data = std::make_unique(); const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); - auto npcRecs = withBaseTemplates(ptr.get()->mBase); + const ESM4::Npc* const base = ptr.get()->mBase; + auto npcRecs = withBaseTemplates(base); data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::Template_UseTraits); + + if (data->mTraits == nullptr) + Log(Debug::Warning) << "Traits are not found for ESM4 NPC base record: \"" << base->mEditorId << "\" (" + << ESM::RefId(base->mId) << ")"; + data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::Template_UseBaseData); - if (!data->mTraits) - throw std::runtime_error("ESM4 NPC traits not found"); - if (!data->mBaseData) - throw std::runtime_error("ESM4 NPC base data not found"); + if (data->mBaseData == nullptr) + Log(Debug::Warning) << "Base data is not found for ESM4 NPC base record: \"" << base->mEditorId << "\" (" + << ESM::RefId(base->mId) << ")"; - data->mRace = store->get().find(data->mTraits->mRace); - if (data->mTraits->mIsTES4) - data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; - else if (data->mTraits->mIsFONV) - data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; - else if (data->mTraits->mIsFO4) - data->mIsFemale - = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5 - else - data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; + if (data->mTraits != nullptr) + { + data->mRace = store->get().find(data->mTraits->mRace); + if (data->mTraits->mIsTES4) + data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; + else if (data->mTraits->mIsFONV) + data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; + else if (data->mTraits->mIsFO4) + data->mIsFemale + = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5 + else + data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; + } if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::Template_UseInventory)) { @@ -116,7 +124,7 @@ namespace MWClass if (auto* armor = ESM4Impl::resolveLevelled(ESM::FormId::fromUint32(item.item))) data->mEquippedArmor.push_back(armor); - else if (data->mTraits->mIsTES4) + else if (data->mTraits != nullptr && data->mTraits->mIsTES4) { const auto* clothing = ESM4Impl::resolveLevelled( ESM::FormId::fromUint32(item.item)); @@ -170,6 +178,8 @@ namespace MWClass std::string ESM4Npc::getModel(const MWWorld::ConstPtr& ptr) const { const ESM4NpcCustomData& data = getCustomData(ptr); + if (data.mTraits == nullptr) + return {}; std::string_view model; if (data.mTraits->mIsTES4) model = data.mTraits->mModel; @@ -181,6 +191,9 @@ namespace MWClass std::string_view ESM4Npc::getName(const MWWorld::ConstPtr& ptr) const { - return getCustomData(ptr).mBaseData->mFullName; + const ESM4::Npc* const baseData = getCustomData(ptr).mBaseData; + if (baseData == nullptr) + return {}; + return baseData->mFullName; } } diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 3ea8f829ce..3a3ed37344 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -13,7 +13,6 @@ #include #include "../mwclass/esm4npc.hpp" -#include "../mwworld/customdata.hpp" #include "../mwworld/esmstore.hpp" namespace MWRender @@ -28,11 +27,13 @@ namespace MWRender void ESM4NpcAnimation::updateParts() { - if (!mObjectRoot.get()) + if (mObjectRoot == nullptr) return; const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); + if (traits == nullptr) + return; if (traits->mIsTES4) - updatePartsTES4(); + updatePartsTES4(*traits); else if (traits->mIsFONV) { // Not implemented yet @@ -42,7 +43,7 @@ namespace MWRender // There is no easy way to distinguish TES5 and FO3. // In case of FO3 the function shouldn't crash the game and will // only lead to the NPC not being rendered. - updatePartsTES5(); + updatePartsTES5(*traits); } } @@ -65,9 +66,8 @@ namespace MWRender return rec->mModel; } - void ESM4NpcAnimation::updatePartsTES4() + void ESM4NpcAnimation::updatePartsTES4(const ESM4::Npc& traits) { - const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); bool isFemale = MWClass::ESM4Npc::isFemale(mPtr); @@ -77,13 +77,13 @@ namespace MWRender insertPart(bodyPart.mesh); for (const ESM4::Race::BodyPart& bodyPart : race->mHeadParts) insertPart(bodyPart.mesh); - if (!traits->mHair.isZeroOrUnset()) + if (!traits.mHair.isZeroOrUnset()) { const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); - if (const ESM4::Hair* hair = store->get().search(traits->mHair)) + if (const ESM4::Hair* hair = store->get().search(traits.mHair)) insertPart(hair->mModel); else - Log(Debug::Error) << "Hair not found: " << ESM::RefId(traits->mHair); + Log(Debug::Error) << "Hair not found: " << ESM::RefId(traits.mHair); } for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) @@ -111,11 +111,10 @@ namespace MWRender } } - void ESM4NpcAnimation::updatePartsTES5() + void ESM4NpcAnimation::updatePartsTES5(const ESM4::Npc& traits) { const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); - const ESM4::Npc* traits = MWClass::ESM4Npc::getTraitsRecord(mPtr); const ESM4::Race* race = MWClass::ESM4Npc::getRace(mPtr); bool isFemale = MWClass::ESM4Npc::isFemale(mPtr); @@ -132,9 +131,9 @@ namespace MWRender Log(Debug::Error) << "ArmorAddon not found: " << ESM::RefId(armaId); continue; } - bool compatibleRace = arma->mRacePrimary == traits->mRace; + bool compatibleRace = arma->mRacePrimary == traits.mRace; for (auto r : arma->mRaces) - if (r == traits->mRace) + if (r == traits.mRace) compatibleRace = true; if (compatibleRace) armorAddons.push_back(arma); @@ -143,12 +142,12 @@ namespace MWRender for (const ESM4::Armor* armor : MWClass::ESM4Npc::getEquippedArmor(mPtr)) findArmorAddons(armor); - if (!traits->mWornArmor.isZeroOrUnset()) + if (!traits.mWornArmor.isZeroOrUnset()) { - if (const ESM4::Armor* armor = store->get().search(traits->mWornArmor)) + if (const ESM4::Armor* armor = store->get().search(traits.mWornArmor)) findArmorAddons(armor); else - Log(Debug::Error) << "Worn armor not found: " << ESM::RefId(traits->mWornArmor); + Log(Debug::Error) << "Worn armor not found: " << ESM::RefId(traits.mWornArmor); } if (!race->mSkin.isZeroOrUnset()) { @@ -183,7 +182,7 @@ namespace MWRender std::set usedHeadPartTypes; if (usedParts & ESM4::Armor::TES5_Hair) usedHeadPartTypes.insert(ESM4::HeadPart::Type_Hair); - insertHeadParts(traits->mHeadParts, usedHeadPartTypes); + insertHeadParts(traits.mHeadParts, usedHeadPartTypes); insertHeadParts(isFemale ? race->mHeadPartIdsFemale : race->mHeadPartIdsMale, usedHeadPartTypes); } } diff --git a/apps/openmw/mwrender/esm4npcanimation.hpp b/apps/openmw/mwrender/esm4npcanimation.hpp index 7bb3fe1103..274c060b06 100644 --- a/apps/openmw/mwrender/esm4npcanimation.hpp +++ b/apps/openmw/mwrender/esm4npcanimation.hpp @@ -3,6 +3,11 @@ #include "animation.hpp" +namespace ESM4 +{ + struct Npc; +} + namespace MWRender { class ESM4NpcAnimation : public Animation @@ -18,8 +23,8 @@ namespace MWRender void insertHeadParts(const std::vector& partIds, std::set& usedHeadPartTypes); void updateParts(); - void updatePartsTES4(); - void updatePartsTES5(); + void updatePartsTES4(const ESM4::Npc& traits); + void updatePartsTES5(const ESM4::Npc& traits); }; } From 94c052dfefee9eaca0aa2f46adc9de8b6b383c9c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Dec 2023 01:50:30 +0300 Subject: [PATCH 138/231] Classify the damage passed to Class::onHit --- apps/openmw/mwbase/luamanager.hpp | 3 ++- apps/openmw/mwclass/creature.cpp | 9 ++++++--- apps/openmw/mwclass/creature.hpp | 3 ++- apps/openmw/mwclass/npc.cpp | 8 +++++--- apps/openmw/mwclass/npc.hpp | 3 ++- apps/openmw/mwmechanics/combat.cpp | 6 ++++-- apps/openmw/mwmechanics/damagesourcetype.hpp | 15 +++++++++++++++ apps/openmw/mwmechanics/spelleffects.cpp | 3 ++- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/class.hpp | 9 ++++++--- 10 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 apps/openmw/mwmechanics/damagesourcetype.hpp diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index e4b16ff725..6db85d77ca 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -61,7 +61,8 @@ namespace MWBase // TODO: notify LuaManager about other events // virtual void objectOnHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, - // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) = 0; + // const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful, + // DamageSourceType sourceType) = 0; struct InputEvent { diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 8b80b9336b..cbf5a3d63d 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -284,7 +284,8 @@ namespace MWClass if (!success) { - victim.getClass().onHit(victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false); + victim.getClass().onHit( + victim, 0.0f, false, MWWorld::Ptr(), ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee); MWMechanics::reduceWeaponCondition(0.f, false, weapon, ptr); return; } @@ -345,11 +346,13 @@ namespace MWClass MWMechanics::diseaseContact(victim, ptr); - victim.getClass().onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); + victim.getClass().onHit( + victim, damage, healthdmg, weapon, ptr, hitPosition, true, MWMechanics::DamageSourceType::Melee); } void Creature::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const { MWMechanics::CreatureStats& stats = getCreatureStats(ptr); diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index bd7101e93d..b407852242 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -67,7 +67,8 @@ namespace MWClass const osg::Vec3f& hitPosition, bool success) const override; void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const override; + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const override; std::unique_ptr activate(const MWWorld::Ptr& ptr, const MWWorld::Ptr& actor) const override; ///< Generate action for activation diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 0295d3600f..df074ec8bf 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -599,7 +599,8 @@ namespace MWClass float damage = 0.0f; if (!success) { - othercls.onHit(victim, damage, false, weapon, ptr, osg::Vec3f(), false); + othercls.onHit( + victim, damage, false, weapon, ptr, osg::Vec3f(), false, MWMechanics::DamageSourceType::Melee); MWMechanics::reduceWeaponCondition(damage, false, weapon, ptr); MWMechanics::resistNormalWeapon(victim, ptr, weapon, damage); return; @@ -672,11 +673,12 @@ namespace MWClass MWMechanics::diseaseContact(victim, ptr); - othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true); + othercls.onHit(victim, damage, healthdmg, weapon, ptr, hitPosition, true, MWMechanics::DamageSourceType::Melee); } void Npc::onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const { MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); MWMechanics::CreatureStats& stats = getCreatureStats(ptr); diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index eb8cafc9d1..ca0d0ac95d 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -82,7 +82,8 @@ namespace MWClass const osg::Vec3f& hitPosition, bool success) const override; void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const override; + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const override; void getModelsToPreload(const MWWorld::Ptr& ptr, std::vector& models) const override; ///< Get a list of models to preload that this object may use (directly or indirectly). default implementation: diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 3ce55f8f6c..3f17df96fd 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -231,7 +231,8 @@ namespace MWMechanics if (Misc::Rng::roll0to99(world->getPrng()) >= getHitChance(attacker, victim, skillValue)) { - victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false); + victim.getClass().onHit(victim, damage, false, projectile, attacker, osg::Vec3f(), false, + MWMechanics::DamageSourceType::Ranged); MWMechanics::reduceWeaponCondition(damage, false, weapon, attacker); return; } @@ -287,7 +288,8 @@ namespace MWMechanics victim.getClass().getContainerStore(victim).add(projectile, 1); } - victim.getClass().onHit(victim, damage, true, projectile, attacker, hitPosition, true); + victim.getClass().onHit( + victim, damage, true, projectile, attacker, hitPosition, true, MWMechanics::DamageSourceType::Ranged); } } diff --git a/apps/openmw/mwmechanics/damagesourcetype.hpp b/apps/openmw/mwmechanics/damagesourcetype.hpp new file mode 100644 index 0000000000..e140a8106f --- /dev/null +++ b/apps/openmw/mwmechanics/damagesourcetype.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_MWMECHANICS_DAMAGESOURCETYPE_H +#define OPENMW_MWMECHANICS_DAMAGESOURCETYPE_H + +namespace MWMechanics +{ + enum class DamageSourceType + { + Unspecified, + Melee, + Ranged, + Magical, + }; +} + +#endif diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index db9ec3e588..88d978733c 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -356,7 +356,8 @@ namespace // Notify the target actor they've been hit bool isHarmful = magicEffect->mData.mFlags & ESM::MagicEffect::Harmful; if (target.getClass().isActor() && target != caster && !caster.isEmpty() && isHarmful) - target.getClass().onHit(target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true); + target.getClass().onHit( + target, 0.0f, true, MWWorld::Ptr(), caster, osg::Vec3f(), true, MWMechanics::DamageSourceType::Magical); // Apply resistances if (!(effect.mFlags & ESM::ActiveEffect::Flag_Ignore_Resistances)) { diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index e6080ce447..5fbda6d570 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -124,7 +124,7 @@ namespace MWWorld } void Class::onHit(const Ptr& ptr, float damage, bool ishealth, const Ptr& object, const Ptr& attacker, - const osg::Vec3f& hitPosition, bool successful) const + const osg::Vec3f& hitPosition, bool successful, const MWMechanics::DamageSourceType sourceType) const { throw std::runtime_error("class cannot be hit"); } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index 7b7e9135ba..87e70b3198 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -13,6 +13,8 @@ #include "ptr.hpp" #include "../mwmechanics/aisetting.hpp" +#include "../mwmechanics/damagesourcetype.hpp" + #include #include @@ -142,11 +144,12 @@ namespace MWWorld /// (default implementation: throw an exception) virtual void onHit(const MWWorld::Ptr& ptr, float damage, bool ishealth, const MWWorld::Ptr& object, - const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful) const; + const MWWorld::Ptr& attacker, const osg::Vec3f& hitPosition, bool successful, + const MWMechanics::DamageSourceType sourceType) const; ///< Alerts \a ptr that it's being hit for \a damage points to health if \a ishealth is /// true (else fatigue) by \a object (sword, arrow, etc). \a attacker specifies the - /// actor responsible for the attack, and \a successful specifies if the hit is - /// successful or not. + /// actor responsible for the attack. \a successful specifies if the hit is + /// successful or not. \a sourceType classifies the damage source. virtual void block(const Ptr& ptr) const; ///< Play the appropriate sound for a blocked attack, depending on the currently equipped shield From d5428b23d8b7e03d5416b0e2897deaf147256554 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 27 Dec 2023 01:51:57 +0300 Subject: [PATCH 139/231] Disable voiced responses to magical friendly hits (bug #7646) Disable ranged friendly fire --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 4 +++- apps/openmw/mwclass/npc.cpp | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b84b7296..e86dc8d354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ Bug #7641: loopgroup loops the animation one time too many for actors Bug #7642: Items in repair and recharge menus aren't sorted alphabetically Bug #7643: Can't enchant items with constant effect on self magic effects for non-player character + Bug #7646: Follower voices pain sounds when attacked with magic Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index cbf5a3d63d..4e8cdabba0 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -367,7 +367,9 @@ namespace MWClass // No retaliation for totally static creatures (they have no movement or attacks anyway) if (isMobile(ptr)) { - if (MWMechanics::friendlyHit(attacker, ptr, true)) + bool complain = sourceType == MWMechanics::DamageSourceType::Melee; + bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged; + if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain)) setOnPcHitMe = false; else setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index df074ec8bf..f3cd1534d1 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -690,7 +690,9 @@ namespace MWClass if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker)) { stats.setAttacked(true); - if (MWMechanics::friendlyHit(attacker, ptr, true)) + bool complain = sourceType == MWMechanics::DamageSourceType::Melee; + bool supportFriendlyFire = sourceType != MWMechanics::DamageSourceType::Ranged; + if (supportFriendlyFire && MWMechanics::friendlyHit(attacker, ptr, complain)) setOnPcHitMe = false; else setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); From b2f972df3d6f4b25f0ace3ac5c46472ac29efc20 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Wed, 27 Dec 2023 17:14:33 +0000 Subject: [PATCH 140/231] Update custom-shader-effects.rst to reflect new change --- docs/source/reference/modding/custom-shader-effects.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/source/reference/modding/custom-shader-effects.rst b/docs/source/reference/modding/custom-shader-effects.rst index 0bd1fbec85..60a306a97a 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -6,10 +6,9 @@ This node must have the prefix `omw:data` and have a valid JSON object that foll .. note:: - This is a new feature to inject OpenMW-specific shader effects. Only a single - effect is currently supported. By default, the shader effects will propogate - to all a node's children. Other propogation modes and effects will come with - future releases. + This is a new feature to inject OpenMW-specific shader effects. By default, + the shader effects will propagate to all of a node's children. + Other propagation modes and effects will come with future releases. Soft Effect From 9d3ede7575e8e6c2fb890c32e12be031bccaa00d Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 27 Dec 2023 19:11:49 +0000 Subject: [PATCH 141/231] Revert "Merge branch 'skating-olympics' into 'master'" This reverts merge request !3631 --- CHANGELOG.md | 1 - apps/openmw/mwbase/world.hpp | 6 +- apps/openmw/mwmechanics/character.cpp | 6 +- apps/openmw/mwphysics/movementsolver.cpp | 11 +-- apps/openmw/mwphysics/mtphysics.cpp | 88 +++-------------------- apps/openmw/mwphysics/mtphysics.hpp | 8 +-- apps/openmw/mwphysics/physicssystem.cpp | 17 ++--- apps/openmw/mwphysics/physicssystem.hpp | 3 +- apps/openmw/mwphysics/ptrholder.hpp | 19 +---- apps/openmw/mwworld/projectilemanager.cpp | 7 +- apps/openmw/mwworld/worldimp.cpp | 4 +- apps/openmw/mwworld/worldimp.hpp | 4 +- 12 files changed, 32 insertions(+), 142 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8d6f8ccf..f0181da99a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ------ Bug #2623: Snowy Granius doesn't prioritize conjuration spells - Bug #3330: Backward displacement of the character when attacking in 3rd person Bug #3438: NPCs can't hit bull netch with melee weapons Bug #3842: Body part skeletons override the main skeleton Bug #4127: Weapon animation looks choppy diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 23961554ca..14e3b2b3b7 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -295,13 +295,9 @@ namespace MWBase /// relative to \a referenceObject (but the object may be placed somewhere else if the wanted location is /// obstructed). - virtual void queueMovement( - const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false) - = 0; + virtual void queueMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) = 0; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. - /// \param duration The duration this speed shall be held, starting at current simulation time - /// \param jump Whether the movement shall be run over time, or immediately added as inertia instead virtual void updateAnimatedCollisionShape(const MWWorld::Ptr& ptr) = 0; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 58a5dfbba4..20c7fd0a92 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2416,9 +2416,7 @@ namespace MWMechanics } if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) - { - world->queueMovement(mPtr, vec, duration, mInJump && mJumpState == JumpState_None); - } + world->queueMovement(mPtr, vec); } movement = vec; @@ -2491,7 +2489,7 @@ namespace MWMechanics } // Update movement - world->queueMovement(mPtr, movement, duration, mInJump && mJumpState == JumpState_None); + world->queueMovement(mPtr, movement); } mSkipAnim = false; diff --git a/apps/openmw/mwphysics/movementsolver.cpp b/apps/openmw/mwphysics/movementsolver.cpp index 63ffb055dd..c0b5014b31 100644 --- a/apps/openmw/mwphysics/movementsolver.cpp +++ b/apps/openmw/mwphysics/movementsolver.cpp @@ -170,8 +170,6 @@ namespace MWPhysics } // Now that we have the effective movement vector, apply wind forces to it - // TODO: This will cause instability in idle animations and other in-place animations. Should include a flag for - // this when queueing up movement if (worldData.mIsInStorm && velocity.length() > 0) { osg::Vec3f stormDirection = worldData.mStormDirection; @@ -202,8 +200,7 @@ namespace MWPhysics for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations) { - osg::Vec3f diff = velocity * remainingTime; - osg::Vec3f nextpos = newPosition + diff; + osg::Vec3f nextpos = newPosition + velocity * remainingTime; bool underwater = newPosition.z() < swimlevel; // If not able to fly, don't allow to swim up into the air @@ -215,11 +212,7 @@ namespace MWPhysics continue; // velocity updated, calculate nextpos again } - // Note, we use an epsilon of 1e-6 instead of std::numeric_limits::epsilon() to avoid doing extremely - // small steps. But if we make it any larger we'll start rejecting subtle movements from e.g. idle - // animations. Note that, although both these comparisons to 1e-6 are logically the same, they test separate - // floating point accuracy cases. - if (diff.length2() > 1e-6 && (newPosition - nextpos).length2() > 1e-6) + if ((newPosition - nextpos).length2() > 0.0001) { // trace to where character would go if there were no obstructions tracer.doTrace(actor.mCollisionObject, newPosition, nextpos, collisionWorld, actor.mIsOnGround); diff --git a/apps/openmw/mwphysics/mtphysics.cpp b/apps/openmw/mwphysics/mtphysics.cpp index 238d00deac..52b96d9d13 100644 --- a/apps/openmw/mwphysics/mtphysics.cpp +++ b/apps/openmw/mwphysics/mtphysics.cpp @@ -25,7 +25,6 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/datetimemanager.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -36,7 +35,6 @@ #include "object.hpp" #include "physicssystem.hpp" #include "projectile.hpp" -#include "ptrholder.hpp" namespace MWPhysics { @@ -197,67 +195,6 @@ namespace void operator()(MWPhysics::ProjectileSimulation& /*sim*/) const {} }; - struct InitMovement - { - int mSteps = 0; - float mDelta = 0.f; - float mSimulationTime = 0.f; - - // Returns how the actor or projectile wants to move between startTime and endTime - osg::Vec3f takeMovement(MWPhysics::PtrHolder& actor, float startTime, float endTime) const - { - osg::Vec3f movement = osg::Vec3f(); - actor.eraseMovementIf([&](MWPhysics::Movement& v) { - if (v.mJump) - return false; - float start = std::max(v.mSimulationTimeStart, startTime); - float stop = std::min(v.mSimulationTimeStop, endTime); - movement += v.mVelocity * (stop - start); - if (std::abs(stop - v.mSimulationTimeStop) < 0.0001f) - return true; - return false; - }); - - return movement; - } - - std::optional takeInertia(MWPhysics::PtrHolder& actor, float startTime) const - { - std::optional inertia = std::nullopt; - actor.eraseMovementIf([&](MWPhysics::Movement& v) { - if (v.mJump && v.mSimulationTimeStart >= startTime) - { - inertia = v.mVelocity; - return true; - } - return false; - }); - return inertia; - } - - void operator()(auto& sim) const - { - if (mSteps <= 0 || mDelta < 0.00001f) - return; - - auto locked = sim.lock(); - if (!locked.has_value()) - return; - - auto& [ptrHolder, frameDataRef] = *locked; - - // Because takeMovement() returns movement instead of velocity, convert it back to velocity for the - // movement solver - osg::Vec3f velocity - = takeMovement(*ptrHolder, mSimulationTime, mSimulationTime + mDelta * mSteps) / (mSteps * mDelta); - // takeInertia() returns a velocity and should be taken over the velocity calculated above to initiate a - // jump - auto inertia = takeInertia(*ptrHolder, mSimulationTime); - - frameDataRef.get().mMovement += inertia.value_or(velocity); - } - }; - struct PreStep { btCollisionWorld* mCollisionWorld; @@ -564,18 +501,18 @@ namespace MWPhysics return std::make_tuple(numSteps, actualDelta); } - void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, float simulationTime, - std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) + void PhysicsTaskScheduler::applyQueuedMovements(float& timeAccum, std::vector& simulations, + osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { assert(mSimulations != &simulations); waitForWorkers(); - prepareWork(timeAccum, simulationTime, simulations, frameStart, frameNumber, stats); + prepareWork(timeAccum, simulations, frameStart, frameNumber, stats); if (mWorkersSync != nullptr) mWorkersSync->wakeUpWorkers(); } - void PhysicsTaskScheduler::prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, + void PhysicsTaskScheduler::prepareWork(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats) { // This function run in the main thread. @@ -585,9 +522,6 @@ namespace MWPhysics double timeStart = mTimer->tick(); - // The simulation time when the movement solving begins. - float simulationTimeStart = simulationTime - timeAccum; - // start by finishing previous background computation if (mNumThreads != 0) { @@ -602,15 +536,10 @@ namespace MWPhysics timeAccum -= numSteps * newDelta; // init - const Visitors::InitPosition initPositionVisitor{ mCollisionWorld }; + const Visitors::InitPosition vis{ mCollisionWorld }; for (auto& sim : simulations) { - std::visit(initPositionVisitor, sim); - } - const Visitors::InitMovement initMovementVisitor{ numSteps, newDelta, simulationTimeStart }; - for (auto& sim : simulations) - { - std::visit(initMovementVisitor, sim); + std::visit(vis, sim); } mPrevStepCount = numSteps; mRemainingSteps = numSteps; @@ -623,10 +552,10 @@ namespace MWPhysics mNextJob.store(0, std::memory_order_release); if (mAdvanceSimulation) - { mWorldFrameData = std::make_unique(); + + if (mAdvanceSimulation) mBudgetCursor += 1; - } if (mNumThreads == 0) { @@ -935,7 +864,6 @@ namespace MWPhysics std::remove_if(mLOSCache.begin(), mLOSCache.end(), [](const LOSRequest& req) { return req.mStale; }), mLOSCache.end()); } - mTimeEnd = mTimer->tick(); if (mWorkersSync != nullptr) mWorkersSync->workIsDone(); diff --git a/apps/openmw/mwphysics/mtphysics.hpp b/apps/openmw/mwphysics/mtphysics.hpp index 986f2be973..57f3711096 100644 --- a/apps/openmw/mwphysics/mtphysics.hpp +++ b/apps/openmw/mwphysics/mtphysics.hpp @@ -46,8 +46,8 @@ namespace MWPhysics /// @param timeAccum accumulated time from previous run to interpolate movements /// @param actorsData per actor data needed to compute new positions /// @return new position of each actor - void applyQueuedMovements(float& timeAccum, float simulationTime, std::vector& simulations, - osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void applyQueuedMovements(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + unsigned int frameNumber, osg::Stats& stats); void resetSimulation(const ActorMap& actors); @@ -87,8 +87,8 @@ namespace MWPhysics void afterPostSim(); void syncWithMainThread(); void waitForWorkers(); - void prepareWork(float& timeAccum, float simulationTime, std::vector& simulations, - osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats); + void prepareWork(float& timeAccum, std::vector& simulations, osg::Timer_t frameStart, + unsigned int frameNumber, osg::Stats& stats); std::unique_ptr mWorldFrameData; std::vector* mSimulations = nullptr; diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index 9a85ee009f..2196834a50 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -43,7 +43,6 @@ #include "../mwrender/bulletdebugdraw.hpp" #include "../mwworld/class.hpp" -#include "../mwworld/datetimemanager.hpp" #include "actor.hpp" #include "collisiontype.hpp" @@ -624,20 +623,18 @@ namespace MWPhysics return false; } - void PhysicsSystem::queueObjectMovement( - const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) + void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity) { - float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); ActorMap::iterator found = mActors.find(ptr.mRef); if (found != mActors.end()) - found->second->queueMovement(velocity, start, start + duration, jump); + found->second->setVelocity(velocity); } void PhysicsSystem::clearQueuedMovement() { for (const auto& [_, actor] : mActors) { - actor->clearMovement(); + actor->setVelocity(osg::Vec3f()); actor->setInertialForce(osg::Vec3f()); } } @@ -725,10 +722,8 @@ namespace MWPhysics { std::vector& simulations = mSimulations[mSimulationsCounter++ % mSimulations.size()]; prepareSimulation(mTimeAccum >= mPhysicsDt, simulations); - float simulationTime = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime() + dt; // modifies mTimeAccum - mTaskScheduler->applyQueuedMovements( - mTimeAccum, simulationTime, simulations, frameStart, frameNumber, stats); + mTaskScheduler->applyQueuedMovements(mTimeAccum, simulations, frameStart, frameNumber, stats); } } @@ -912,7 +907,7 @@ namespace MWPhysics ->mValue.getFloat())) , mSlowFall(slowFall) , mRotation() - , mMovement() + , mMovement(actor.velocity()) , mWaterlevel(waterlevel) , mHalfExtentsZ(actor.getHalfExtents().z()) , mOldHeight(0) @@ -927,7 +922,7 @@ namespace MWPhysics ProjectileFrameData::ProjectileFrameData(Projectile& projectile) : mPosition(projectile.getPosition()) - , mMovement() + , mMovement(projectile.velocity()) , mCaster(projectile.getCasterCollisionObject()) , mCollisionObject(projectile.getCollisionObject()) , mProjectile(&projectile) diff --git a/apps/openmw/mwphysics/physicssystem.hpp b/apps/openmw/mwphysics/physicssystem.hpp index 7758c6dfd7..ad56581eb3 100644 --- a/apps/openmw/mwphysics/physicssystem.hpp +++ b/apps/openmw/mwphysics/physicssystem.hpp @@ -245,8 +245,7 @@ namespace MWPhysics /// Queues velocity movement for a Ptr. If a Ptr is already queued, its velocity will /// be overwritten. Valid until the next call to stepSimulation - void queueObjectMovement( - const MWWorld::Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false); + void queueObjectMovement(const MWWorld::Ptr& ptr, const osg::Vec3f& velocity); /// Clear the queued movements list without applying. void clearQueuedMovement(); diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 16c3db0691..fc8fd94c30 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -1,7 +1,6 @@ #ifndef OPENMW_MWPHYSICS_PTRHOLDER_H #define OPENMW_MWPHYSICS_PTRHOLDER_H -#include #include #include #include @@ -14,14 +13,6 @@ namespace MWPhysics { - struct Movement - { - osg::Vec3f mVelocity = osg::Vec3f(); - float mSimulationTimeStart = 0.f; // The time at which this movement begun - float mSimulationTimeStop = 0.f; // The time at which this movement finished - bool mJump = false; - }; - class PtrHolder { public: @@ -41,13 +32,9 @@ namespace MWPhysics btCollisionObject* getCollisionObject() const { return mCollisionObject.get(); } - void clearMovement() { mMovement = {}; } - void queueMovement(osg::Vec3f velocity, float simulationTimeStart, float simulationTimeStop, bool jump = false) - { - mMovement.push_back(Movement{ velocity, simulationTimeStart, simulationTimeStop, jump }); - } + void setVelocity(osg::Vec3f velocity) { mVelocity = velocity; } - void eraseMovementIf(const auto& predicate) { std::erase_if(mMovement, predicate); } + osg::Vec3f velocity() { return std::exchange(mVelocity, osg::Vec3f()); } void setSimulationPosition(const osg::Vec3f& position) { mSimulationPosition = position; } @@ -66,7 +53,7 @@ namespace MWPhysics protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; - std::list mMovement; + osg::Vec3f mVelocity; osg::Vec3f mSimulationPosition; osg::Vec3d mPosition; osg::Vec3d mPreviousPosition; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 1cb593f208..3cb08be5fa 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -30,7 +30,6 @@ #include #include "../mwworld/class.hpp" -#include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/manualref.hpp" @@ -455,8 +454,7 @@ namespace MWWorld } osg::Vec3f direction = orient * osg::Vec3f(0, 1, 0); direction.normalize(); - float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); - projectile->queueMovement(direction * speed, start, start + duration); + projectile->setVelocity(direction * speed); update(magicBoltState, duration); @@ -484,8 +482,7 @@ namespace MWWorld projectileState.mVelocity -= osg::Vec3f(0, 0, Constants::GravityConst * Constants::UnitsPerMeter * 0.1f) * duration; - float start = MWBase::Environment::get().getWorld()->getTimeManager()->getSimulationTime(); - projectile->queueMovement(projectileState.mVelocity, start, start + duration); + projectile->setVelocity(projectileState.mVelocity); // rotation does not work well for throwing projectiles - their roll angle will depend on shooting // direction. diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index e42f75eb97..07334396b7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1448,9 +1448,9 @@ namespace MWWorld return placed; } - void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump) + void World::queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) { - mPhysics->queueObjectMovement(ptr, velocity, duration, jump); + mPhysics->queueObjectMovement(ptr, velocity); } void World::updateAnimatedCollisionShape(const Ptr& ptr) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index aa5f9d56f0..4b9a0ccb98 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -383,11 +383,9 @@ namespace MWWorld float getMaxActivationDistance() const override; - void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity, float duration, bool jump = false) override; + void queueMovement(const Ptr& ptr, const osg::Vec3f& velocity) override; ///< Queues movement for \a ptr (in local space), to be applied in the next call to /// doPhysics. - /// \param duration The duration this speed shall be held, starting at current simulation time - /// \param jump Whether the movement shall be run over time, or immediately added as inertia instead void updateAnimatedCollisionShape(const Ptr& ptr) override; From 30cff6f6eeeb84b45dba38aec810df0abb201567 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 28 Dec 2023 01:05:45 +0300 Subject: [PATCH 142/231] Avoid crashes upon Weapon::canBeEquipped attack check for the inventory doll --- apps/openmw/mwclass/weapon.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 1cc2c86761..7e4c47993c 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -276,8 +276,8 @@ namespace MWClass return { 0, "#{sInventoryMessage1}" }; // Do not allow equip weapons from inventory during attack - if (MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc) - && MWBase::Environment::get().getWindowManager()->isGuiMode()) + if (npc.isInCell() && MWBase::Environment::get().getWindowManager()->isGuiMode() + && MWBase::Environment::get().getMechanicsManager()->isAttackingOrSpell(npc)) return { 0, "#{sCantEquipWeapWarning}" }; std::pair, bool> slots_ = getEquipmentSlots(ptr); From 310b8206dd341d2992e70882f1bfd1f9a57be2b9 Mon Sep 17 00:00:00 2001 From: alekulyn Date: Wed, 29 Nov 2023 08:43:33 -0600 Subject: [PATCH 143/231] Fix #7696 --- components/terrain/compositemaprenderer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/terrain/compositemaprenderer.cpp b/components/terrain/compositemaprenderer.cpp index 5319820c83..34655a03d6 100644 --- a/components/terrain/compositemaprenderer.cpp +++ b/components/terrain/compositemaprenderer.cpp @@ -49,7 +49,8 @@ namespace Terrain double timeLeft = availableTime; - while (!mCompileSet.empty() && timeLeft > 0) + const auto deadline = std::chrono::steady_clock::now() + std::chrono::duration(availableTime); + while (!mCompileSet.empty() && std::chrono::steady_clock::now() < deadline) { osg::ref_ptr node = *mCompileSet.begin(); mCompileSet.erase(node); From f71862fb7659c959a804b42efb8c007745be5850 Mon Sep 17 00:00:00 2001 From: alekulyn Date: Wed, 27 Dec 2023 22:30:04 -0600 Subject: [PATCH 144/231] Remove unnecessary code --- components/terrain/compositemaprenderer.cpp | 17 +++-------------- components/terrain/compositemaprenderer.hpp | 2 +- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/components/terrain/compositemaprenderer.cpp b/components/terrain/compositemaprenderer.cpp index 34655a03d6..adf58162e8 100644 --- a/components/terrain/compositemaprenderer.cpp +++ b/components/terrain/compositemaprenderer.cpp @@ -43,12 +43,10 @@ namespace Terrain mImmediateCompileSet.erase(node); mMutex.unlock(); - compile(*node, renderInfo, nullptr); + compile(*node, renderInfo); mMutex.lock(); } - double timeLeft = availableTime; - const auto deadline = std::chrono::steady_clock::now() + std::chrono::duration(availableTime); while (!mCompileSet.empty() && std::chrono::steady_clock::now() < deadline) { @@ -56,7 +54,7 @@ namespace Terrain mCompileSet.erase(node); mMutex.unlock(); - compile(*node, renderInfo, &timeLeft); + compile(*node, renderInfo); mMutex.lock(); if (node->mCompiled < node->mDrawables.size()) @@ -69,7 +67,7 @@ namespace Terrain mTimer.setStartTick(); } - void CompositeMapRenderer::compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo, double* timeLeft) const + void CompositeMapRenderer::compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo) const { // if there are no more external references we can assume the texture is no longer required if (compositeMap.mTexture->referenceCount() <= 1) @@ -125,15 +123,6 @@ namespace Terrain ++compositeMap.mCompiled; compositeMap.mDrawables[i] = nullptr; - - if (timeLeft) - { - *timeLeft -= timer.time_s(); - timer.setStartTick(); - - if (*timeLeft <= 0) - break; - } } if (compositeMap.mCompiled == compositeMap.mDrawables.size()) compositeMap.mDrawables = std::vector>(); diff --git a/components/terrain/compositemaprenderer.hpp b/components/terrain/compositemaprenderer.hpp index eeecec75dc..1e33c717ec 100644 --- a/components/terrain/compositemaprenderer.hpp +++ b/components/terrain/compositemaprenderer.hpp @@ -38,7 +38,7 @@ namespace Terrain void drawImplementation(osg::RenderInfo& renderInfo) const override; - void compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo, double* timeLeft) const; + void compile(CompositeMap& compositeMap, osg::RenderInfo& renderInfo) const; /// Set the available time in seconds for compiling (non-immediate) composite maps each frame void setMinimumTimeAvailableForCompile(double time); From 02775c490bbd3a601436815807492664574a7517 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 28 Dec 2023 21:49:25 +0100 Subject: [PATCH 145/231] Discard additional tokens in non-expression contexts --- .../mwscript/test_scripts.cpp | 26 ++++++++++++++++++- components/compiler/lineparser.cpp | 13 +++++----- components/compiler/skipparser.cpp | 20 ++++++++++++-- components/compiler/skipparser.hpp | 6 ++++- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/apps/openmw_test_suite/mwscript/test_scripts.cpp b/apps/openmw_test_suite/mwscript/test_scripts.cpp index b623084fb9..dbed262235 100644 --- a/apps/openmw_test_suite/mwscript/test_scripts.cpp +++ b/apps/openmw_test_suite/mwscript/test_scripts.cpp @@ -19,7 +19,16 @@ namespace mErrorHandler.reset(); std::istringstream input(scriptBody); Compiler::Scanner scanner(mErrorHandler, input, mCompilerContext.getExtensions()); - scanner.scan(mParser); + try + { + scanner.scan(mParser); + } + catch (...) + { + if (!shouldFail) + logErrors(); + throw; + } if (mErrorHandler.isGood()) return CompiledScript(mParser.getProgram(), mParser.getLocals()); else if (!shouldFail) @@ -385,6 +394,12 @@ if (player->GameHour == 10) set player->GameHour to 20 endif +End)mwscript"; + + const std::string sIssue4996 = R"mwscript(---Begin issue4996 + +player-> SetPos, Z, myZ + 50 + End)mwscript"; const std::string sIssue5087 = R"mwscript(Begin Begin @@ -457,6 +472,9 @@ set a to 1 -+'\/.,><$@---!=\/?--------(){}------ show a +( GetDisabled == 1 ) +GetDisabled == 1 + End)mwscript"; TEST_F(MWScriptTest, mwscript_test_invalid) @@ -810,6 +828,12 @@ End)mwscript"; EXPECT_FALSE(!compile(sIssue4888)); } + TEST_F(MWScriptTest, mwscript_test_4996) + { + registerExtensions(); + EXPECT_FALSE(!compile(sIssue4996)); + } + TEST_F(MWScriptTest, mwscript_test_5087) { registerExtensions(); diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index 460dcfb2f9..90bdac1610 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -271,18 +271,14 @@ namespace Compiler mCode.insert(mCode.end(), code.begin(), code.end()); extensions->generateInstructionCode(keyword, mCode, mLiterals, mExplicit, optionals); + SkipParser skip(getErrorHandler(), getContext(), true); + scanner.scan(skip); + mState = EndState; return true; } - } - if (const Extensions* extensions = getContext().getExtensions()) - { char returnType; - std::string argumentType; - - bool hasExplicit = mState == ExplicitState; - if (extensions->isFunction(keyword, returnType, argumentType, hasExplicit)) { if (!hasExplicit && mState == ExplicitState) @@ -302,6 +298,9 @@ namespace Compiler int optionals = mExprParser.parseArguments(argumentType, scanner, code, keyword); mCode.insert(mCode.end(), code.begin(), code.end()); extensions->generateFunctionCode(keyword, mCode, mLiterals, mExplicit, optionals); + + SkipParser skip(getErrorHandler(), getContext(), true); + scanner.scan(skip); } mState = EndState; return true; diff --git a/components/compiler/skipparser.cpp b/components/compiler/skipparser.cpp index 0036e93dda..39d834c8ca 100644 --- a/components/compiler/skipparser.cpp +++ b/components/compiler/skipparser.cpp @@ -1,39 +1,55 @@ #include "skipparser.hpp" +#include "errorhandler.hpp" #include "scanner.hpp" namespace Compiler { - SkipParser::SkipParser(ErrorHandler& errorHandler, const Context& context) + SkipParser::SkipParser(ErrorHandler& errorHandler, const Context& context, bool reportStrayArguments) : Parser(errorHandler, context) + , mReportStrayArguments(reportStrayArguments) { } + void SkipParser::reportStrayArgument(const TokenLoc& loc) + { + if (mReportStrayArguments) + getErrorHandler().warning("Extra argument", loc); + } + bool SkipParser::parseInt(int value, const TokenLoc& loc, Scanner& scanner) { + reportStrayArgument(loc); return true; } bool SkipParser::parseFloat(float value, const TokenLoc& loc, Scanner& scanner) { + reportStrayArgument(loc); return true; } bool SkipParser::parseName(const std::string& name, const TokenLoc& loc, Scanner& scanner) { + reportStrayArgument(loc); return true; } bool SkipParser::parseKeyword(int keyword, const TokenLoc& loc, Scanner& scanner) { + reportStrayArgument(loc); return true; } bool SkipParser::parseSpecial(int code, const TokenLoc& loc, Scanner& scanner) { if (code == Scanner::S_newline) + { + if (mReportStrayArguments) + scanner.putbackSpecial(code, loc); return false; - + } + reportStrayArgument(loc); return true; } } diff --git a/components/compiler/skipparser.hpp b/components/compiler/skipparser.hpp index fdc5effc13..304ed40330 100644 --- a/components/compiler/skipparser.hpp +++ b/components/compiler/skipparser.hpp @@ -11,8 +11,12 @@ namespace Compiler class SkipParser : public Parser { + bool mReportStrayArguments; + + void reportStrayArgument(const TokenLoc& loc); + public: - SkipParser(ErrorHandler& errorHandler, const Context& context); + SkipParser(ErrorHandler& errorHandler, const Context& context, bool reportStrayArguments = false); bool parseInt(int value, const TokenLoc& loc, Scanner& scanner) override; ///< Handle an int token. From 56401a90a14b467e780283009849ddd1da1552f7 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 15 Dec 2023 23:14:44 +0100 Subject: [PATCH 146/231] Merge GenericObjectCache update and remove functions They are always called together. Single iteration over the items is more efficient along with locking the mutex only once. --- .../resource/testobjectcache.cpp | 46 ++++++---------- components/resource/objectcache.hpp | 54 ++++++------------- components/resource/resourcemanager.hpp | 6 +-- 3 files changed, 32 insertions(+), 74 deletions(-) diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp index 5b7741025a..25b6d87e52 100644 --- a/apps/openmw_test_suite/resource/testobjectcache.cpp +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -63,9 +63,7 @@ namespace Resource const double referenceTime = 1000; const double expiryDelay = 1; - - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); } @@ -92,16 +90,13 @@ namespace Resource const int key = 42; cache->addEntryToObjectCache(key, nullptr, referenceTime + expiryDelay); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime); + cache->update(referenceTime + expiryDelay, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + 2 * expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime + expiryDelay); + cache->update(referenceTime + 2 * expiryDelay, expiryDelay); EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); } @@ -117,12 +112,10 @@ namespace Resource cache->addEntryToObjectCache(key, value); value = nullptr; - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime); + cache->update(referenceTime + expiryDelay, expiryDelay); EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); } @@ -137,12 +130,10 @@ namespace Resource osg::ref_ptr value(new Object); cache->addEntryToObjectCache(key, value); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime); + cache->update(referenceTime + expiryDelay, expiryDelay); EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(value)); } @@ -158,12 +149,10 @@ namespace Resource cache->addEntryToObjectCache(key, value); value = nullptr; - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime + expiryDelay, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + cache->update(referenceTime + expiryDelay / 2, expiryDelay); EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); } @@ -177,12 +166,10 @@ namespace Resource const int key = 42; cache->addEntryToObjectCache(key, nullptr); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime + expiryDelay, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + cache->update(referenceTime + expiryDelay / 2, expiryDelay); EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); } @@ -196,16 +183,13 @@ namespace Resource const int key = 42; cache->addEntryToObjectCache(key, nullptr); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + cache->update(referenceTime, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); - cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + cache->update(referenceTime + expiryDelay / 2, expiryDelay); ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); - cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); - cache->removeExpiredObjectsInCache(referenceTime); + cache->update(referenceTime + expiryDelay, expiryDelay); EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index f8a5843395..08ef0dc060 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -48,48 +49,25 @@ namespace Resource { } - /** For each object in the cache which has an reference count greater than 1 - * (and therefore referenced by elsewhere in the application) set the time stamp - * for that object in the cache to specified time. - * This would typically be called once per frame by applications which are doing database paging, - * and need to prune objects that are no longer required. - * The time used should be taken from the FrameStamp::getReferenceTime().*/ - void updateTimeStampOfObjectsInCacheWithExternalReferences(double referenceTime) - { - // look for objects with external references and update their time stamp. - std::lock_guard lock(_objectCacheMutex); - for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) - { - // If ref count is greater than 1, the object has an external reference. - // If the timestamp is yet to be initialized, it needs to be updated too. - if ((itr->second.mValue != nullptr && itr->second.mValue->referenceCount() > 1) - || itr->second.mLastUsage == 0.0) - itr->second.mLastUsage = referenceTime; - } - } - - /** Removed object in the cache which have a time stamp at or before the specified expiry time. - * This would typically be called once per frame by applications which are doing database paging, - * and need to prune objects that are no longer required, and called after the a called - * after the call to updateTimeStampOfObjectsInCacheWithExternalReferences(expirtyTime).*/ - void removeExpiredObjectsInCache(double expiryTime) + // Update last usage timestamp using referenceTime for each cache time if they are not nullptr and referenced + // from somewhere else. Remove items with last usage > expiryTime. Note: last usage might be updated from other + // places so nullptr or not references elsewhere items are not always removed. + void update(double referenceTime, double expiryDelay) { std::vector> objectsToRemove; { + const double expiryTime = referenceTime - expiryDelay; std::lock_guard lock(_objectCacheMutex); - // Remove expired entries from object cache - typename ObjectCacheMap::iterator oitr = _objectCache.begin(); - while (oitr != _objectCache.end()) - { - if (oitr->second.mLastUsage <= expiryTime) - { - if (oitr->second.mValue != nullptr) - objectsToRemove.push_back(std::move(oitr->second.mValue)); - _objectCache.erase(oitr++); - } - else - ++oitr; - } + std::erase_if(_objectCache, [&](auto& v) { + Item& item = v.second; + if ((item.mValue != nullptr && item.mValue->referenceCount() > 1) || item.mLastUsage == 0) + item.mLastUsage = referenceTime; + if (item.mLastUsage > expiryTime) + return false; + if (item.mValue != nullptr) + objectsToRemove.push_back(std::move(item.mValue)); + return true; + }); } // note, actual unref happens outside of the lock objectsToRemove.clear(); diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index 55e4e142b5..b2427c308a 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -49,11 +49,7 @@ namespace Resource virtual ~GenericResourceManager() = default; /// Clear cache entries that have not been referenced for longer than expiryDelay. - void updateCache(double referenceTime) override - { - mCache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); - mCache->removeExpiredObjectsInCache(referenceTime - mExpiryDelay); - } + void updateCache(double referenceTime) override { mCache->update(referenceTime, mExpiryDelay); } /// Clear all cache entries. void clearCache() override { mCache->clear(); } From fd2fc63dd3c1c7d7788c4e16b979d82c90eafba2 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 13:50:58 +0100 Subject: [PATCH 147/231] Support heterogeneous lookup in GenericObjectCache --- .../resource/testobjectcache.cpp | 57 +++++++++++++++++++ components/resource/objectcache.hpp | 17 ++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp index 25b6d87e52..1c8cff6af0 100644 --- a/apps/openmw_test_suite/resource/testobjectcache.cpp +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -288,5 +288,62 @@ namespace Resource EXPECT_EQ(cache->lowerBound(4), std::nullopt); } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(std::string_view("key"), value); + EXPECT_EQ(cache->getRefFromObjectCache(key), value); + } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldKeyMoving) + { + osg::ref_ptr> cache(new GenericObjectCache); + std::string key(128, 'a'); + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(std::move(key), value); + EXPECT_EQ(key, ""); + EXPECT_EQ(cache->getRefFromObjectCache(std::string(128, 'a')), value); + } + + TEST(ResourceGenericObjectCacheTest, removeFromObjectCacheShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + ASSERT_EQ(cache->getRefFromObjectCache(key), value); + cache->removeFromObjectCache(std::string_view("key")); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + EXPECT_EQ(cache->getRefFromObjectCache(std::string_view("key")), value); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheOrNoneShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(std::string_view("key")), Optional(value)); + } + + TEST(ResourceGenericObjectCacheTest, checkInObjectCacheShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + const std::string key = "key"; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + EXPECT_TRUE(cache->checkInObjectCache(std::string_view("key"), 0)); + } } } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 08ef0dc060..066f5b1d3e 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -81,14 +81,19 @@ namespace Resource } /** Add a key,object,timestamp triple to the Registry::ObjectCache.*/ - void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0) + template + void addEntryToObjectCache(K&& key, osg::Object* object, double timestamp = 0.0) { std::lock_guard lock(_objectCacheMutex); - _objectCache[key] = Item{ object, timestamp }; + const auto it = _objectCache.find(key); + if (it == _objectCache.end()) + _objectCache.emplace_hint(it, std::forward(key), Item{ object, timestamp }); + else + it->second = Item{ object, timestamp }; } /** Remove Object from cache.*/ - void removeFromObjectCache(const KeyType& key) + void removeFromObjectCache(const auto& key) { std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); @@ -97,7 +102,7 @@ namespace Resource } /** Get an ref_ptr from the object cache*/ - osg::ref_ptr getRefFromObjectCache(const KeyType& key) + osg::ref_ptr getRefFromObjectCache(const auto& key) { std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); @@ -107,7 +112,7 @@ namespace Resource return nullptr; } - std::optional> getRefFromObjectCacheOrNone(const KeyType& key) + std::optional> getRefFromObjectCacheOrNone(const auto& key) { const std::lock_guard lock(_objectCacheMutex); const auto it = _objectCache.find(key); @@ -117,7 +122,7 @@ namespace Resource } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ - bool checkInObjectCache(const KeyType& key, double timeStamp) + bool checkInObjectCache(const auto& key, double timeStamp) { std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); From 2f0613c8d453f72a22f71076384e45c07a9b235b Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 14:18:29 +0100 Subject: [PATCH 148/231] Remove user defined destructor for GenericObjectCache --- components/resource/objectcache.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 066f5b1d3e..9cb74c762c 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -194,8 +194,6 @@ namespace Resource double mLastUsage; }; - virtual ~GenericObjectCache() {} - using ObjectCacheMap = std::map>; ObjectCacheMap _objectCache; From 7b1ee2780b16652406cbfae21e5936f425489aa5 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 14:17:12 +0100 Subject: [PATCH 149/231] Use ranged for loops in GenericObjectCache --- components/resource/objectcache.hpp | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 9cb74c762c..8f99e6dfda 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -139,26 +139,18 @@ namespace Resource void releaseGLObjects(osg::State* state) { std::lock_guard lock(_objectCacheMutex); - for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) - { - osg::Object* object = itr->second.mValue.get(); - object->releaseGLObjects(state); - } + for (const auto& [k, v] : _objectCache) + v.mValue->releaseGLObjects(state); } /** call node->accept(nv); for all nodes in the objectCache. */ void accept(osg::NodeVisitor& nv) { std::lock_guard lock(_objectCacheMutex); - for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) - { - if (osg::Object* object = itr->second.mValue.get()) - { - osg::Node* node = dynamic_cast(object); - if (node) + for (const auto& [k, v] : _objectCache) + if (osg::Object* const object = v.mValue.get()) + if (osg::Node* const node = dynamic_cast(object)) node->accept(nv); - } - } } /** call operator()(KeyType, osg::Object*) for each object in the cache. */ @@ -166,8 +158,8 @@ namespace Resource void call(Functor&& f) { std::lock_guard lock(_objectCacheMutex); - for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) - f(it->first, it->second.mValue.get()); + for (const auto& [k, v] : _objectCache) + f(k, v.mValue.get()); } /** Get the number of objects in the cache. */ From 45b1b4f1e0344caf3c8dd503ea3d9e167000badd Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 14:19:13 +0100 Subject: [PATCH 150/231] Remove redundant ObjectCacheMap alias --- components/resource/objectcache.hpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 8f99e6dfda..5374cef460 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -96,7 +96,7 @@ namespace Resource void removeFromObjectCache(const auto& key) { std::lock_guard lock(_objectCacheMutex); - typename ObjectCacheMap::iterator itr = _objectCache.find(key); + const auto itr = _objectCache.find(key); if (itr != _objectCache.end()) _objectCache.erase(itr); } @@ -105,7 +105,7 @@ namespace Resource osg::ref_ptr getRefFromObjectCache(const auto& key) { std::lock_guard lock(_objectCacheMutex); - typename ObjectCacheMap::iterator itr = _objectCache.find(key); + const auto itr = _objectCache.find(key); if (itr != _objectCache.end()) return itr->second.mValue; else @@ -125,7 +125,7 @@ namespace Resource bool checkInObjectCache(const auto& key, double timeStamp) { std::lock_guard lock(_objectCacheMutex); - typename ObjectCacheMap::iterator itr = _objectCache.find(key); + const auto itr = _objectCache.find(key); if (itr != _objectCache.end()) { itr->second.mLastUsage = timeStamp; @@ -186,9 +186,7 @@ namespace Resource double mLastUsage; }; - using ObjectCacheMap = std::map>; - - ObjectCacheMap _objectCache; + std::map> _objectCache; mutable std::mutex _objectCacheMutex; }; From 7a817d31470d1804e7944e6d1f116c59662dfcb9 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 25 Dec 2023 14:23:36 +0100 Subject: [PATCH 151/231] Apply project naming styleguide to GenericObjectCache --- components/resource/objectcache.hpp | 68 ++++++++++++++--------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 5374cef460..dffa0e9fdb 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -57,8 +57,8 @@ namespace Resource std::vector> objectsToRemove; { const double expiryTime = referenceTime - expiryDelay; - std::lock_guard lock(_objectCacheMutex); - std::erase_if(_objectCache, [&](auto& v) { + std::lock_guard lock(mMutex); + std::erase_if(mItems, [&](auto& v) { Item& item = v.second; if ((item.mValue != nullptr && item.mValue->referenceCount() > 1) || item.mLastUsage == 0) item.mLastUsage = referenceTime; @@ -76,18 +76,18 @@ namespace Resource /** Remove all objects in the cache regardless of having external references or expiry times.*/ void clear() { - std::lock_guard lock(_objectCacheMutex); - _objectCache.clear(); + std::lock_guard lock(mMutex); + mItems.clear(); } /** Add a key,object,timestamp triple to the Registry::ObjectCache.*/ template void addEntryToObjectCache(K&& key, osg::Object* object, double timestamp = 0.0) { - std::lock_guard lock(_objectCacheMutex); - const auto it = _objectCache.find(key); - if (it == _objectCache.end()) - _objectCache.emplace_hint(it, std::forward(key), Item{ object, timestamp }); + std::lock_guard lock(mMutex); + const auto it = mItems.find(key); + if (it == mItems.end()) + mItems.emplace_hint(it, std::forward(key), Item{ object, timestamp }); else it->second = Item{ object, timestamp }; } @@ -95,18 +95,18 @@ namespace Resource /** Remove Object from cache.*/ void removeFromObjectCache(const auto& key) { - std::lock_guard lock(_objectCacheMutex); - const auto itr = _objectCache.find(key); - if (itr != _objectCache.end()) - _objectCache.erase(itr); + std::lock_guard lock(mMutex); + const auto itr = mItems.find(key); + if (itr != mItems.end()) + mItems.erase(itr); } /** Get an ref_ptr from the object cache*/ osg::ref_ptr getRefFromObjectCache(const auto& key) { - std::lock_guard lock(_objectCacheMutex); - const auto itr = _objectCache.find(key); - if (itr != _objectCache.end()) + std::lock_guard lock(mMutex); + const auto itr = mItems.find(key); + if (itr != mItems.end()) return itr->second.mValue; else return nullptr; @@ -114,9 +114,9 @@ namespace Resource std::optional> getRefFromObjectCacheOrNone(const auto& key) { - const std::lock_guard lock(_objectCacheMutex); - const auto it = _objectCache.find(key); - if (it == _objectCache.end()) + const std::lock_guard lock(mMutex); + const auto it = mItems.find(key); + if (it == mItems.end()) return std::nullopt; return it->second.mValue; } @@ -124,9 +124,9 @@ namespace Resource /** Check if an object is in the cache, and if it is, update its usage time stamp. */ bool checkInObjectCache(const auto& key, double timeStamp) { - std::lock_guard lock(_objectCacheMutex); - const auto itr = _objectCache.find(key); - if (itr != _objectCache.end()) + std::lock_guard lock(mMutex); + const auto itr = mItems.find(key); + if (itr != mItems.end()) { itr->second.mLastUsage = timeStamp; return true; @@ -138,16 +138,16 @@ namespace Resource /** call releaseGLObjects on all objects attached to the object cache.*/ void releaseGLObjects(osg::State* state) { - std::lock_guard lock(_objectCacheMutex); - for (const auto& [k, v] : _objectCache) + std::lock_guard lock(mMutex); + for (const auto& [k, v] : mItems) v.mValue->releaseGLObjects(state); } /** call node->accept(nv); for all nodes in the objectCache. */ void accept(osg::NodeVisitor& nv) { - std::lock_guard lock(_objectCacheMutex); - for (const auto& [k, v] : _objectCache) + std::lock_guard lock(mMutex); + for (const auto& [k, v] : mItems) if (osg::Object* const object = v.mValue.get()) if (osg::Node* const node = dynamic_cast(object)) node->accept(nv); @@ -157,24 +157,24 @@ namespace Resource template void call(Functor&& f) { - std::lock_guard lock(_objectCacheMutex); - for (const auto& [k, v] : _objectCache) + std::lock_guard lock(mMutex); + for (const auto& [k, v] : mItems) f(k, v.mValue.get()); } /** Get the number of objects in the cache. */ unsigned int getCacheSize() const { - std::lock_guard lock(_objectCacheMutex); - return _objectCache.size(); + std::lock_guard lock(mMutex); + return mItems.size(); } template std::optional>> lowerBound(K&& key) { - const std::lock_guard lock(_objectCacheMutex); - const auto it = _objectCache.lower_bound(std::forward(key)); - if (it == _objectCache.end()) + const std::lock_guard lock(mMutex); + const auto it = mItems.lower_bound(std::forward(key)); + if (it == mItems.end()) return std::nullopt; return std::pair(it->first, it->second.mValue); } @@ -186,8 +186,8 @@ namespace Resource double mLastUsage; }; - std::map> _objectCache; - mutable std::mutex _objectCacheMutex; + std::map> mItems; + mutable std::mutex mMutex; }; class ObjectCache : public GenericObjectCache From 088d4ee5ceae7f27465615f3563969954b0c8b27 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 15 Nov 2023 23:45:56 +0100 Subject: [PATCH 152/231] Use settings values to declare double settings --- apps/opencs/model/prefs/doublesetting.cpp | 2 +- apps/opencs/model/prefs/doublesetting.hpp | 2 +- apps/opencs/model/prefs/state.cpp | 47 +++++++++++++---------- apps/opencs/model/prefs/state.hpp | 2 +- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/apps/opencs/model/prefs/doublesetting.cpp b/apps/opencs/model/prefs/doublesetting.cpp index 679c8d6a92..bbe573f800 100644 --- a/apps/opencs/model/prefs/doublesetting.cpp +++ b/apps/opencs/model/prefs/doublesetting.cpp @@ -15,7 +15,7 @@ #include "state.hpp" CSMPrefs::DoubleSetting::DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mPrecision(2) , mMin(0) diff --git a/apps/opencs/model/prefs/doublesetting.hpp b/apps/opencs/model/prefs/doublesetting.hpp index add85cb9b3..856cebcb46 100644 --- a/apps/opencs/model/prefs/doublesetting.hpp +++ b/apps/opencs/model/prefs/doublesetting.hpp @@ -21,7 +21,7 @@ namespace CSMPrefs public: explicit DoubleSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); DoubleSetting& setPrecision(int precision); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 9b7475dea8..45ec001bd4 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -180,29 +180,36 @@ void CSMPrefs::State::declare() declareCategory("3D Scene Input"); - declareDouble("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); - declareDouble("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); + declareDouble(mValues->mSceneInput.mNaviWheelFactor, "Camera Zoom Sensitivity").setRange(-100.0, 100.0); + declareDouble(mValues->mSceneInput.mSNaviSensitivity, "Secondary Camera Movement Sensitivity") + .setRange(-1000.0, 1000.0); - declareDouble("p-navi-free-sensitivity", "Free Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); + declareDouble(mValues->mSceneInput.mPNaviFreeSensitivity, "Free Camera Sensitivity") + .setPrecision(5) + .setRange(0.0, 1.0); declareBool(mValues->mSceneInput.mPNaviFreeInvert, "Invert Free Camera Mouse Input"); - declareDouble("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); - declareDouble("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); - declareDouble("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); + declareDouble(mValues->mSceneInput.mNaviFreeLinSpeed, "Free Camera Linear Speed").setRange(1.0, 10000.0); + declareDouble(mValues->mSceneInput.mNaviFreeRotSpeed, "Free Camera Rotational Speed").setRange(0.001, 6.28); + declareDouble(mValues->mSceneInput.mNaviFreeSpeedMult, "Free Camera Speed Multiplier (from Modifier)") + .setRange(0.001, 1000.0); - declareDouble("p-navi-orbit-sensitivity", "Orbit Camera Sensitivity", 1 / 650.).setPrecision(5).setRange(0.0, 1.0); + declareDouble(mValues->mSceneInput.mPNaviOrbitSensitivity, "Orbit Camera Sensitivity") + .setPrecision(5) + .setRange(0.0, 1.0); declareBool(mValues->mSceneInput.mPNaviOrbitInvert, "Invert Orbit Camera Mouse Input"); - declareDouble("navi-orbit-rot-speed", "Orbital Camera Rotational Speed", 3.14 / 4).setRange(0.001, 6.28); - declareDouble("navi-orbit-speed-mult", "Orbital Camera Speed Multiplier (from Modifier)", 4) + declareDouble(mValues->mSceneInput.mNaviOrbitRotSpeed, "Orbital Camera Rotational Speed").setRange(0.001, 6.28); + declareDouble(mValues->mSceneInput.mNaviOrbitSpeedMult, "Orbital Camera Speed Multiplier (from Modifier)") .setRange(0.001, 1000.0); declareBool(mValues->mSceneInput.mNaviOrbitConstRoll, "Keep camera roll constant for orbital camera"); declareBool(mValues->mSceneInput.mContextSelect, "Context Sensitive Selection"); - declareDouble("drag-factor", "Mouse sensitivity during drag operations", 1.0).setRange(0.001, 100.0); - declareDouble("drag-wheel-factor", "Mouse wheel sensitivity during drag operations", 1.0).setRange(0.001, 100.0); - declareDouble("drag-shift-factor", "Shift-acceleration factor during drag operations", 4.0) + declareDouble(mValues->mSceneInput.mDragFactor, "Mouse sensitivity during drag operations").setRange(0.001, 100.0); + declareDouble(mValues->mSceneInput.mDragWheelFactor, "Mouse wheel sensitivity during drag operations") + .setRange(0.001, 100.0); + declareDouble(mValues->mSceneInput.mDragShiftFactor, "Shift-acceleration factor during drag operations") .setTooltip("Acceleration factor during drag operations while holding down shift") .setRange(0.001, 100.0); - declareDouble("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); + declareDouble(mValues->mSceneInput.mRotateFactor, "Free rotation factor").setPrecision(4).setRange(0.0001, 0.1); declareCategory("Rendering"); declareInt(mValues->mRendering.mFramerateLimit, "FPS limit") @@ -213,7 +220,7 @@ void CSMPrefs::State::declare() declareInt(mValues->mRendering.mCameraOrthoSize, "Orthographic projection size parameter") .setTooltip("Size of the orthographic frustum, greater value will allow the camera to see more of the world.") .setRange(10, 10000); - declareDouble("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0, 1); + declareDouble(mValues->mRendering.mObjectMarkerAlpha, "Object Marker Transparency").setPrecision(2).setRange(0, 1); declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background"); declareColour("scene-day-background-colour", "Day Background Colour", QColor(110, 120, 128, 255)); declareColour("scene-day-gradient-colour", "Day Gradient Colour", QColor(47, 51, 51, 255)) @@ -264,9 +271,9 @@ void CSMPrefs::State::declare() secondarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert); declareCategory("3D Scene Editing"); - declareDouble("gridsnap-movement", "Grid snap size", 16); - declareDouble("gridsnap-rotation", "Angle snap size", 15); - declareDouble("gridsnap-scale", "Scale snap size", 0.25); + declareDouble(mValues->mSceneEditing.mGridsnapMovement, "Grid snap size"); + declareDouble(mValues->mSceneEditing.mGridsnapRotation, "Angle snap size"); + declareDouble(mValues->mSceneEditing.mGridsnapScale, "Scale snap size"); declareInt(mValues->mSceneEditing.mDistance, "Drop Distance") .setTooltip( "If an instance drop can not be placed against another object at the " @@ -286,7 +293,7 @@ void CSMPrefs::State::declare() .setMin(1); declareBool(mValues->mSceneEditing.mLandeditPostSmoothpainting, "Smooth land after painting height") .setTooltip("Raise and lower tools will leave bumpy finish without this option"); - declareDouble("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25) + declareDouble(mValues->mSceneEditing.mLandeditPostSmoothstrength, "Smoothing strength (post-edit)") .setTooltip( "If smoothing land after painting height is used, this is the percentage of smooth applied afterwards. " "Negative values may be used to roughen instead of smooth.") @@ -475,13 +482,13 @@ CSMPrefs::IntSetting& CSMPrefs::State::declareInt(Settings::SettingValue& v return *setting; } -CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(const std::string& key, const QString& label, double default_) +CSMPrefs::DoubleSetting& CSMPrefs::State::declareDouble(Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); CSMPrefs::DoubleSetting* setting - = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::DoubleSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index 62db966fcd..eaf829de7e 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -65,7 +65,7 @@ namespace CSMPrefs IntSetting& declareInt(Settings::SettingValue& value, const QString& label); - DoubleSetting& declareDouble(const std::string& key, const QString& label, double default_); + DoubleSetting& declareDouble(Settings::SettingValue& value, const QString& label); BoolSetting& declareBool(Settings::SettingValue& value, const QString& label); From abbbd8cf698a209adc516cec2aadde8daef02921 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 28 Dec 2023 19:23:03 +0300 Subject: [PATCH 153/231] Fix interior sun direction (bug #7585) --- CHANGELOG.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3380a3537d..44cbca1001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ Bug #7553: Faction reaction loading is incorrect Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Bug #7573: Drain Fatigue can't bring fatigue below zero by default + Bug #7585: Difference in interior lighting between OpenMW with legacy lighting method enabled and vanilla Morrowind Bug #7603: Scripts menu size is not updated properly Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 2cfcd2fd37..fa92fa1420 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -693,8 +693,9 @@ namespace MWRender osg::Vec4f diffuse = SceneUtil::colourFromRGB(cell.getMood().mDirectionalColor); setSunColour(diffuse, diffuse, 1.f); - - const osg::Vec4f interiorSunPos = osg::Vec4f(-0.15f, 0.15f, 1.f, 0.f); + // This is total nonsense but it's what Morrowind uses + static const osg::Vec4f interiorSunPos + = osg::Vec4f(-1.f, osg::DegreesToRadians(45.f), osg::DegreesToRadians(45.f), 0.f); mPostProcessor->getStateUpdater()->setSunPos(interiorSunPos, false); mSunLight->setPosition(interiorSunPos); } From 0fc78aa1732279d5a62cc1723e4552287a0ebcb8 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 29 Dec 2023 13:10:13 +0100 Subject: [PATCH 154/231] Make ESM::StringRefId compatible with std::string UniversalId --- apps/opencs/model/world/universalid.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index dec533b015..d883cccea5 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -360,6 +360,10 @@ const std::string& CSMWorld::UniversalId::getId() const if (const std::string* result = std::get_if(&mValue)) return *result; + if (const ESM::RefId* refId = std::get_if(&mValue)) + if (const ESM::StringRefId* result = refId->getIf()) + return result->getValue(); + throw std::logic_error("invalid access to ID of " + ::toString(getArgumentType()) + " UniversalId"); } From 7b5310b5694b4128f6c9b9addc04701ea582b3db Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 29 Dec 2023 14:04:02 +0100 Subject: [PATCH 155/231] Write StringRefId as is when converting UniversalId to string To avoid adding quotes which on conversion ESM::StringRefId -> UniversalId -> std::string -> UniversalId changes the string value adding quotes. --- apps/opencs/model/world/universalid.cpp | 2 ++ apps/opencs_tests/model/world/testuniversalid.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index d883cccea5..3d40d5f30f 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -187,6 +187,8 @@ namespace { mStream << ": " << value; } + + void operator()(const ESM::RefId& value) const { mStream << ": " << value.toString(); } }; struct GetTypeData diff --git a/apps/opencs_tests/model/world/testuniversalid.cpp b/apps/opencs_tests/model/world/testuniversalid.cpp index 2e610b0dd0..54538a591d 100644 --- a/apps/opencs_tests/model/world/testuniversalid.cpp +++ b/apps/opencs_tests/model/world/testuniversalid.cpp @@ -177,11 +177,11 @@ namespace CSMWorld UniversalId::ArgumentType_Id, "Instance", "Instance: f", ":./instance.png" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::stringRefId("g")), UniversalId::Type_Reference, - UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: \"g\"", + UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", "Instance: g", ":./instance.png" }, Params{ UniversalId(UniversalId::Type_Reference, ESM::RefId::index(ESM::REC_SKIL, 42)), UniversalId::Type_Reference, UniversalId::Class_SubRecord, UniversalId::ArgumentType_RefId, "Instance", - "Instance: Index:SKIL:0x2a", ":./instance.png" }, + "Instance: SKIL:0x2a", ":./instance.png" }, }; INSTANTIATE_TEST_SUITE_P(ValidParams, CSMWorldUniversalIdValidPerTypeTest, ValuesIn(validParams)); From 39dd73263dc37c6ac116e794d14f0bba3c5301e4 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 29 Dec 2023 14:15:28 +0100 Subject: [PATCH 156/231] Avoid converting UniversalId to a different type via string --- apps/opencs/model/world/universalid.cpp | 6 ++++++ apps/opencs/model/world/universalid.hpp | 2 ++ apps/opencs/view/doc/view.cpp | 2 +- apps/opencs/view/world/table.cpp | 4 ++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 3d40d5f30f..2c2948e9e2 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -328,6 +328,12 @@ CSMWorld::UniversalId::UniversalId(Type type, ESM::RefId id) throw std::logic_error("invalid RefId argument UniversalId type: " + std::to_string(type)); } +CSMWorld::UniversalId::UniversalId(Type type, const UniversalId& id) + : mType(type) + , mValue(id.mValue) +{ +} + CSMWorld::UniversalId::UniversalId(Type type, int index) : mType(type) , mValue(index) diff --git a/apps/opencs/model/world/universalid.hpp b/apps/opencs/model/world/universalid.hpp index 2d3385bcb4..8c188e8da5 100644 --- a/apps/opencs/model/world/universalid.hpp +++ b/apps/opencs/model/world/universalid.hpp @@ -158,6 +158,8 @@ namespace CSMWorld UniversalId(Type type, int index); ///< Using a type for a non-index-argument UniversalId will throw an exception. + UniversalId(Type type, const UniversalId& id); + Class getClass() const; ArgumentType getArgumentType() const; diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 4f4e687d8f..e1bf7e6ac6 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -629,7 +629,7 @@ void CSVDoc::View::addSubView(const CSMWorld::UniversalId& id, const std::string if (isReferenceable) { view = mSubViewFactory.makeSubView( - CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id.getId()), *mDocument); + CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Referenceable, id), *mDocument); } else { diff --git a/apps/opencs/view/world/table.cpp b/apps/opencs/view/world/table.cpp index 1e80805630..4212e952e8 100644 --- a/apps/opencs/view/world/table.cpp +++ b/apps/opencs/view/world/table.cpp @@ -694,10 +694,10 @@ void CSVWorld::Table::previewRecord() if (selectedRows.size() == 1) { - std::string id = getUniversalId(selectedRows.begin()->row()).getId(); + CSMWorld::UniversalId id = getUniversalId(selectedRows.begin()->row()); QModelIndex index - = mModel->getModelIndex(id, mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification)); + = mModel->getModelIndex(id.getId(), mModel->findColumnIndex(CSMWorld::Columns::ColumnId_Modification)); if (mModel->data(index) != CSMWorld::RecordBase::State_Deleted) emit editRequest(CSMWorld::UniversalId(CSMWorld::UniversalId::Type_Preview, id), ""); From 0e2e386dc99f08f42361c8581f63081c147f458f Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 29 Dec 2023 18:56:59 +0000 Subject: [PATCH 157/231] Lua actions take 3 --- CI/file_name_exceptions.txt | 1 + apps/openmw/mwbase/luamanager.hpp | 8 + apps/openmw/mwlua/inputbindings.cpp | 124 +++++++ apps/openmw/mwlua/luamanagerimp.cpp | 12 +- apps/openmw/mwlua/luamanagerimp.hpp | 7 + apps/openmw_test_suite/CMakeLists.txt | 1 + .../lua/test_inputactions.cpp | 65 ++++ components/CMakeLists.txt | 2 +- components/lua/inputactions.cpp | 288 +++++++++++++++ components/lua/inputactions.hpp | 153 ++++++++ components/misc/strings/algorithm.hpp | 7 + .../lua-scripting/engine_handlers.rst | 3 +- .../lua-scripting/setting_renderers.rst | 24 ++ files/data/CMakeLists.txt | 2 + files/data/builtin.omwscripts | 2 + files/data/l10n/OMWControls/en.yaml | 73 +++- files/data/l10n/OMWControls/fr.yaml | 3 + files/data/l10n/OMWControls/ru.yaml | 2 + files/data/l10n/OMWControls/sv.yaml | 3 + files/data/scripts/omw/camera/camera.lua | 64 ++-- files/data/scripts/omw/camera/move360.lua | 41 ++- .../data/scripts/omw/input/actionbindings.lua | 255 +++++++++++++ .../data/scripts/omw/input/smoothmovement.lua | 92 +++++ files/data/scripts/omw/playercontrols.lua | 344 +++++++++++------- files/lua_api/openmw/input.lua | 115 +++++- 25 files changed, 1490 insertions(+), 201 deletions(-) create mode 100644 apps/openmw_test_suite/lua/test_inputactions.cpp create mode 100644 components/lua/inputactions.cpp create mode 100644 components/lua/inputactions.hpp create mode 100644 files/data/scripts/omw/input/actionbindings.lua create mode 100644 files/data/scripts/omw/input/smoothmovement.lua diff --git a/CI/file_name_exceptions.txt b/CI/file_name_exceptions.txt index 5035d73f27..c3bcee8661 100644 --- a/CI/file_name_exceptions.txt +++ b/CI/file_name_exceptions.txt @@ -19,6 +19,7 @@ apps/openmw_test_suite/lua/test_serialization.cpp apps/openmw_test_suite/lua/test_storage.cpp apps/openmw_test_suite/lua/test_ui_content.cpp apps/openmw_test_suite/lua/test_utilpackage.cpp +apps/openmw_test_suite/lua/test_inputactions.cpp apps/openmw_test_suite/misc/test_endianness.cpp apps/openmw_test_suite/misc/test_resourcehelpers.cpp apps/openmw_test_suite/misc/test_stringops.cpp diff --git a/apps/openmw/mwbase/luamanager.hpp b/apps/openmw/mwbase/luamanager.hpp index e4b16ff725..6e611aa88f 100644 --- a/apps/openmw/mwbase/luamanager.hpp +++ b/apps/openmw/mwbase/luamanager.hpp @@ -29,6 +29,14 @@ namespace ESM struct LuaScripts; } +namespace LuaUtil +{ + namespace InputAction + { + class Registry; + } +} + namespace MWBase { // \brief LuaManager is the central interface through which the engine invokes lua scripts. diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 02babf0399..8763dce28d 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -4,12 +4,14 @@ #include #include +#include #include #include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" #include "../mwinput/actions.hpp" +#include "luamanagerimp.hpp" namespace sol { @@ -17,6 +19,16 @@ namespace sol struct is_automagical : std::false_type { }; + + template <> + struct is_automagical : std::false_type + { + }; + + template <> + struct is_automagical : std::false_type + { + }; } namespace MWLua @@ -46,9 +58,121 @@ namespace MWLua touchpadEvent["pressure"] = sol::readonly_property([](const SDLUtil::TouchEvent& e) -> float { return e.mPressure; }); + auto inputActions = context.mLua->sol().new_usertype("InputActions"); + inputActions[sol::meta_function::index] + = [](LuaUtil::InputAction::Registry& registry, std::string_view key) { return registry[key]; }; + { + auto pairs = [](LuaUtil::InputAction::Registry& registry) { + auto next = [](LuaUtil::InputAction::Registry& registry, std::string_view key) + -> sol::optional> { + std::optional nextKey(registry.nextKey(key)); + if (!nextKey.has_value()) + return sol::nullopt; + else + return std::make_tuple(*nextKey, registry[*nextKey].value()); + }; + return std::make_tuple(next, registry, registry.firstKey()); + }; + inputActions[sol::meta_function::pairs] = pairs; + } + + auto actionInfo = context.mLua->sol().new_usertype("ActionInfo", "key", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mKey; }), "name", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mName; }), "description", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDescription; }), "type", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mType; }), "l10n", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mL10n; }), "defaultValue", + sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; })); + + auto inputTriggers = context.mLua->sol().new_usertype("InputTriggers"); + inputTriggers[sol::meta_function::index] + = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) { return registry[key]; }; + { + auto pairs = [](LuaUtil::InputTrigger::Registry& registry) { + auto next = [](LuaUtil::InputTrigger::Registry& registry, std::string_view key) + -> sol::optional> { + std::optional nextKey(registry.nextKey(key)); + if (!nextKey.has_value()) + return sol::nullopt; + else + return std::make_tuple(*nextKey, registry[*nextKey].value()); + }; + return std::make_tuple(next, registry, registry.firstKey()); + }; + inputTriggers[sol::meta_function::pairs] = pairs; + } + + auto triggerInfo = context.mLua->sol().new_usertype("TriggerInfo", "key", + sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mKey; }), "name", + sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mName; }), "description", + sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mDescription; }), "l10n", + sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mL10n; })); + MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); sol::table api(context.mLua->sol(), sol::create); + api["ACTION_TYPE"] + = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Boolean", LuaUtil::InputAction::Type::Boolean }, + { "Number", LuaUtil::InputAction::Type::Number }, + { "Range", LuaUtil::InputAction::Type::Range }, + })); + + api["actions"] = std::ref(context.mLuaManager->inputActions()); + api["registerAction"] = [manager = context.mLuaManager](sol::table options) { + LuaUtil::InputAction::Info parsedOptions; + parsedOptions.mKey = options["key"].get(); + parsedOptions.mType = options["type"].get(); + parsedOptions.mL10n = options["l10n"].get(); + parsedOptions.mName = options["name"].get(); + parsedOptions.mDescription = options["description"].get(); + parsedOptions.mDefaultValue = options["defaultValue"].get(); + manager->inputActions().insert(parsedOptions); + }; + api["bindAction"] = [manager = context.mLuaManager]( + std::string_view key, const sol::table& callback, sol::table dependencies) { + std::vector parsedDependencies; + parsedDependencies.reserve(dependencies.size()); + for (size_t i = 1; i <= dependencies.size(); ++i) + { + sol::object dependency = dependencies[i]; + if (!dependency.is()) + throw std::domain_error("The dependencies argument must be a list of Action keys"); + parsedDependencies.push_back(dependency.as()); + } + if (!manager->inputActions().bind(key, LuaUtil::Callback::fromLua(callback), parsedDependencies)) + throw std::domain_error("Cyclic action binding"); + }; + api["registerActionHandler"] + = [manager = context.mLuaManager](std::string_view key, const sol::table& callback) { + manager->inputActions().registerHandler(key, LuaUtil::Callback::fromLua(callback)); + }; + api["getBooleanActionValue"] = [manager = context.mLuaManager](std::string_view key) { + return manager->inputActions().valueOfType(key, LuaUtil::InputAction::Type::Boolean); + }; + api["getNumberActionValue"] = [manager = context.mLuaManager](std::string_view key) { + return manager->inputActions().valueOfType(key, LuaUtil::InputAction::Type::Number); + }; + api["getRangeActionValue"] = [manager = context.mLuaManager](std::string_view key) { + return manager->inputActions().valueOfType(key, LuaUtil::InputAction::Type::Range); + }; + + api["triggers"] = std::ref(context.mLuaManager->inputTriggers()); + api["registerTrigger"] = [manager = context.mLuaManager](sol::table options) { + LuaUtil::InputTrigger::Info parsedOptions; + parsedOptions.mKey = options["key"].get(); + parsedOptions.mL10n = options["l10n"].get(); + parsedOptions.mName = options["name"].get(); + parsedOptions.mDescription = options["description"].get(); + manager->inputTriggers().insert(parsedOptions); + }; + api["registerTriggerHandler"] + = [manager = context.mLuaManager](std::string_view key, const sol::table& callback) { + manager->inputTriggers().registerHandler(key, LuaUtil::Callback::fromLua(callback)); + }; + api["activateTrigger"] + = [manager = context.mLuaManager](std::string_view key) { manager->inputTriggers().activate(key); }; + api["isIdle"] = [input]() { return input->isIdle(); }; api["isActionPressed"] = [input](int action) { return input->actionIsActive(action); }; api["isKeyPressed"] = [](SDL_Scancode code) -> bool { diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index c324360287..baf13edac4 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -225,10 +225,12 @@ namespace MWLua playerScripts->processInputEvent(event); } mInputEvents.clear(); + double frameDuration = MWBase::Environment::get().getWorld()->getTimeManager()->isPaused() + ? 0.0 + : MWBase::Environment::get().getFrameDuration(); + mInputActions.update(frameDuration); if (playerScripts) - playerScripts->onFrame(MWBase::Environment::get().getWorld()->getTimeManager()->isPaused() - ? 0.0 - : MWBase::Environment::get().getFrameDuration()); + playerScripts->onFrame(frameDuration); mProcessingInputEvents = false; for (const std::string& message : mUIMessages) @@ -291,6 +293,8 @@ namespace MWLua } mGlobalStorage.clearTemporaryAndRemoveCallbacks(); mPlayerStorage.clearTemporaryAndRemoveCallbacks(); + mInputActions.clear(); + mInputTriggers.clear(); for (int i = 0; i < 5; ++i) lua_gc(mLua.sol(), LUA_GCCOLLECT, 0); } @@ -520,6 +524,8 @@ namespace MWLua MWBase::Environment::get().getL10nManager()->dropCache(); mUiResourceManager.clear(); mLua.dropScriptCache(); + mInputActions.clear(); + mInputTriggers.clear(); initConfiguration(); { // Reload global scripts diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index ae16689562..8bd189d8e9 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -144,6 +145,9 @@ namespace MWLua void reportStats(unsigned int frameNumber, osg::Stats& stats) const; std::string formatResourceUsageStats() const override; + LuaUtil::InputAction::Registry& inputActions() { return mInputActions; } + LuaUtil::InputTrigger::Registry& inputTriggers() { return mInputTriggers; } + private: void initConfiguration(); LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, @@ -206,6 +210,9 @@ namespace MWLua LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() }; LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() }; + + LuaUtil::InputAction::Registry mInputActions; + LuaUtil::InputTrigger::Registry mInputTriggers; }; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 4f93319c96..c8679ab7f4 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -28,6 +28,7 @@ file(GLOB UNITTEST_SRC_FILES lua/test_l10n.cpp lua/test_storage.cpp lua/test_async.cpp + lua/test_inputactions.cpp lua/test_ui_content.cpp diff --git a/apps/openmw_test_suite/lua/test_inputactions.cpp b/apps/openmw_test_suite/lua/test_inputactions.cpp new file mode 100644 index 0000000000..5bdd39ada1 --- /dev/null +++ b/apps/openmw_test_suite/lua/test_inputactions.cpp @@ -0,0 +1,65 @@ +#include "gmock/gmock.h" +#include + +#include +#include + +#include "../testing_util.hpp" + +namespace +{ + using namespace testing; + using namespace TestingOpenMW; + + TEST(LuaInputActionsTest, MultiTree) + { + { + LuaUtil::InputAction::MultiTree tree; + auto a = tree.insert(); + auto b = tree.insert(); + auto c = tree.insert(); + auto d = tree.insert(); + EXPECT_TRUE(tree.multiEdge(c, { a, b })); + EXPECT_TRUE(tree.multiEdge(a, { d })); + EXPECT_FALSE(tree.multiEdge(d, { c })); + } + + { + LuaUtil::InputAction::MultiTree tree; + auto a = tree.insert(); + auto b = tree.insert(); + auto c = tree.insert(); + EXPECT_TRUE(tree.multiEdge(b, { a })); + EXPECT_TRUE(tree.multiEdge(c, { a, b })); + } + } + + TEST(LuaInputActionsTest, Registry) + { + sol::state lua; + LuaUtil::InputAction::Registry registry; + LuaUtil::InputAction::Info a({ "a", LuaUtil::InputAction::Type::Boolean, "test", "a_name", "a_description", + sol::make_object(lua, false) }); + registry.insert(a); + LuaUtil::InputAction::Info b({ "b", LuaUtil::InputAction::Type::Boolean, "test", "b_name", "b_description", + sol::make_object(lua, false) }); + registry.insert(b); + LuaUtil::Callback bindA({ lua.load("return function() return true end")(), sol::table(lua, sol::create) }); + LuaUtil::Callback bindBToA( + { lua.load("return function(_, _, aValue) return aValue end")(), sol::table(lua, sol::create) }); + EXPECT_TRUE(registry.bind("a", bindA, {})); + EXPECT_TRUE(registry.bind("b", bindBToA, { "a" })); + registry.update(1.0); + sol::object bValue = registry.valueOfType("b", LuaUtil::InputAction::Type::Boolean); + EXPECT_TRUE(bValue.is()); + LuaUtil::Callback badA( + { lua.load("return function() return 'not_a_bool' end")(), sol::table(lua, sol::create) }); + EXPECT_TRUE(registry.bind("a", badA, {})); + testing::internal::CaptureStderr(); + registry.update(1.0); + sol::object aValue = registry.valueOfType("a", LuaUtil::InputAction::Type::Boolean); + EXPECT_TRUE(aValue.is()); + bValue = registry.valueOfType("b", LuaUtil::InputAction::Type::Boolean); + EXPECT_TRUE(bValue.is() && bValue.as() == aValue.as()); + } +} diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index f61a5bd0b2..95e523d07c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -44,7 +44,7 @@ list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") add_component_dir (lua luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 - shapes/box + shapes/box inputactions ) add_component_dir (l10n diff --git a/components/lua/inputactions.cpp b/components/lua/inputactions.cpp new file mode 100644 index 0000000000..c21fbcf112 --- /dev/null +++ b/components/lua/inputactions.cpp @@ -0,0 +1,288 @@ +#include "inputactions.hpp" + +#include +#include + +#include +#include + +#include "luastate.hpp" + +namespace LuaUtil +{ + namespace InputAction + { + namespace + { + std::string_view typeName(Type actionType) + { + switch (actionType) + { + case Type::Boolean: + return "Boolean"; + case Type::Number: + return "Number"; + case Type::Range: + return "Range"; + default: + throw std::logic_error("Unknown input action type"); + } + } + } + + MultiTree::Node MultiTree::insert() + { + size_t nextId = size(); + mChildren.push_back({}); + mParents.push_back({}); + return nextId; + } + + bool MultiTree::validateTree() const + { + std::vector complete(size(), false); + traverse([&complete](Node node) { complete[node] = true; }); + return std::find(complete.begin(), complete.end(), false) == complete.end(); + } + + template + void MultiTree::traverse(Function callback) const + { + std::queue nodeQueue; + std::vector complete(size(), false); + for (Node root = 0; root < size(); ++root) + { + if (!complete[root]) + nodeQueue.push(root); + while (!nodeQueue.empty()) + { + Node node = nodeQueue.back(); + nodeQueue.pop(); + + bool isComplete = true; + for (Node parent : mParents[node]) + isComplete = isComplete && complete[parent]; + complete[node] = isComplete; + if (isComplete) + { + callback(node); + for (Node child : mChildren[node]) + nodeQueue.push(child); + } + } + } + } + + bool MultiTree::multiEdge(Node target, const std::vector& source) + { + mParents[target].reserve(mParents[target].size() + source.size()); + for (Node s : source) + { + mParents[target].push_back(s); + mChildren[s].push_back(target); + } + bool validTree = validateTree(); + if (!validTree) + { + for (Node s : source) + { + mParents[target].pop_back(); + mChildren[s].pop_back(); + } + } + return validTree; + } + + namespace + { + bool validateActionValue(sol::object value, Type type) + { + switch (type) + { + case Type::Boolean: + return value.get_type() == sol::type::boolean; + case Type::Number: + return value.get_type() == sol::type::number; + case Type::Range: + if (value.get_type() != sol::type::number) + return false; + double d = value.as(); + return 0.0 <= d && d <= 1.0; + } + throw std::invalid_argument("Unknown action type"); + } + } + + void Registry::insert(Info info) + { + if (mIds.find(info.mKey) != mIds.end()) + throw std::domain_error(Misc::StringUtils::format("Action key \"%s\" is already in use", info.mKey)); + if (info.mKey.empty()) + throw std::domain_error("Action key can't be an empty string"); + if (info.mL10n.empty()) + throw std::domain_error("Localization context can't be empty"); + if (!validateActionValue(info.mDefaultValue, info.mType)) + throw std::logic_error(Misc::StringUtils::format( + "Invalid value: \"%s\" for action \"%s\"", LuaUtil::toString(info.mDefaultValue), info.mKey)); + Id id = mBindingTree.insert(); + mKeys.push_back(info.mKey); + mIds[std::string(info.mKey)] = id; + mInfo.push_back(info); + mHandlers.push_back({}); + mBindings.push_back({}); + mValues.push_back(info.mDefaultValue); + } + + std::optional Registry::nextKey(std::string_view key) const + { + auto it = mIds.find(key); + if (it == mIds.end()) + return std::nullopt; + auto nextId = it->second + 1; + if (nextId >= mKeys.size()) + return std::nullopt; + return mKeys.at(nextId); + } + + std::optional Registry::operator[](std::string_view actionKey) + { + auto iter = mIds.find(actionKey); + if (iter == mIds.end()) + return std::nullopt; + return mInfo[iter->second]; + } + + Registry::Id Registry::safeIdByKey(std::string_view key) + { + auto iter = mIds.find(key); + if (iter == mIds.end()) + throw std::logic_error(Misc::StringUtils::format("Unknown action key: \"%s\"", key)); + return iter->second; + } + + bool Registry::bind( + std::string_view key, const LuaUtil::Callback& callback, const std::vector& dependencies) + { + Id id = safeIdByKey(key); + std::vector dependencyIds; + dependencyIds.reserve(dependencies.size()); + for (std::string_view s : dependencies) + dependencyIds.push_back(safeIdByKey(s)); + bool validEdge = mBindingTree.multiEdge(id, dependencyIds); + if (validEdge) + mBindings[id].push_back(Binding{ + callback, + std::move(dependencyIds), + }); + return validEdge; + } + + sol::object Registry::valueOfType(std::string_view key, Type type) + { + Id id = safeIdByKey(key); + Info info = mInfo[id]; + if (info.mType != type) + throw std::logic_error( + Misc::StringUtils::format("Attempt to get value of type \"%s\" from action \"%s\" with type \"%s\"", + typeName(type), key, typeName(info.mType))); + return mValues[id]; + } + + void Registry::update(double dt) + { + std::vector dependencyValues; + mBindingTree.traverse([this, &dependencyValues, dt](Id node) { + sol::main_object newValue = mValues[node]; + std::vector& bindings = mBindings[node]; + bindings.erase(std::remove_if(bindings.begin(), bindings.end(), + [&](const Binding& binding) { + if (!binding.mCallback.isValid()) + return true; + + dependencyValues.clear(); + for (Id parent : binding.mDependencies) + dependencyValues.push_back(mValues[parent]); + try + { + newValue = sol::main_object( + binding.mCallback.call(dt, newValue, sol::as_args(dependencyValues))); + } + catch (std::exception& e) + { + if (!validateActionValue(newValue, mInfo[node].mType)) + Log(Debug::Error) << Misc::StringUtils::format( + "Error due to invalid value of action \"%s\"(\"%s\"): ", mKeys[node], + LuaUtil::toString(newValue)) + << e.what(); + else + Log(Debug::Error) << "Error in callback: " << e.what(); + } + return false; + }), + bindings.end()); + + if (!validateActionValue(newValue, mInfo[node].mType)) + Log(Debug::Error) << Misc::StringUtils::format( + "Invalid value of action \"%s\": %s", mKeys[node], LuaUtil::toString(newValue)); + if (mValues[node] != newValue) + { + mValues[node] = sol::object(newValue); + std::vector& handlers = mHandlers[node]; + handlers.erase(std::remove_if(handlers.begin(), handlers.end(), + [&](const LuaUtil::Callback& handler) { + if (!handler.isValid()) + return true; + handler.tryCall(newValue); + return false; + }), + handlers.end()); + } + }); + } + } + + namespace InputTrigger + { + Registry::Id Registry::safeIdByKey(std::string_view key) + { + auto it = mIds.find(key); + if (it == mIds.end()) + throw std::domain_error(Misc::StringUtils::format("Unknown trigger key \"%s\"", key)); + return it->second; + } + + void Registry::insert(Info info) + { + if (mIds.find(info.mKey) != mIds.end()) + throw std::domain_error(Misc::StringUtils::format("Trigger key \"%s\" is already in use", info.mKey)); + if (info.mKey.empty()) + throw std::domain_error("Trigger key can't be an empty string"); + if (info.mL10n.empty()) + throw std::domain_error("Localization context can't be empty"); + Id id = mIds.size(); + mIds[info.mKey] = id; + mInfo.push_back(info); + mHandlers.push_back({}); + } + + void Registry::registerHandler(std::string_view key, const LuaUtil::Callback& callback) + { + Id id = safeIdByKey(key); + mHandlers[id].push_back(callback); + } + + void Registry::activate(std::string_view key) + { + Id id = safeIdByKey(key); + std::vector& handlers = mHandlers[id]; + handlers.erase(std::remove_if(handlers.begin(), handlers.end(), + [&](const LuaUtil::Callback& handler) { + if (!handler.isValid()) + return true; + handler.tryCall(); + return false; + }), + handlers.end()); + } + } +} diff --git a/components/lua/inputactions.hpp b/components/lua/inputactions.hpp new file mode 100644 index 0000000000..ac3907b55d --- /dev/null +++ b/components/lua/inputactions.hpp @@ -0,0 +1,153 @@ +#ifndef COMPONENTS_LUA_INPUTACTIONS +#define COMPONENTS_LUA_INPUTACTIONS + +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace LuaUtil::InputAction +{ + enum class Type + { + Boolean, + Number, + Range, + }; + + struct Info + { + std::string mKey; + Type mType; + std::string mL10n; + std::string mName; + std::string mDescription; + sol::main_object mDefaultValue; + }; + + class MultiTree + { + public: + using Node = size_t; + + Node insert(); + bool multiEdge(Node target, const std::vector& source); + size_t size() const { return mParents.size(); } + + template // Function = void(Node) + void traverse(Function callback) const; + + void clear() + { + mParents.clear(); + mChildren.clear(); + } + + private: + std::vector> mParents; + std::vector> mChildren; + + bool validateTree() const; + }; + + class Registry + { + public: + using ConstIterator = std::vector::const_iterator; + void insert(Info info); + size_t size() const { return mKeys.size(); } + std::optional firstKey() const { return mKeys.empty() ? std::nullopt : std::optional(mKeys[0]); } + std::optional nextKey(std::string_view key) const; + std::optional operator[](std::string_view actionKey); + bool bind( + std::string_view key, const LuaUtil::Callback& callback, const std::vector& dependencies); + sol::object valueOfType(std::string_view key, Type type); + void update(double dt); + void registerHandler(std::string_view key, const LuaUtil::Callback& handler) + { + mHandlers[safeIdByKey(key)].push_back(handler); + } + void clear() + { + mKeys.clear(); + mIds.clear(); + mInfo.clear(); + mHandlers.clear(); + mBindings.clear(); + mValues.clear(); + mBindingTree.clear(); + } + + private: + using Id = MultiTree::Node; + Id safeIdByKey(std::string_view key); + struct Binding + { + LuaUtil::Callback mCallback; + std::vector mDependencies; + }; + std::vector mKeys; + std::unordered_map> mIds; + std::vector mInfo; + std::vector> mHandlers; + std::vector> mBindings; + std::vector mValues; + MultiTree mBindingTree; + }; +} + +namespace LuaUtil::InputTrigger +{ + struct Info + { + std::string mKey; + std::string mL10n; + std::string mName; + std::string mDescription; + }; + + class Registry + { + public: + std::optional firstKey() const + { + return mIds.empty() ? std::nullopt : std::optional(mIds.begin()->first); + } + std::optional nextKey(std::string_view key) const + { + auto it = mIds.find(key); + if (it == mIds.end() || ++it == mIds.end()) + return std::nullopt; + return it->first; + } + std::optional operator[](std::string_view key) + { + Id id = safeIdByKey(key); + return mInfo[id]; + } + void insert(Info info); + void registerHandler(std::string_view key, const LuaUtil::Callback& callback); + void activate(std::string_view key); + void clear() + { + mInfo.clear(); + mHandlers.clear(); + mIds.clear(); + } + + private: + using Id = size_t; + Id safeIdByKey(std::string_view key); + std::unordered_map> mIds; + std::vector mInfo; + std::vector> mHandlers; + }; +} + +#endif // COMPONENTS_LUA_INPUTACTIONS diff --git a/components/misc/strings/algorithm.hpp b/components/misc/strings/algorithm.hpp index f34801b8d3..28bc696cd3 100644 --- a/components/misc/strings/algorithm.hpp +++ b/components/misc/strings/algorithm.hpp @@ -99,6 +99,13 @@ namespace Misc::StringUtils bool operator()(std::string_view left, std::string_view right) const { return ciLess(left, right); } }; + struct StringHash + { + using is_transparent = void; + [[nodiscard]] size_t operator()(std::string_view sv) const { return std::hash{}(sv); } + [[nodiscard]] size_t operator()(const std::string& s) const { return std::hash{}(s); } + }; + /** @brief Replaces all occurrences of a string in another string. * * @param str The string to operate on. diff --git a/docs/source/reference/lua-scripting/engine_handlers.rst b/docs/source/reference/lua-scripting/engine_handlers.rst index 1ffa1820f3..10ed3ee555 100644 --- a/docs/source/reference/lua-scripting/engine_handlers.rst +++ b/docs/source/reference/lua-scripting/engine_handlers.rst @@ -109,7 +109,8 @@ Engine handler is a function defined by a script, that can be called by the engi | Usage example: | ``if id == input.CONTROLLER_BUTTON.LeftStick then ...`` * - onInputAction(id) - - | `Game control `_ is pressed. + - | (DEPRECATED, use `registerActionHandler `_) + | `Game control `_ is pressed. | Usage example: | ``if id == input.ACTION.ToggleWeapon then ...`` * - onTouchPress(touchEvent) diff --git a/docs/source/reference/lua-scripting/setting_renderers.rst b/docs/source/reference/lua-scripting/setting_renderers.rst index 966246d503..7f40eb08bd 100644 --- a/docs/source/reference/lua-scripting/setting_renderers.rst +++ b/docs/source/reference/lua-scripting/setting_renderers.rst @@ -126,3 +126,27 @@ Table with the following optional fields: * - disabled - bool (false) - Disables changing the setting from the UI + +inputBinding +----- + +Allows the user to bind inputs to an action or trigger + +**Argument** + +Table with the following fields: + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - name + - type (default) + - description + * - type + - 'keyboardPress', 'keyboardHold' + - The type of input that's allowed to be bound + * - key + - #string + - Key of the action or trigger to which the input is bound + diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index dbf86cc44d..cec613d128 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -92,6 +92,8 @@ set(BUILTIN_DATA_FILES scripts/omw/ui.lua scripts/omw/usehandlers.lua scripts/omw/worldeventhandlers.lua + scripts/omw/input/actionbindings.lua + scripts/omw/input/smoothmovement.lua shaders/adjustments.omwfx shaders/bloomlinear.omwfx diff --git a/files/data/builtin.omwscripts b/files/data/builtin.omwscripts index ec08c5299d..e4338df533 100644 --- a/files/data/builtin.omwscripts +++ b/files/data/builtin.omwscripts @@ -13,6 +13,8 @@ GLOBAL: scripts/omw/worldeventhandlers.lua PLAYER: scripts/omw/mechanics/playercontroller.lua PLAYER: scripts/omw/playercontrols.lua PLAYER: scripts/omw/camera/camera.lua +PLAYER: scripts/omw/input/actionbindings.lua +PLAYER: scripts/omw/input/smoothmovement.lua NPC,CREATURE: scripts/omw/ai.lua # User interface diff --git a/files/data/l10n/OMWControls/en.yaml b/files/data/l10n/OMWControls/en.yaml index de6edde19a..9c45c1d1e5 100644 --- a/files/data/l10n/OMWControls/en.yaml +++ b/files/data/l10n/OMWControls/en.yaml @@ -10,7 +10,76 @@ alwaysRunDescription: | toggleSneak: "Toggle sneak" toggleSneakDescription: | - This setting causes the behavior of the sneak key (bound to Ctrl by default) - to toggle sneaking on and off rather than requiring the key to be held down while sneaking. + This setting causes the sneak key (bound to Ctrl by default) to toggle sneaking on and off + rather than requiring the key to be held down while sneaking. Players that spend significant time sneaking may find the character easier to control with this option enabled. +smoothControllerMovement: "Smooth controller movement" +smoothControllerMovementDescription: | + Enables smooth movement with controller stick, with no abrupt switch from walking to running. + +TogglePOV_name: "Toggle POV" +TogglePOV_description: "Toggle between first and third person view. Hold to enter preview mode." + +Zoom3rdPerson_name: "Zoom In/Out" +Zoom3rdPerson_description: "Moves the camera closer / further away when in third person view." + +MoveForward_name: "Move Forward" +MoveForward_description: "Can cancel out with Move Backward" + +MoveBackward_name: "Move Backward" +MoveBackward_description: "Can cancel out with Move Forward" + +MoveLeft_name: "Move Left" +MoveLeft_description: "Can cancel out with Move Right" + +MoveRight_name: "Move Right" +MoveRight_description: "Can cancel out with Move Left" + +Use_name: "Use" +Use_description: "Attack with a weapon or cast a spell depending on current stance" + +Run_name: "Run" +Run_description: "Hold to run/walk, depending on the Always Run setting" + +AlwaysRun_name: "Always Run" +AlwaysRun_description: "Toggle the Always Run setting" + +Jump_name: "Jump" +Jump_description: "Jump whenever you are on the ground" + +AutoMove_name: "Auto Run" +AutoMove_description: "Toggle continous forward movement" + +Sneak_name: "Sneak" +Sneak_description: "Hold to sneak, if the Toggle Sneak setting is off" + +ToggleSneak_name: "Toggle Sneak" +ToggleSneak_description: "Toggle sneak, if the Toggle Sneak setting is on" + +ToggleWeapon_name: "Ready Weapon" +ToggleWeapon_description: "Enter or leave the weapon stance" + +ToggleSpell_name: "Ready Magic" +ToggleSpell_description: "Enter or leave the magic stance" + +Inventory_name: "Inventory" +Inventory_description: "Open the inventory" + +Journal_name: "Journal" +Journal_description: "Open the journal" + +QuickKeysMenu_name: "QuickKeysMenu" +QuickKeysMenu_description: "Open the quick keys menu" + +SmoothMoveForward_name: "Smooth Move Forward" +SmoothMoveForward_description: "Forward movement adjusted for smooth Walk-Run transitions" + +SmoothMoveBackward_name: "Smooth Move Backward" +SmoothMoveBackward_description: "Backward movement adjusted for smooth Walk-Run transitions" + +SmoothMoveLeft_name: "Smooth Move Left" +SmoothMoveLeft_description: "Left movement adjusted for smooth Walk-Run transitions" + +SkmoothMoveRight_name: "SmoothMove Right" +SkmoothMoveRight_description: "Right movement adjusted for smooth Walk-Run transitions" diff --git a/files/data/l10n/OMWControls/fr.yaml b/files/data/l10n/OMWControls/fr.yaml index 6d58ee1794..dab9ddb8fc 100644 --- a/files/data/l10n/OMWControls/fr.yaml +++ b/files/data/l10n/OMWControls/fr.yaml @@ -14,3 +14,6 @@ toggleSneakDescription: | Une simple pression de la touche associée (Ctrl par défaut) active le mode discrétion, une seconde pression désactive le mode discrétion.\n\n Il n'est plus nécessaire de maintenir une touche appuyée pour que le mode discrétion soit actif.\n\n Certains joueurs ayant une utilisation intensive du mode discrétion considèrent qu'il est plus aisé de contrôler leur personnage ainsi. + +# smoothControllerMovement +# smoothControllerMovementDescription diff --git a/files/data/l10n/OMWControls/ru.yaml b/files/data/l10n/OMWControls/ru.yaml index 30bb15ee57..0ce3609e16 100644 --- a/files/data/l10n/OMWControls/ru.yaml +++ b/files/data/l10n/OMWControls/ru.yaml @@ -14,3 +14,5 @@ toggleSneakDescription: | чтобы красться, её достаточно нажать единожды для переключения положения, а не зажимать. Игрокам, которые много времени крадутся, может быть проще управлять персонажем, когда опция включена. +# smoothControllerMovement +# smoothControllerMovementDescription diff --git a/files/data/l10n/OMWControls/sv.yaml b/files/data/l10n/OMWControls/sv.yaml index 43565053c5..73fc5e18dc 100644 --- a/files/data/l10n/OMWControls/sv.yaml +++ b/files/data/l10n/OMWControls/sv.yaml @@ -13,3 +13,6 @@ toggleSneakDescription: | Denna inställningen gör att smygknappen (förinställt till ctrl) slår smygning på eller av vid ett knapptryck istället för att att kräva att knappen hålls nedtryckt för att smyga. Spelare som spenderar mycket tid med att smyga lär ha lättare att kontrollera rollfiguren med denna funktion aktiverad. + +# smoothControllerMovement +# smoothControllerMovementDescription diff --git a/files/data/scripts/omw/camera/camera.lua b/files/data/scripts/omw/camera/camera.lua index 38441f9e59..6c162f3a25 100644 --- a/files/data/scripts/omw/camera/camera.lua +++ b/files/data/scripts/omw/camera/camera.lua @@ -10,6 +10,24 @@ local I = require('openmw.interfaces') local Actor = require('openmw.types').Actor local Player = require('openmw.types').Player +input.registerAction { + key = 'TogglePOV', + l10n = 'OMWControls', + name = 'TogglePOV_name', + description = 'TogglePOV_description', + type = input.ACTION_TYPE.Boolean, + defaultValue = false, +} + +input.registerAction { + key = 'Zoom3rdPerson', + l10n = 'OMWControls', + name = 'Zoom3rdPerson_name', + description = 'Zoom3rdPerson_description', + type = input.ACTION_TYPE.Number, + defaultValue = 0, +} + local settings = require('scripts.omw.camera.settings').thirdPerson local head_bobbing = require('scripts.omw.camera.head_bobbing') local third_person = require('scripts.omw.camera.third_person') @@ -63,7 +81,7 @@ local previewTimer = 0 local function updatePOV(dt) local switchLimit = 0.25 - if input.isActionPressed(input.ACTION.TogglePOV) and Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) then + if input.getBooleanActionValue('TogglePOV') and Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) then previewTimer = previewTimer + dt if primaryMode == MODE.ThirdPerson or previewTimer >= switchLimit then third_person.standingPreview = false @@ -117,18 +135,19 @@ local maxDistance = 800 local function zoom(delta) if not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or - not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or - camera.getMode() == MODE.Static or next(noZoom) then + not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or + camera.getMode() == MODE.Static or next(noZoom) then return end if camera.getMode() ~= MODE.FirstPerson then local obstacleDelta = third_person.preferredDistance - camera.getThirdPersonDistance() if delta > 0 and third_person.baseDistance == minDistance and - (camera.getMode() ~= MODE.Preview or third_person.standingPreview) and not next(noModeControl) then + (camera.getMode() ~= MODE.Preview or third_person.standingPreview) and not next(noModeControl) then primaryMode = MODE.FirstPerson camera.setMode(primaryMode) elseif delta > 0 or obstacleDelta < -delta then - third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance, maxDistance) + third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance, + maxDistance) end elseif delta < 0 and not next(noModeControl) then primaryMode = MODE.ThirdPerson @@ -137,21 +156,10 @@ local function zoom(delta) end end -local function applyControllerZoom(dt) - if input.isActionPressed(input.ACTION.TogglePOV) then - local triggerLeft = input.getAxisValue(input.CONTROLLER_AXIS.TriggerLeft) - local triggerRight = input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) - local controllerZoom = (triggerRight - triggerLeft) * 100 * dt - if controllerZoom ~= 0 then - zoom(controllerZoom) - end - end -end - local function updateStandingPreview() local mode = camera.getMode() if not previewIfStandStill or next(noStandingPreview) - or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then + or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then third_person.standingPreview = false return end @@ -184,7 +192,7 @@ local function updateIdleTimer(dt) if not input.isIdle() then idleTimer = 0 elseif self.controls.movement ~= 0 or self.controls.sideMovement ~= 0 or self.controls.jump or self.controls.use ~= 0 then - idleTimer = 0 -- also reset the timer in case of a scripted movement + idleTimer = 0 -- also reset the timer in case of a scripted movement else idleTimer = idleTimer + dt end @@ -205,7 +213,14 @@ local function onFrame(dt) updateStandingPreview() updateCrosshair() end - applyControllerZoom(dt) + + do + local Zoom3rdPerson = input.getNumberActionValue('Zoom3rdPerson') + if Zoom3rdPerson ~= 0 then + zoom(Zoom3rdPerson) + end + end + third_person.update(dt, smoothedSpeed) if not next(noHeadBobbing) then head_bobbing.update(dt, smoothedSpeed) end if slowViewChange then @@ -312,15 +327,6 @@ return { engineHandlers = { onUpdate = onUpdate, onFrame = onFrame, - onInputAction = function(action) - if core.isWorldPaused() or I.UI.getMode() then return end - if action == input.ACTION.ZoomIn then - zoom(10) - elseif action == input.ACTION.ZoomOut then - zoom(-10) - end - move360.onInputAction(action) - end, onTeleported = function() camera.instantTransition() end, @@ -329,7 +335,7 @@ return { if data and data.distance then third_person.baseDistance = data.distance end end, onSave = function() - return {version = 0, distance = third_person.baseDistance} + return { version = 0, distance = third_person.baseDistance } end, }, } diff --git a/files/data/scripts/omw/camera/move360.lua b/files/data/scripts/omw/camera/move360.lua index 18c30ae77b..a80019f3a0 100644 --- a/files/data/scripts/omw/camera/move360.lua +++ b/files/data/scripts/omw/camera/move360.lua @@ -30,6 +30,26 @@ local function turnOff() end end +local function processZoom3rdPerson() + if + not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or + not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or + input.getBooleanActionValue('TogglePOV') or + not I.Camera.isModeControlEnabled() + then + return + end + local Zoom3rdPerson = input.getNumberActionValue('Zoom3rdPerson') + if Zoom3rdPerson > 0 and camera.getMode() == MODE.Preview + and I.Camera.getBaseThirdPersonDistance() == 30 then + self.controls.yawChange = camera.getYaw() - self.rotation:getYaw() + camera.setMode(MODE.FirstPerson) + elseif Zoom3rdPerson < 0 and camera.getMode() == MODE.FirstPerson then + camera.setMode(MODE.Preview) + I.Camera.setBaseThirdPersonDistance(30) + end +end + function M.onFrame(dt) if core.isWorldPaused() then return end local newActive = M.enabled and Actor.getStance(self) == Actor.STANCE.Nothing @@ -39,9 +59,10 @@ function M.onFrame(dt) turnOff() end if not active then return end + processZoom3rdPerson() if camera.getMode() == MODE.Static then return end if camera.getMode() == MODE.ThirdPerson then camera.setMode(MODE.Preview) end - if camera.getMode() == MODE.Preview and not input.isActionPressed(input.ACTION.TogglePOV) then + if camera.getMode() == MODE.Preview and not input.getBooleanActionValue('TogglePOV') then camera.showCrosshair(camera.getFocalPreferredOffset():length() > 5) local move = util.vector2(self.controls.sideMovement, self.controls.movement) local yawDelta = camera.getYaw() - self.rotation:getYaw() @@ -59,22 +80,4 @@ function M.onFrame(dt) end end -function M.onInputAction(action) - if not active or core.isWorldPaused() or - not Player.getControlSwitch(self, Player.CONTROL_SWITCH.ViewMode) or - not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) or - input.isActionPressed(input.ACTION.TogglePOV) or - not I.Camera.isModeControlEnabled() then - return - end - if action == input.ACTION.ZoomIn and camera.getMode() == MODE.Preview - and I.Camera.getBaseThirdPersonDistance() == 30 then - self.controls.yawChange = camera.getYaw() - self.rotation:getYaw() - camera.setMode(MODE.FirstPerson) - elseif action == input.ACTION.ZoomOut and camera.getMode() == MODE.FirstPerson then - camera.setMode(MODE.Preview) - I.Camera.setBaseThirdPersonDistance(30) - end -end - return M diff --git a/files/data/scripts/omw/input/actionbindings.lua b/files/data/scripts/omw/input/actionbindings.lua new file mode 100644 index 0000000000..06ded80793 --- /dev/null +++ b/files/data/scripts/omw/input/actionbindings.lua @@ -0,0 +1,255 @@ +local core = require('openmw.core') +local input = require('openmw.input') +local util = require('openmw.util') +local async = require('openmw.async') +local storage = require('openmw.storage') +local ui = require('openmw.ui') + +local I = require('openmw.interfaces') + +local actionPressHandlers = {} +local function onActionPress(id, handler) + actionPressHandlers[id] = actionPressHandlers[id] or {} + table.insert(actionPressHandlers[id], handler) +end + +local function bindHold(key, actionId) + input.bindAction(key, async:callback(function() + return input.isActionPressed(actionId) + end), {}) +end + +local function bindMovement(key, actionId, axisId, direction) + input.bindAction(key, async:callback(function() + local actionActive = input.isActionPressed(actionId) + local axisActive = input.getAxisValue(axisId) * direction > 0 + return (actionActive or axisActive) and 1 or 0 + end), {}) +end + +local function bindTrigger(key, actionid) + onActionPress(actionid, function() + input.activateTrigger(key) + end) +end + +bindTrigger('AlwaysRun', input.ACTION.AlwaysRun) +bindTrigger('ToggleSneak', input.ACTION.Sneak) +bindTrigger('ToggleWeapon', input.ACTION.ToggleWeapon) +bindTrigger('ToggleSpell', input.ACTION.ToggleSpell) +bindTrigger('Jump', input.ACTION.Jump) +bindTrigger('AutoMove', input.ACTION.AutoMove) +bindTrigger('Inventory', input.ACTION.Inventory) +bindTrigger('Journal', input.ACTION.Journal) +bindTrigger('QuickKeysMenu', input.ACTION.QuickKeysMenu) + +bindHold('TogglePOV', input.ACTION.TogglePOV) +bindHold('Sneak', input.ACTION.Sneak) + +bindHold('Run', input.ACTION.Run) +input.bindAction('Run', async:callback(function(_, value) + local controllerInput = util.vector2( + input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward), + input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight) + ):length2() + return value or controllerInput > 0.25 +end), {}) + +input.bindAction('Use', async:callback(function() + -- The value "0.6" shouldn't exceed the triggering threshold in BindingsManager::actionValueChanged. + -- TODO: Move more logic from BindingsManager to Lua and consider to make this threshold configurable. + return input.isActionPressed(input.ACTION.Use) or input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) >= 0.6 +end), {}) + +bindMovement('MoveBackward', input.ACTION.MoveBackward, input.CONTROLLER_AXIS.MoveForwardBackward, 1) +bindMovement('MoveForward', input.ACTION.MoveForward, input.CONTROLLER_AXIS.MoveForwardBackward, -1) +bindMovement('MoveRight', input.ACTION.MoveRight, input.CONTROLLER_AXIS.MoveLeftRight, 1) +bindMovement('MoveLeft', input.ACTION.MoveLeft, input.CONTROLLER_AXIS.MoveLeftRight, -1) + +do + local zoomInOut = 0 + onActionPress(input.ACTION.ZoomIn, function() + zoomInOut = zoomInOut + 1 + end) + onActionPress(input.ACTION.ZoomOut, function() + zoomInOut = zoomInOut - 1 + end) + input.bindAction('Zoom3rdPerson', async:callback(function(dt, _, togglePOV) + local Zoom3rdPerson = zoomInOut * 10 + if togglePOV then + local triggerLeft = input.getAxisValue(input.CONTROLLER_AXIS.TriggerLeft) + local triggerRight = input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) + local controllerZoom = (triggerRight - triggerLeft) * 100 * dt + Zoom3rdPerson = Zoom3rdPerson + controllerZoom + end + zoomInOut = 0 + return Zoom3rdPerson + end), { 'TogglePOV' }) +end + +local bindingSection = storage.playerSection('OMWInputBindings') + +local keyboardPresses = {} +local keybordHolds = {} +local boundActions = {} + +local function bindAction(action) + if boundActions[action] then return end + boundActions[action] = true + input.bindAction(action, async:callback(function() + if keybordHolds[action] then + for _, binding in pairs(keybordHolds[action]) do + if input.isKeyPressed(binding.code) then return true end + end + end + return false + end), {}) +end + +local function registerBinding(binding, id) + if not input.actions[binding.key] and not input.triggers[binding.key] then + print(string.format('Skipping binding for unknown action or trigger: "%s"', binding.key)) + return + end + if binding.type == 'keyboardPress' then + local bindings = keyboardPresses[binding.code] or {} + bindings[id] = binding + keyboardPresses[binding.code] = bindings + elseif binding.type == 'keyboardHold' then + local bindings = keybordHolds[binding.key] or {} + bindings[id] = binding + keybordHolds[binding.key] = bindings + bindAction(binding.key) + else + error('Unknown binding type "' .. binding.type .. '"') + end +end + +function clearBinding(id) + for _, boundTriggers in pairs(keyboardPresses) do + boundTriggers[id] = nil + end + for _, boundKeys in pairs(keybordHolds) do + boundKeys[id] = nil + end +end + +local function updateBinding(id, binding) + bindingSection:set(id, binding) + clearBinding(id) + if binding ~= nil then + registerBinding(binding, id) + end + return id +end + +local interfaceL10n = core.l10n('interface') + +I.Settings.registerRenderer('inputBinding', function(id, set, arg) + if type(id) ~= 'string' then error('inputBinding: must have a string default value') end + if not arg.type then error('inputBinding: type argument is required') end + if not arg.key then error('inputBinding: key argument is required') end + local info = input.actions[arg.key] or input.triggers[arg.key] + if not info then return {} end + + local l10n = core.l10n(info.key) + + local name = { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(info.name), + }, + } + + local description = { + template = I.MWUI.templates.textNormal, + props = { + text = l10n(info.description), + }, + } + + local binding = bindingSection:get(id) + local label = binding and input.getKeyName(binding.code) or interfaceL10n('None') + + local recorder = { + template = I.MWUI.templates.textEditLine, + props = { + readOnly = true, + text = label, + }, + events = { + focusGain = async:callback(function() + if binding == nil then return end + updateBinding(id, nil) + set(id) + end), + keyPress = async:callback(function(key) + if binding ~= nil or key.code == input.KEY.Escape then return end + + local newBinding = { + code = key.code, + type = arg.type, + key = arg.key, + } + updateBinding(id, newBinding) + set(id) + end), + }, + } + + local row = { + type = ui.TYPE.Flex, + props = { + horizontal = true, + }, + content = ui.content { + name, + { props = { size = util.vector2(10, 0) } }, + recorder, + }, + } + local column = { + type = ui.TYPE.Flex, + content = ui.content { + row, + description, + }, + } + + return column +end) + +local initiated = false + +local function init() + for id, binding in pairs(bindingSection:asTable()) do + registerBinding(binding, id) + end +end + +return { + engineHandlers = { + onFrame = function() + if not initiated then + initiated = true + init() + end + end, + onInputAction = function(id) + if not actionPressHandlers[id] then + return + end + for _, handler in ipairs(actionPressHandlers[id]) do + handler() + end + end, + onKeyPress = function(e) + local bindings = keyboardPresses[e.code] + if bindings then + for _, binding in pairs(bindings) do + input.activateTrigger(binding.key) + end + end + end, + } +} diff --git a/files/data/scripts/omw/input/smoothmovement.lua b/files/data/scripts/omw/input/smoothmovement.lua new file mode 100644 index 0000000000..ebd322f25d --- /dev/null +++ b/files/data/scripts/omw/input/smoothmovement.lua @@ -0,0 +1,92 @@ +local input = require('openmw.input') +local util = require('openmw.util') +local async = require('openmw.async') +local storage = require('openmw.storage') +local types = require('openmw.types') +local self = require('openmw.self') + +local NPC = types.NPC + +local moveActions = { + 'MoveForward', + 'MoveBackward', + 'MoveLeft', + 'MoveRight' +} +for _, key in ipairs(moveActions) do + local smoothKey = 'Smooth' .. key + input.registerAction { + key = smoothKey, + l10n = 'OMWControls', + name = smoothKey .. '_name', + description = smoothKey .. '_description', + type = input.ACTION_TYPE.Range, + defaultValue = 0, + } +end + +local settings = storage.playerSection('SettingsOMWControls') + +local function shouldAlwaysRun(actor) + return actor.controls.sneak or not NPC.isOnGround(actor) or NPC.isSwimming(actor) +end + +local function remapToWalkRun(actor, inputMovement) + if shouldAlwaysRun(actor) then + return true, inputMovement + end + local normalizedInput, inputSpeed = inputMovement:normalize() + local switchPoint = 0.5 + if inputSpeed < switchPoint then + return false, inputMovement * 2 + else + local matchWalkingSpeed = NPC.getWalkSpeed(actor) / NPC.getRunSpeed(actor) + local runSpeedRatio = 2 * (inputSpeed - switchPoint) * (1 - matchWalkingSpeed) + matchWalkingSpeed + return true, normalizedInput * math.min(1, runSpeedRatio) + end +end + +local function computeSmoothMovement() + local controllerInput = util.vector2( + input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward), + input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight) + ) + return remapToWalkRun(self, controllerInput) +end + +local function bindSmoothMove(key, axis, direction) + local smoothKey = 'Smooth' .. key + input.bindAction(smoothKey, async:callback(function() + local _, movement = computeSmoothMovement() + return math.max(direction * movement[axis], 0) + end), {}) + input.bindAction(key, async:callback(function(_, standardMovement, smoothMovement) + if not settings:get('smoothControllerMovement') then + return standardMovement + end + + if smoothMovement > 0 then + return smoothMovement + else + return standardMovement + end + end), { smoothKey }) +end + +bindSmoothMove('MoveForward', 'x', -1) +bindSmoothMove('MoveBackward', 'x', 1) +bindSmoothMove('MoveRight', 'y', 1) +bindSmoothMove('MoveLeft', 'y', -1) + +input.bindAction('Run', async:callback(function(_, run) + if not settings:get('smoothControllerMovement') then + return run + end + local smoothRun, movement = computeSmoothMovement() + if movement:length2() > 0 then + -- ignore always run + return smoothRun ~= settings:get('alwaysRun') + else + return run + end +end), {}) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 7b405180e8..d3f9ecea5f 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -1,12 +1,12 @@ local core = require('openmw.core') local input = require('openmw.input') local self = require('openmw.self') -local util = require('openmw.util') +local storage = require('openmw.storage') local ui = require('openmw.ui') +local async = require('openmw.async') local Actor = require('openmw.types').Actor local Player = require('openmw.types').Player -local storage = require('openmw.storage') local I = require('openmw.interfaces') local settingsGroup = 'SettingsOMWControls' @@ -16,16 +16,16 @@ local function boolSetting(key, default) key = key, renderer = 'checkbox', name = key, - description = key..'Description', + description = key .. 'Description', default = default, } end I.Settings.registerPage({ - key = 'OMWControls', - l10n = 'OMWControls', - name = 'ControlsPage', - description = 'ControlsPageDescription', + key = 'OMWControls', + l10n = 'OMWControls', + name = 'ControlsPage', + description = 'ControlsPageDescription', }) I.Settings.registerGroup({ @@ -36,95 +36,68 @@ I.Settings.registerGroup({ permanentStorage = true, settings = { boolSetting('alwaysRun', false), - boolSetting('toggleSneak', false), + boolSetting('toggleSneak', false), -- TODO: consider removing this setting when we have the advanced binding UI + boolSetting('smoothControllerMovement', true), }, }) -local settings = storage.playerSection(settingsGroup) +local settings = storage.playerSection('SettingsOMWControls') -local attemptJump = false -local startAttack = false -local autoMove = false -local movementControlsOverridden = false -local combatControlsOverridden = false -local uiControlsOverridden = false +do + local rangeActions = { + 'MoveForward', + 'MoveBackward', + 'MoveLeft', + 'MoveRight' + } + for _, key in ipairs(rangeActions) do + input.registerAction { + key = key, + l10n = 'OMWControls', + name = key .. '_name', + description = key .. '_description', + type = input.ACTION_TYPE.Range, + defaultValue = 0, + } + end -local function processMovement() - local controllerMovement = -input.getAxisValue(input.CONTROLLER_AXIS.MoveForwardBackward) - local controllerSideMovement = input.getAxisValue(input.CONTROLLER_AXIS.MoveLeftRight) - if controllerMovement ~= 0 or controllerSideMovement ~= 0 then - -- controller movement - if util.vector2(controllerMovement, controllerSideMovement):length2() < 0.25 - and not self.controls.sneak and Actor.isOnGround(self) and not Actor.isSwimming(self) then - self.controls.run = false - self.controls.movement = controllerMovement * 2 - self.controls.sideMovement = controllerSideMovement * 2 - else - self.controls.run = true - self.controls.movement = controllerMovement - self.controls.sideMovement = controllerSideMovement - end - else - -- keyboard movement - self.controls.movement = 0 - self.controls.sideMovement = 0 - if input.isActionPressed(input.ACTION.MoveLeft) then - self.controls.sideMovement = self.controls.sideMovement - 1 - end - if input.isActionPressed(input.ACTION.MoveRight) then - self.controls.sideMovement = self.controls.sideMovement + 1 - end - if input.isActionPressed(input.ACTION.MoveBackward) then - self.controls.movement = self.controls.movement - 1 - end - if input.isActionPressed(input.ACTION.MoveForward) then - self.controls.movement = self.controls.movement + 1 - end - self.controls.run = input.isActionPressed(input.ACTION.Run) ~= settings:get('alwaysRun') + local booleanActions = { + 'Use', + 'Run', + 'Sneak', + } + for _, key in ipairs(booleanActions) do + input.registerAction { + key = key, + l10n = 'OMWControls', + name = key .. '_name', + description = key .. '_description', + type = input.ACTION_TYPE.Boolean, + defaultValue = false, + } end - if self.controls.movement ~= 0 or not Actor.canMove(self) then - autoMove = false - elseif autoMove then - self.controls.movement = 1 - end - self.controls.jump = attemptJump and Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) - if not settings:get('toggleSneak') then - self.controls.sneak = input.isActionPressed(input.ACTION.Sneak) - end -end -local function processAttacking() - if startAttack then - self.controls.use = 1 - elseif Actor.stance(self) == Actor.STANCE.Spell then - self.controls.use = 0 - elseif input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight) < 0.6 - and not input.isActionPressed(input.ACTION.Use) then - -- The value "0.6" shouldn't exceed the triggering threshold in BindingsManager::actionValueChanged. - -- TODO: Move more logic from BindingsManager to Lua and consider to make this threshold configurable. - self.controls.use = 0 + local triggers = { + 'Jump', + 'AutoMove', + 'ToggleWeapon', + 'ToggleSpell', + 'AlwaysRun', + 'ToggleSneak', + 'Inventory', + 'Journal', + 'QuickKeysMenu', + } + for _, key in ipairs(triggers) do + input.registerTrigger { + key = key, + l10n = 'OMWControls', + name = key .. '_name', + description = key .. '_description', + } end end -local function onFrame(dt) - local controlsAllowed = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) - and not core.isWorldPaused() and not I.UI.getMode() - if not movementControlsOverridden then - if controlsAllowed then - processMovement() - else - self.controls.movement = 0 - self.controls.sideMovement = 0 - self.controls.jump = false - end - end - if controlsAllowed and not combatControlsOverridden then - processAttacking() - end - attemptJump = false - startAttack = false -end - local function checkNotWerewolf() if Player.isWerewolf(self) then ui.showMessage(core.getGMST('sWerewolfRefusal')) @@ -139,68 +112,163 @@ local function isJournalAllowed() return I.UI.getWindowsForMode(I.UI.MODE.Interface)[I.UI.WINDOW.Magic] end -local function onInputAction(action) - if not Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) then - return +local movementControlsOverridden = false + +local autoMove = false +local function processMovement() + local movement = input.getRangeActionValue('MoveForward') - input.getRangeActionValue('MoveBackward') + local sideMovement = input.getRangeActionValue('MoveRight') - input.getRangeActionValue('MoveLeft') + local run = input.getBooleanActionValue('Run') ~= settings:get('alwaysRun') + + if movement ~= 0 or not Actor.canMove(self) then + autoMove = false + elseif autoMove then + movement = 1 end - if not uiControlsOverridden then - if action == input.ACTION.Inventory then - if I.UI.getMode() == nil then - I.UI.setMode(I.UI.MODE.Interface) - elseif I.UI.getMode() == I.UI.MODE.Interface or I.UI.getMode() == I.UI.MODE.Container then - I.UI.removeMode(I.UI.getMode()) - end - elseif action == input.ACTION.Journal then - if I.UI.getMode() == I.UI.MODE.Journal then - I.UI.removeMode(I.UI.MODE.Journal) - elseif isJournalAllowed() then - I.UI.addMode(I.UI.MODE.Journal) - end - elseif action == input.ACTION.QuickKeysMenu then - if I.UI.getMode() == I.UI.MODE.QuickKeysMenu then - I.UI.removeMode(I.UI.MODE.QuickKeysMenu) - elseif checkNotWerewolf() and Player.isCharGenFinished(self) then - I.UI.addMode(I.UI.MODE.QuickKeysMenu) - end + self.controls.movement = movement + self.controls.sideMovement = sideMovement + self.controls.run = run + + if not settings:get('toggleSneak') then + self.controls.sneak = input.getBooleanActionValue('Sneak') + end +end + +local function controlsAllowed() + return not core.isWorldPaused() + and Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) + and not I.UI.getMode() +end + +local function movementAllowed() + return controlsAllowed() and not movementControlsOverridden +end + +input.registerTriggerHandler('Jump', async:callback(function() + if not movementAllowed() then return end + self.controls.jump = Player.getControlSwitch(self, Player.CONTROL_SWITCH.Jumping) +end)) + +input.registerTriggerHandler('ToggleSneak', async:callback(function() + if not movementAllowed() then return end + if settings:get('toggleSneak') then + self.controls.sneak = not self.controls.sneak + end +end)) + +input.registerTriggerHandler('AlwaysRun', async:callback(function() + if not movementAllowed() then return end + settings:set('alwaysRun', not settings:get('alwaysRun')) +end)) + +input.registerTriggerHandler('AutoMove', async:callback(function() + if not movementAllowed() then return end + autoMove = not autoMove +end)) + +local combatControlsOverridden = false + +local function combatAllowed() + return controlsAllowed() and not combatControlsOverridden +end + +input.registerTriggerHandler('ToggleSpell', async:callback(function() + if not combatAllowed() then return end + if Actor.stance(self) == Actor.STANCE.Spell then + Actor.setStance(self, Actor.STANCE.Nothing) + elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Magic) then + if checkNotWerewolf() then + Actor.setStance(self, Actor.STANCE.Spell) end end +end)) - if core.isWorldPaused() or I.UI.getMode() then - return +input.registerTriggerHandler('ToggleWeapon', async:callback(function() + if not combatAllowed() then return end + if Actor.stance(self) == Actor.STANCE.Weapon then + Actor.setStance(self, Actor.STANCE.Nothing) + elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then + Actor.setStance(self, Actor.STANCE.Weapon) end +end)) - if action == input.ACTION.Jump then - attemptJump = true - elseif action == input.ACTION.Use then - startAttack = Actor.stance(self) ~= Actor.STANCE.Nothing - elseif action == input.ACTION.AutoMove and not movementControlsOverridden then - autoMove = not autoMove - elseif action == input.ACTION.AlwaysRun and not movementControlsOverridden then - settings:set('alwaysRun', not settings:get('alwaysRun')) - elseif action == input.ACTION.Sneak and not movementControlsOverridden then - if settings:get('toggleSneak') then - self.controls.sneak = not self.controls.sneak - end - elseif action == input.ACTION.ToggleSpell and not combatControlsOverridden then - if Actor.stance(self) == Actor.STANCE.Spell then - Actor.setStance(self, Actor.STANCE.Nothing) - elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Magic) then - if checkNotWerewolf() then - Actor.setStance(self, Actor.STANCE.Spell) - end - end - elseif action == input.ACTION.ToggleWeapon and not combatControlsOverridden then - if Actor.stance(self) == Actor.STANCE.Weapon then - Actor.setStance(self, Actor.STANCE.Nothing) - elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then - Actor.setStance(self, Actor.STANCE.Weapon) - end +local startUse = false +input.registerActionHandler('Use', async:callback(function(value) + if value then startUse = true end +end)) +local function processAttacking() + if Actor.stance(self) == Actor.STANCE.Spell then + self.controls.use = startUse and 1 or 0 + else + self.controls.use = input.getBooleanActionValue('Use') and 1 or 0 + end + startUse = false +end + +local uiControlsOverridden = false + +input.registerTriggerHandler('ToggleWeapon', async:callback(function() + if not combatAllowed() then return end + if Actor.stance(self) == Actor.STANCE.Weapon then + Actor.setStance(self, Actor.STANCE.Nothing) + elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then + Actor.setStance(self, Actor.STANCE.Weapon) + end +end)) + + +local function uiAllowed() + return Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) and not uiControlsOverridden +end + +input.registerTriggerHandler('Inventory', async:callback(function() + if not uiAllowed() then return end + + if I.UI.getMode() == nil then + I.UI.setMode(I.UI.MODE.Interface) + elseif I.UI.getMode() == I.UI.MODE.Interface or I.UI.getMode() == I.UI.MODE.Container then + I.UI.removeMode(I.UI.getMode()) + end +end)) + +input.registerTriggerHandler('Journal', async:callback(function() + if not uiAllowed() then return end + + if I.UI.getMode() == I.UI.MODE.Journal then + I.UI.removeMode(I.UI.MODE.Journal) + elseif isJournalAllowed() then + I.UI.addMode(I.UI.MODE.Journal) + end +end)) + +input.registerTriggerHandler('QuickKeysMenu', async:callback(function() + if not uiAllowed() then return end + + if I.UI.getMode() == I.UI.MODE.QuickKeysMenu then + I.UI.removeMode(I.UI.MODE.QuickKeysMenu) + elseif checkNotWerewolf() and Player.isCharGenFinished(self) then + I.UI.addMode(I.UI.MODE.QuickKeysMenu) + end +end)) + +local function onFrame(_) + if movementAllowed() then + processMovement() + elseif not movementControlsOverridden then + self.controls.movement = 0 + self.controls.sideMovement = 0 + self.controls.jump = false + end + if combatAllowed() then + processAttacking() end end local function onSave() - return {sneaking = self.controls.sneak} + return { + sneaking = self.controls.sneak + } end local function onLoad(data) @@ -211,7 +279,6 @@ end return { engineHandlers = { onFrame = onFrame, - onInputAction = onInputAction, onSave = onSave, onLoad = onLoad, }, @@ -242,4 +309,3 @@ return { overrideUiControls = function(v) uiControlsOverridden = v end, } } - diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 4ca4e5af4e..563a4ab1f5 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -11,8 +11,7 @@ -- @return #boolean --- --- Is a specific control currently pressed. --- Input bindings can be changed ingame using Options/Controls menu. +-- (DEPRECATED, use getBooleanActionValue) Input bindings can be changed ingame using Options/Controls menu. -- @function [parent=#input] isActionPressed -- @param #number actionId One of @{openmw.input#ACTION} -- @return #boolean @@ -108,6 +107,7 @@ -- @field [parent=#input] #CONTROL_SWITCH CONTROL_SWITCH --- +-- (DEPRECATED, use actions with matching keys) -- @type ACTION -- @field [parent=#ACTION] #number GameMenu -- @field [parent=#ACTION] #number Screenshot @@ -153,7 +153,7 @@ -- @field [parent=#ACTION] #number TogglePostProcessorHUD --- --- Values that can be used with isActionPressed. +-- (DEPRECATED, use getBooleanActionValue) Values that can be used with isActionPressed. -- @field [parent=#input] #ACTION ACTION --- @@ -187,10 +187,10 @@ -- @field [parent=#CONTROLLER_AXIS] #number RightY Right stick vertical axis (from -1 to 1) -- @field [parent=#CONTROLLER_AXIS] #number TriggerLeft Left trigger (from 0 to 1) -- @field [parent=#CONTROLLER_AXIS] #number TriggerRight Right trigger (from 0 to 1) --- @field [parent=#CONTROLLER_AXIS] #number LookUpDown View direction vertical axis (RightY by default, can be mapped to another axis in Options/Controls menu) --- @field [parent=#CONTROLLER_AXIS] #number LookLeftRight View direction horizontal axis (RightX by default, can be mapped to another axis in Options/Controls menu) --- @field [parent=#CONTROLLER_AXIS] #number MoveForwardBackward Movement forward/backward (LeftY by default, can be mapped to another axis in Options/Controls menu) --- @field [parent=#CONTROLLER_AXIS] #number MoveLeftRight Side movement (LeftX by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number LookUpDown (DEPRECATED, use the LookUpDown action) View direction vertical axis (RightY by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number LookLeftRight (DEPRECATED, use the LookLeftRight action) View direction horizontal axis (RightX by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number MoveForwardBackward (DEPRECATED, use the MoveForwardBackward action) Movement forward/backward (LeftY by default, can be mapped to another axis in Options/Controls menu) +-- @field [parent=#CONTROLLER_AXIS] #number MoveLeftRight (DEPRECATED, use the MoveLeftRight action) Side movement (LeftX by default, can be mapped to another axis in Options/Controls menu) --- -- Values that can be used with getAxisValue. @@ -327,4 +327,105 @@ -- @field [parent=#TouchEvent] openmw.util#Vector2 position Relative position on the touch device (0 to 1 from top left corner), -- @field [parent=#TouchEvent] #number pressure Pressure of the finger. +--- +-- @type ActionType + +--- +-- @type ACTION_TYPE +-- @field #ActionType Boolean Input action with value of true or false +-- @field #ActionType Number Input action with a numeric value +-- @field #ActionType Range Input action with a numeric value between 0 and 1 (inclusive) + +--- +-- Values that can be used in registerAction +-- @field [parent=#input] #ACTION_TYPE ACTION_TYPE + +--- +-- @type ActionInfo +-- @field [parent=#Actioninfo] #string key +-- @field [parent=#Actioninfo] #ActionType type +-- @field [parent=#Actioninfo] #string l10n Localization context containing the name and description keys +-- @field [parent=#Actioninfo] #string name Localization key of the action's name +-- @field [parent=#Actioninfo] #string description Localization key of the action's description +-- @field [parent=#Actioninfo] defaultValue initial value of the action + +--- +-- Map of all currently registered actions +-- @field [parent=#input] #map<#string,#ActionInfo> actions + +--- +-- Registers a new input action. The key must be unique +-- @function [parent=#input] registerAction +-- @param #ActionInfo info + +--- +-- Provides a function computing the value of given input action. +-- The callback is called once a frame, after the values of dependency actions are resolved. +-- Throws an error if a cyclic action dependency is detected. +-- @function [parent=#input] bindAction +-- @param #string key +-- @param openmw.async#Callback callback returning the new value of the action, and taking as arguments: +-- frame time in seconds, +-- value of the function, +-- value of the first dependency action, +-- ... +-- @param #list<#string> dependencies +-- @usage +-- input.bindAction('Activate', async:callback(function(dt, use, sneak, run) +-- -- while sneaking, only activate things while holding the run binding +-- return use and (run or not sneak) +-- end), { 'Sneak', 'Run' }) + +--- +-- Registers a function to be called whenever the action's value changes +-- @function [parent=#input] registerActionHandler +-- @param #string key +-- @param openmw.async#Callback callback takes the new action value as the only argument + +--- +-- Returns the value of a Boolean action +-- @function [parent=#input] getBooleanActionValue +-- @param #string key +-- @return #boolean + +--- +-- Returns the value of a Number action +-- @function [parent=#input] getNumberActionValue +-- @param #string key +-- @return #number + +--- +-- Returns the value of a Range action +-- @function [parent=#input] getRangeActionValue +-- @param #string key +-- @return #number + +--- +-- @type TriggerInfo +-- @field [parent=#Actioninfo] #string key +-- @field [parent=#Actioninfo] #string l10n Localization context containing the name and description keys +-- @field [parent=#Actioninfo] #string name Localization key of the trigger's name +-- @field [parent=#Actioninfo] #string description Localization key of the trigger's description + +--- +-- Map of all currently registered triggers +-- @field [parent=#input] #map<#string,#TriggerInfo> triggers + +--- +-- Registers a new input trigger. The key must be unique +-- @function [parent=#input] registerTrigger +-- @param #TriggerInfo info + +--- +-- Registers a function to be called whenever the trigger activates +-- @function [parent=#input] registerTriggerHandler +-- @param #string key +-- @param openmw.async#Callback callback takes the new action value as the only argument + +--- +-- Activates the trigger with the given key +-- @function [parent=#input] activateTrigger +-- @param #string key + + return nil From f83a7b711a87cc01e0ad035b73129dce20b70948 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 30 Dec 2023 02:43:38 +0300 Subject: [PATCH 158/231] Don't handle per-vertex normals as tangent space normals --- files/shaders/compatibility/bs/default.frag | 2 +- files/shaders/compatibility/bs/default.vert | 2 +- files/shaders/compatibility/bs/nolighting.vert | 2 +- files/shaders/compatibility/groundcover.vert | 2 +- files/shaders/compatibility/objects.frag | 2 +- files/shaders/compatibility/objects.vert | 2 +- files/shaders/compatibility/terrain.frag | 2 +- files/shaders/compatibility/terrain.vert | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index ec5f5c8978..77131c6a52 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -80,7 +80,7 @@ void main() vec3 viewNormal = normalToView(normalTex.xyz * 2.0 - 1.0); specularColor *= normalTex.a; #else - vec3 viewNormal = normalToView(normalize(passNormal)); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif float shadowing = unshadowedLightRatio(linearDepth); diff --git a/files/shaders/compatibility/bs/default.vert b/files/shaders/compatibility/bs/default.vert index d9d47843c0..21942ec91e 100644 --- a/files/shaders/compatibility/bs/default.vert +++ b/files/shaders/compatibility/bs/default.vert @@ -69,7 +69,7 @@ void main(void) #if @shadows_enabled - vec3 viewNormal = normalToView(passNormal); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); setupShadowCoords(viewPos, viewNormal); #endif } diff --git a/files/shaders/compatibility/bs/nolighting.vert b/files/shaders/compatibility/bs/nolighting.vert index 3b0fa7b626..57cedc6e94 100644 --- a/files/shaders/compatibility/bs/nolighting.vert +++ b/files/shaders/compatibility/bs/nolighting.vert @@ -63,7 +63,7 @@ void main(void) } #if @shadows_enabled - vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); setupShadowCoords(viewPos, viewNormal); #endif } diff --git a/files/shaders/compatibility/groundcover.vert b/files/shaders/compatibility/groundcover.vert index 95a17b5084..8cf53a19e0 100644 --- a/files/shaders/compatibility/groundcover.vert +++ b/files/shaders/compatibility/groundcover.vert @@ -156,7 +156,7 @@ void main(void) #endif #if (!PER_PIXEL_LIGHTING || @shadows_enabled) - vec3 viewNormal = normalToView(passNormal); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif #if @diffuseMap diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 043aa266d8..56c7abf27c 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -169,7 +169,7 @@ vec2 screenCoords = gl_FragCoord.xy / screenRes; #if @normalMap vec3 viewNormal = normalToView(texture2D(normalMap, normalMapUV + offset).xyz * 2.0 - 1.0); #else - vec3 viewNormal = normalToView(normalize(passNormal)); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif vec3 viewVec = normalize(passViewPos); diff --git a/files/shaders/compatibility/objects.vert b/files/shaders/compatibility/objects.vert index 2bebcd60bf..081ff909cf 100644 --- a/files/shaders/compatibility/objects.vert +++ b/files/shaders/compatibility/objects.vert @@ -101,7 +101,7 @@ void main(void) #endif #if @envMap || !PER_PIXEL_LIGHTING || @shadows_enabled - vec3 viewNormal = normalToView(passNormal); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif #if @envMap diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index 38b985223e..abc7425eb0 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -65,7 +65,7 @@ void main() #if @normalMap vec3 viewNormal = normalToView(texture2D(normalMap, adjustedUV).xyz * 2.0 - 1.0); #else - vec3 viewNormal = normalToView(normalize(passNormal)); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif float shadowing = unshadowedLightRatio(linearDepth); diff --git a/files/shaders/compatibility/terrain.vert b/files/shaders/compatibility/terrain.vert index 3b2cb16db4..cbfb7769ba 100644 --- a/files/shaders/compatibility/terrain.vert +++ b/files/shaders/compatibility/terrain.vert @@ -52,7 +52,7 @@ void main(void) #endif #if !PER_PIXEL_LIGHTING || @shadows_enabled - vec3 viewNormal = normalToView(passNormal); + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); #endif #if !PER_PIXEL_LIGHTING From 91a01ae1e2be1034d04a0b103b5b81d5bb60fa0b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 30 Dec 2023 03:23:27 +0300 Subject: [PATCH 159/231] Remove duplicate ToggleWeapon trigger handler registration --- files/data/scripts/omw/playercontrols.lua | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index d3f9ecea5f..f14724f0a2 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -208,16 +208,6 @@ end local uiControlsOverridden = false -input.registerTriggerHandler('ToggleWeapon', async:callback(function() - if not combatAllowed() then return end - if Actor.stance(self) == Actor.STANCE.Weapon then - Actor.setStance(self, Actor.STANCE.Nothing) - elseif Player.getControlSwitch(self, Player.CONTROL_SWITCH.Fighting) then - Actor.setStance(self, Actor.STANCE.Weapon) - end -end)) - - local function uiAllowed() return Player.getControlSwitch(self, Player.CONTROL_SWITCH.Controls) and not uiControlsOverridden end From 640fa53bb82ce4ef079d91daf22e7b037b785bfd Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 30 Dec 2023 04:00:20 +0300 Subject: [PATCH 160/231] Make rain and snow ripple settings global rather than per-weather (#7748) --- apps/openmw/mwrender/sky.cpp | 15 ++++++++++++--- apps/openmw/mwrender/sky.hpp | 3 ++- apps/openmw/mwrender/skyutil.hpp | 1 - apps/openmw/mwworld/weather.cpp | 4 ---- apps/openmw/mwworld/weather.hpp | 2 -- components/fallback/validate.cpp | 6 ++---- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 87c6d1f2e7..6df3734252 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -257,7 +257,8 @@ namespace MWRender , mRainMaxHeight(0.f) , mRainEntranceSpeed(1.f) , mRainMaxRaindrops(0) - , mRipples(false) + , mRainRipplesEnabled(Fallback::Map::getBool("Weather_Rain_Ripples")) + , mSnowRipplesEnabled(Fallback::Map::getBool("Weather_Snow_Ripples")) , mWindSpeed(0.f) , mBaseWindSpeed(0.f) , mEnabled(true) @@ -519,7 +520,16 @@ namespace MWRender bool SkyManager::getRainRipplesEnabled() const { - return mRipples; + if (!mEnabled || mIsStorm) + return false; + + if (hasRain()) + return mRainRipplesEnabled; + + if (mParticleNode && mCurrentParticleEffect == "meshes\\snow.nif") + return mSnowRipplesEnabled; + + return false; } float SkyManager::getPrecipitationAlpha() const @@ -636,7 +646,6 @@ namespace MWRender mRainMinHeight = weather.mRainMinHeight; mRainMaxHeight = weather.mRainMaxHeight; mRainSpeed = weather.mRainSpeed; - mRipples = weather.mRipples; mWindSpeed = weather.mWindSpeed; mBaseWindSpeed = weather.mBaseWindSpeed; diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 9e4801ad7d..96af2e6ec9 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -196,7 +196,8 @@ namespace MWRender float mRainMaxHeight; float mRainEntranceSpeed; int mRainMaxRaindrops; - bool mRipples; + bool mRainRipplesEnabled; + bool mSnowRipplesEnabled; float mWindSpeed; float mBaseWindSpeed; diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index 9e279fb5c2..1018724595 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -77,7 +77,6 @@ namespace MWRender float mRainSpeed; float mRainEntranceSpeed; int mRainMaxRaindrops; - bool mRipples; osg::Vec3f mStormDirection; osg::Vec3f mNextStormDirection; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 5e2d7d3fec..aa75730b40 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -175,7 +175,6 @@ namespace MWWorld , mRainMaxHeight(Fallback::Map::getFloat("Weather_" + name + "_Rain_Height_Max")) , mParticleEffect(particleEffect) , mRainEffect(Fallback::Map::getBool("Weather_" + name + "_Using_Precip") ? "meshes\\raindrop.nif" : "") - , mRipples(Fallback::Map::getBool("Weather_" + name + "_Ripples")) , mStormDirection(Weather::defaultDirection()) , mCloudsMaximumPercent(Fallback::Map::getFloat("Weather_" + name + "_Clouds_Maximum_Percent")) , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) @@ -1130,7 +1129,6 @@ namespace MWWorld mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; - mResult.mRipples = current.mRipples; mResult.mParticleEffect = current.mParticleEffect; mResult.mRainEffect = current.mRainEffect; @@ -1243,7 +1241,6 @@ namespace MWWorld mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; mResult.mRainMaxRaindrops = current.mRainMaxRaindrops; - mResult.mRipples = current.mRipples; } else { @@ -1260,7 +1257,6 @@ namespace MWWorld mResult.mRainMinHeight = other.mRainMinHeight; mResult.mRainMaxHeight = other.mRainMaxHeight; mResult.mRainMaxRaindrops = other.mRainMaxRaindrops; - mResult.mRipples = other.mRipples; } } } diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index d40f7030c8..65f926c096 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -191,8 +191,6 @@ namespace MWWorld std::string mRainEffect; - bool mRipples; - osg::Vec3f mStormDirection; float mCloudsMaximumPercent; diff --git a/components/fallback/validate.cpp b/components/fallback/validate.cpp index 2a6d1817c9..8dfcd97570 100644 --- a/components/fallback/validate.cpp +++ b/components/fallback/validate.cpp @@ -11,10 +11,8 @@ static const std::set allowedKeysInt = { "LightAttenuation_Lin "Water_RippleFrameCount", "Water_SurfaceTileCount", "Water_SurfaceFrameCount", "Weather_Clear_Using_Precip", "Weather_Cloudy_Using_Precip", "Weather_Foggy_Using_Precip", "Weather_Overcast_Using_Precip", "Weather_Rain_Using_Precip", "Weather_Thunderstorm_Using_Precip", "Weather_Ashstorm_Using_Precip", - "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", - "Weather_Clear_Ripples", "Weather_Cloudy_Ripples", "Weather_Foggy_Ripples", "Weather_Overcast_Ripples", - "Weather_Rain_Ripples", "Weather_Thunderstorm_Ripples", "Weather_Ashstorm_Ripples", "Weather_Blight_Ripples", - "Weather_Snow_Ripples", "Weather_Blizzard_Ripples" }; + "Weather_Blight_Using_Precip", "Weather_Snow_Using_Precip", "Weather_Blizzard_Using_Precip", "Weather_Rain_Ripples", + "Weather_Snow_Ripples" }; static const std::set allowedKeysFloat = { "General_Werewolf_FOV", "Inventory_DirectionalAmbientB", "Inventory_DirectionalAmbientG", "Inventory_DirectionalAmbientR", "Inventory_DirectionalDiffuseB", From e63933efa6da7d7ac23952e1cca62eb944323784 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 31 Dec 2023 17:12:46 +0000 Subject: [PATCH 161/231] Use NAM9 for stack count --- CHANGELOG.md | 1 + apps/esmtool/esmtool.cpp | 2 +- apps/essimporter/converter.cpp | 2 +- apps/essimporter/convertinventory.cpp | 2 +- apps/opencs/model/tools/referencecheck.cpp | 5 +- apps/opencs/model/world/columnimp.hpp | 10 ++-- apps/opencs/model/world/columns.cpp | 2 +- apps/opencs/model/world/columns.hpp | 2 +- apps/opencs/model/world/data.cpp | 2 +- apps/opencs/model/world/refidcollection.cpp | 2 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwclass/creature.cpp | 10 ++-- apps/openmw/mwclass/creaturelevlist.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 12 ++-- apps/openmw/mwclass/npc.cpp | 10 ++-- apps/openmw/mwgui/alchemywindow.cpp | 6 +- apps/openmw/mwgui/containeritemmodel.cpp | 14 ++--- apps/openmw/mwgui/draganddrop.cpp | 2 +- apps/openmw/mwgui/enchantingdialog.cpp | 2 +- apps/openmw/mwgui/inventoryitemmodel.cpp | 2 +- apps/openmw/mwgui/inventorywindow.cpp | 9 ++- apps/openmw/mwgui/itemmodel.cpp | 2 +- apps/openmw/mwgui/quickkeysmenu.cpp | 6 +- apps/openmw/mwgui/recharge.cpp | 2 +- apps/openmw/mwgui/referenceinterface.cpp | 6 +- apps/openmw/mwgui/repair.cpp | 4 +- apps/openmw/mwgui/spellmodel.cpp | 2 +- apps/openmw/mwgui/tooltips.cpp | 8 +-- apps/openmw/mwlua/cellbindings.cpp | 2 +- apps/openmw/mwlua/luamanagerimp.cpp | 5 +- apps/openmw/mwlua/objectbindings.cpp | 32 +++++------ apps/openmw/mwlua/types/actor.cpp | 4 +- apps/openmw/mwlua/types/item.cpp | 2 +- apps/openmw/mwmechanics/aiactivate.cpp | 2 +- apps/openmw/mwmechanics/aicombat.cpp | 5 +- apps/openmw/mwmechanics/aifollow.cpp | 2 +- apps/openmw/mwmechanics/aipackage.cpp | 2 +- apps/openmw/mwmechanics/aipursue.cpp | 4 +- apps/openmw/mwmechanics/alchemy.cpp | 4 +- .../mwmechanics/mechanicsmanagerimp.cpp | 16 ++++-- apps/openmw/mwmechanics/recharge.cpp | 4 +- apps/openmw/mwrender/actoranimation.cpp | 6 +- apps/openmw/mwscript/containerextensions.cpp | 8 +-- apps/openmw/mwscript/miscextensions.cpp | 6 +- apps/openmw/mwworld/actionharvest.cpp | 2 +- apps/openmw/mwworld/actiontake.cpp | 10 ++-- apps/openmw/mwworld/cellref.cpp | 12 ++-- apps/openmw/mwworld/cellref.hpp | 16 +++--- apps/openmw/mwworld/cellstore.cpp | 55 +++++++++---------- apps/openmw/mwworld/cellstore.hpp | 2 +- apps/openmw/mwworld/class.cpp | 2 +- apps/openmw/mwworld/containerstore.cpp | 50 +++++++++-------- apps/openmw/mwworld/inventorystore.cpp | 28 +++++----- apps/openmw/mwworld/livecellref.cpp | 5 ++ apps/openmw/mwworld/livecellref.hpp | 3 + apps/openmw/mwworld/localscripts.cpp | 6 +- apps/openmw/mwworld/localscripts.hpp | 2 +- apps/openmw/mwworld/manualref.cpp | 2 +- apps/openmw/mwworld/projectilemanager.cpp | 2 +- apps/openmw/mwworld/ptr.cpp | 2 +- apps/openmw/mwworld/refdata.cpp | 29 ---------- apps/openmw/mwworld/refdata.hpp | 14 ----- apps/openmw/mwworld/scene.cpp | 4 +- apps/openmw/mwworld/worldimp.cpp | 24 ++++---- apps/openmw/mwworld/worldimp.hpp | 2 +- apps/openmw_test_suite/esm3/testsaveload.cpp | 4 +- components/esm3/cellref.cpp | 8 +-- components/esm3/cellref.hpp | 3 +- components/esm3/formatversion.hpp | 3 +- components/esm3/inventorystate.cpp | 8 +-- components/esm3/objectstate.cpp | 11 ++-- components/esm3/objectstate.hpp | 2 - components/esm4/loadachr.cpp | 6 +- components/esm4/loadachr.hpp | 2 + components/esm4/loadrefr.cpp | 6 +- components/esm4/loadrefr.hpp | 2 +- 76 files changed, 269 insertions(+), 293 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d09f00b9e..26d6c46ce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves Bug #7742: Governing attribute training limit should use the modified attribute + Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty Feature #5492: Let rain and snow collide with statics diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index 88342a2a5b..a36996ff4f 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -265,7 +265,7 @@ namespace std::cout << " Faction rank: " << ref.mFactionRank << '\n'; std::cout << " Enchantment charge: " << ref.mEnchantmentCharge << '\n'; std::cout << " Uses/health: " << ref.mChargeInt << '\n'; - std::cout << " Gold value: " << ref.mGoldValue << '\n'; + std::cout << " Count: " << ref.mCount << '\n'; std::cout << " Blocked: " << static_cast(ref.mReferenceBlocked) << '\n'; std::cout << " Deleted: " << deleted << '\n'; if (!ref.mKey.empty()) diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp index b44d376842..07146fc388 100644 --- a/apps/essimporter/converter.cpp +++ b/apps/essimporter/converter.cpp @@ -34,7 +34,7 @@ namespace objstate.mPosition = cellref.mPos; objstate.mRef.mRefNum = cellref.mRefNum; if (cellref.mDeleted) - objstate.mCount = 0; + objstate.mRef.mCount = 0; convertSCRI(cellref.mActorData.mSCRI, objstate.mLocals); objstate.mHasLocals = !objstate.mLocals.mVariables.empty(); diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp index 69a2ea4120..7025b0ae43 100644 --- a/apps/essimporter/convertinventory.cpp +++ b/apps/essimporter/convertinventory.cpp @@ -16,7 +16,7 @@ namespace ESSImport objstate.blank(); objstate.mRef = item; objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId); - objstate.mCount = item.mCount; + objstate.mRef.mCount = item.mCount; state.mItems.push_back(objstate); if (item.mRelativeEquipmentSlot != -1) // Note we should really write the absolute slot here, which we do not know about diff --git a/apps/opencs/model/tools/referencecheck.cpp b/apps/opencs/model/tools/referencecheck.cpp index 87d133a59e..511458b946 100644 --- a/apps/opencs/model/tools/referencecheck.cpp +++ b/apps/opencs/model/tools/referencecheck.cpp @@ -98,9 +98,8 @@ void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages& message if (cellRef.mEnchantmentCharge < -1) messages.add(id, "Negative number of enchantment points", "", CSMDoc::Message::Severity_Error); - // Check if gold value isn't negative - if (cellRef.mGoldValue < 0) - messages.add(id, "Negative gold value", "", CSMDoc::Message::Severity_Error); + if (cellRef.mCount < 1) + messages.add(id, "Reference without count", {}, CSMDoc::Message::Severity_Error); } int CSMTools::ReferenceCheckStage::setup() diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index 74b81cebc1..eba09bd8b1 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1111,19 +1111,19 @@ namespace CSMWorld }; template - struct GoldValueColumn : public Column + struct StackSizeColumn : public Column { - GoldValueColumn() - : Column(Columns::ColumnId_CoinValue, ColumnBase::Display_Integer) + StackSizeColumn() + : Column(Columns::ColumnId_StackCount, ColumnBase::Display_Integer) { } - QVariant get(const Record& record) const override { return record.get().mGoldValue; } + QVariant get(const Record& record) const override { return record.get().mCount; } void set(Record& record, const QVariant& data) override { ESXRecordT record2 = record.get(); - record2.mGoldValue = data.toInt(); + record2.mCount = data.toInt(); record.setModified(record2); } diff --git a/apps/opencs/model/world/columns.cpp b/apps/opencs/model/world/columns.cpp index 4a476b52f3..45759cd234 100644 --- a/apps/opencs/model/world/columns.cpp +++ b/apps/opencs/model/world/columns.cpp @@ -56,7 +56,7 @@ namespace CSMWorld { ColumnId_FactionIndex, "Faction Index" }, { ColumnId_Charges, "Charges" }, { ColumnId_Enchantment, "Enchantment" }, - { ColumnId_CoinValue, "Coin Value" }, + { ColumnId_StackCount, "Count" }, { ColumnId_Teleport, "Teleport" }, { ColumnId_TeleportCell, "Teleport Cell" }, { ColumnId_LockLevel, "Lock Level" }, diff --git a/apps/opencs/model/world/columns.hpp b/apps/opencs/model/world/columns.hpp index dff8b2d7dd..74e5bdd006 100644 --- a/apps/opencs/model/world/columns.hpp +++ b/apps/opencs/model/world/columns.hpp @@ -45,7 +45,7 @@ namespace CSMWorld ColumnId_FactionIndex = 31, ColumnId_Charges = 32, ColumnId_Enchantment = 33, - ColumnId_CoinValue = 34, + ColumnId_StackCount = 34, ColumnId_Teleport = 35, ColumnId_TeleportCell = 36, ColumnId_LockLevel = 37, diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index 9b137a6602..ea4c8966d8 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -587,7 +587,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mRefs.addColumn(new FactionIndexColumn); mRefs.addColumn(new ChargesColumn); mRefs.addColumn(new EnchantmentChargesColumn); - mRefs.addColumn(new GoldValueColumn); + mRefs.addColumn(new StackSizeColumn); mRefs.addColumn(new TeleportColumn); mRefs.addColumn(new TeleportCellColumn); mRefs.addColumn(new PosColumn(&CellRef::mDoorDest, 0, true)); diff --git a/apps/opencs/model/world/refidcollection.cpp b/apps/opencs/model/world/refidcollection.cpp index b8c3974d75..694f67e445 100644 --- a/apps/opencs/model/world/refidcollection.cpp +++ b/apps/opencs/model/world/refidcollection.cpp @@ -97,7 +97,7 @@ CSMWorld::RefIdCollection::RefIdCollection() inventoryColumns.mIcon = &mColumns.back(); mColumns.emplace_back(Columns::ColumnId_Weight, ColumnBase::Display_Float); inventoryColumns.mWeight = &mColumns.back(); - mColumns.emplace_back(Columns::ColumnId_CoinValue, ColumnBase::Display_Integer); + mColumns.emplace_back(Columns::ColumnId_StackCount, ColumnBase::Display_Integer); inventoryColumns.mValue = &mColumns.back(); IngredientColumns ingredientColumns(inventoryColumns); diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 14e3b2b3b7..4247ef2e3e 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -185,7 +185,7 @@ namespace MWBase virtual std::string_view getCellName(const ESM::Cell* cell) const = 0; - virtual void removeRefScript(MWWorld::RefData* ref) = 0; + virtual void removeRefScript(const MWWorld::CellRef* ref) = 0; //< Remove the script attached to ref from mLocalScripts virtual MWWorld::Ptr getPtr(const ESM::RefId& name, bool activeOnly) = 0; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 052315ce54..810a7840d4 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -828,7 +828,7 @@ namespace MWClass } const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData(); - if (ptr.getRefData().getCount() <= 0 + if (ptr.getCellRef().getCount() <= 0 && (!isFlagBitSet(ptr, ESM::Creature::Respawn) || !customData.mCreatureStats.isDead())) { state.mHasCustomState = false; @@ -848,7 +848,7 @@ namespace MWClass void Creature::respawn(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); - if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + if (ptr.getCellRef().getCount() > 0 && !creatureStats.isDead()) return; if (!creatureStats.isDeathAnimationFinished()) @@ -860,16 +860,16 @@ namespace MWClass static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay - = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + = ptr.getCellRef().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (isFlagBitSet(ptr, ESM::Creature::Respawn) && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { - if (ptr.getRefData().getCount() == 0) + if (ptr.getCellRef().getCount() == 0) { - ptr.getRefData().setCount(1); + ptr.getCellRef().setCount(1); const ESM::RefId& script = getScript(ptr); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index 461cf07276..fbae54737c 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -81,7 +81,7 @@ namespace MWClass if (!creature.isEmpty()) { const MWMechanics::CreatureStats& creatureStats = creature.getClass().getCreatureStats(creature); - if (creature.getRefData().getCount() == 0) + if (creature.getCellRef().getCount() == 0) customData.mSpawn = true; else if (creatureStats.isDead()) { diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index ae78773fa1..fa91b607f2 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -79,8 +79,8 @@ namespace MWClass const MWWorld::LiveCellRef* ref = ptr.get(); int value = ref->mBase->mData.mValue; - if (ptr.getCellRef().getGoldValue() > 1 && ptr.getRefData().getCount() == 1) - value = ptr.getCellRef().getGoldValue(); + if (isGold(ptr) && ptr.getCellRef().getCount() != 1) + value = 1; if (!ptr.getCellRef().getSoul().empty()) { @@ -189,8 +189,7 @@ namespace MWClass const MWWorld::LiveCellRef* ref = newRef.getPtr().get(); MWWorld::Ptr ptr(cell.insert(ref), &cell); - ptr.getCellRef().setGoldValue(goldAmount); - ptr.getRefData().setCount(1); + ptr.getCellRef().setCount(goldAmount); return ptr; } @@ -203,7 +202,7 @@ namespace MWClass { const MWWorld::LiveCellRef* ref = ptr.get(); newPtr = MWWorld::Ptr(cell.insert(ref), &cell); - newPtr.getRefData().setCount(count); + newPtr.getCellRef().setCount(count); } newPtr.getCellRef().unsetRefNum(); newPtr.getRefData().setLuaScripts(nullptr); @@ -216,10 +215,9 @@ namespace MWClass MWWorld::Ptr newPtr; if (isGold(ptr)) { - newPtr = createGold(cell, getValue(ptr) * ptr.getRefData().getCount()); + newPtr = createGold(cell, getValue(ptr) * ptr.getCellRef().getCount()); newPtr.getRefData() = ptr.getRefData(); newPtr.getCellRef().setRefNum(ptr.getCellRef().getRefNum()); - newPtr.getRefData().setCount(1); } else { diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 8e2bf1b351..330a48daeb 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -1358,7 +1358,7 @@ namespace MWClass } const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData(); - if (ptr.getRefData().getCount() <= 0 + if (ptr.getCellRef().getCount() <= 0 && (!(ptr.get()->mBase->mFlags & ESM::NPC::Respawn) || !customData.mNpcStats.isDead())) { state.mHasCustomState = false; @@ -1395,7 +1395,7 @@ namespace MWClass void Npc::respawn(const MWWorld::Ptr& ptr) const { const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr); - if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead()) + if (ptr.getCellRef().getCount() > 0 && !creatureStats.isDead()) return; if (!creatureStats.isDeathAnimationFinished()) @@ -1407,16 +1407,16 @@ namespace MWClass static const float fCorpseClearDelay = gmst.find("fCorpseClearDelay")->mValue.getFloat(); float delay - = ptr.getRefData().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); + = ptr.getCellRef().getCount() == 0 ? fCorpseClearDelay : std::min(fCorpseRespawnDelay, fCorpseClearDelay); if (ptr.get()->mBase->mFlags & ESM::NPC::Respawn && creatureStats.getTimeOfDeath() + delay <= MWBase::Environment::get().getWorld()->getTimeStamp()) { if (ptr.getCellRef().hasContentFile()) { - if (ptr.getRefData().getCount() == 0) + if (ptr.getCellRef().getCount() == 0) { - ptr.getRefData().setCount(1); + ptr.getCellRef().setCount(1); const ESM::RefId& script = getScript(ptr); if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, ptr); diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index de3e0c19e0..4215782e2f 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -151,7 +151,7 @@ namespace MWGui if (mIngredients[i]->isUserString("ToolTipType")) { MWWorld::Ptr ingred = *mIngredients[i]->getUserData(); - if (ingred.getRefData().getCount() == 0) + if (ingred.getCellRef().getCount() == 0) mAlchemy->removeIngredient(i); } @@ -413,7 +413,7 @@ namespace MWGui } if (!item.isEmpty()) - mSortModel->addDragItem(item, item.getRefData().getCount()); + mSortModel->addDragItem(item, item.getCellRef().getCount()); if (ingredient->getChildCount()) MyGUI::Gui::getInstance().destroyWidget(ingredient->getChildAt(0)); @@ -428,7 +428,7 @@ namespace MWGui ingredient->setUserString("ToolTipType", "ItemPtr"); ingredient->setUserData(MWWorld::Ptr(item)); - ingredient->setCount(item.getRefData().getCount()); + ingredient->setCount(item.getCellRef().getCount()); } mItemView->update(); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index ba4f7156c9..af1b585cff 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -129,7 +129,7 @@ namespace MWGui { if (stacks(*it, item.mBase)) { - int quantity = it->mRef->mData.getCount(false); + int quantity = it->mRef->mRef.getCount(false); // If this is a restocking quantity, just don't remove it if (quantity < 0 && mTrading) toRemove += quantity; @@ -144,11 +144,11 @@ namespace MWGui { if (stacks(source, item.mBase)) { - int refCount = source.getRefData().getCount(); + int refCount = source.getCellRef().getCount(); if (refCount - toRemove <= 0) MWBase::Environment::get().getWorld()->deleteObject(source); else - source.getRefData().setCount(std::max(0, refCount - toRemove)); + source.getCellRef().setCount(std::max(0, refCount - toRemove)); toRemove -= refCount; if (toRemove <= 0) return; @@ -176,7 +176,7 @@ namespace MWGui if (stacks(*it, itemStack.mBase)) { // we already have an item stack of this kind, add to it - itemStack.mCount += it->getRefData().getCount(); + itemStack.mCount += it->getCellRef().getCount(); found = true; break; } @@ -185,7 +185,7 @@ namespace MWGui if (!found) { // no stack yet, create one - ItemStack newItem(*it, this, it->getRefData().getCount()); + ItemStack newItem(*it, this, it->getCellRef().getCount()); mItems.push_back(newItem); } } @@ -198,7 +198,7 @@ namespace MWGui if (stacks(source, itemStack.mBase)) { // we already have an item stack of this kind, add to it - itemStack.mCount += source.getRefData().getCount(); + itemStack.mCount += source.getCellRef().getCount(); found = true; break; } @@ -207,7 +207,7 @@ namespace MWGui if (!found) { // no stack yet, create one - ItemStack newItem(source, this, source.getRefData().getCount()); + ItemStack newItem(source, this, source.getCellRef().getCount()); mItems.push_back(newItem); } } diff --git a/apps/openmw/mwgui/draganddrop.cpp b/apps/openmw/mwgui/draganddrop.cpp index c99e97e37d..0fa2cc4e21 100644 --- a/apps/openmw/mwgui/draganddrop.cpp +++ b/apps/openmw/mwgui/draganddrop.cpp @@ -126,7 +126,7 @@ namespace MWGui void DragAndDrop::onFrame() { - if (mIsOnDragAndDrop && mItem.mBase.getRefData().getCount() == 0) + if (mIsOnDragAndDrop && mItem.mBase.getCellRef().getCount() == 0) finish(); } diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index f5bcb1fb5f..8264dd60b6 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -373,7 +373,7 @@ namespace MWGui { MWBase::Environment::get().getWindowManager()->playSound(ESM::RefId::stringRefId("enchant fail")); MWBase::Environment::get().getWindowManager()->messageBox("#{sNotifyMessage34}"); - if (!mEnchanting.getGem().isEmpty() && !mEnchanting.getGem().getRefData().getCount()) + if (!mEnchanting.getGem().isEmpty() && !mEnchanting.getGem().getCellRef().getCount()) { setSoulGem(MWWorld::Ptr()); mEnchanting.nextCastStyle(); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index e20637d58c..b52d49b7c2 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -115,7 +115,7 @@ namespace MWGui if (!item.getClass().showsInInventory(item)) continue; - ItemStack newItem(item, this, item.getRefData().getCount()); + ItemStack newItem(item, this, item.getCellRef().getCount()); if (mActor.getClass().hasInventoryStore(mActor)) { diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 932723c3fd..0fbd15dda2 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -560,7 +560,7 @@ namespace MWGui if (mEquippedStackableCount.has_value()) { // the count to unequip - int count = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - mEquippedStackableCount.value(); + int count = ptr.getCellRef().getCount() - mDragAndDrop->mDraggedCount - mEquippedStackableCount.value(); if (count > 0) { MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); @@ -604,7 +604,7 @@ namespace MWGui // Save the currently equipped count before useItem() if (slotIt != invStore.end() && slotIt->getCellRef().getRefId() == ptr.getCellRef().getRefId()) - mEquippedStackableCount = slotIt->getRefData().getCount(); + mEquippedStackableCount = slotIt->getCellRef().getCount(); else mEquippedStackableCount = 0; } @@ -735,7 +735,7 @@ namespace MWGui if (!object.getClass().hasToolTip(object)) return; - int count = object.getRefData().getCount(); + int count = object.getCellRef().getCount(); if (object.getClass().isGold(object)) count *= object.getClass().getValue(object); @@ -755,8 +755,7 @@ namespace MWGui // add to player inventory // can't use ActionTake here because we need an MWWorld::Ptr to the newly inserted object - MWWorld::Ptr newObject - = *player.getClass().getContainerStore(player).add(object, object.getRefData().getCount()); + MWWorld::Ptr newObject = *player.getClass().getContainerStore(player).add(object, count); // remove from world MWBase::Environment::get().getWorld()->deleteObject(object); diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 4bdd2399fe..a4cf48fcbe 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -58,7 +58,7 @@ namespace MWGui MWWorld::Ptr ItemModel::moveItem(const ItemStack& item, size_t count, ItemModel* otherModel, bool allowAutoEquip) { MWWorld::Ptr ret = MWWorld::Ptr(); - if (static_cast(item.mBase.getRefData().getCount()) <= count) + if (static_cast(item.mBase.getCellRef().getCount()) <= count) { // We are moving the full stack ret = otherModel->addItem(item, count, allowAutoEquip); diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index fb03ab99c9..204fa00492 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -85,7 +85,7 @@ namespace MWGui { MWWorld::Ptr item = *mKey[index].button->getUserData(); // Make sure the item is available and is not broken - if (item.isEmpty() || item.getRefData().getCount() < 1 + if (item.isEmpty() || item.getCellRef().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { // Try searching for a compatible replacement @@ -383,12 +383,12 @@ namespace MWGui item = nullptr; // check the item is available and not broken - if (item.isEmpty() || item.getRefData().getCount() < 1 + if (item.isEmpty() || item.getCellRef().getCount() < 1 || (item.getClass().hasItemHealth(item) && item.getClass().getItemHealth(item) <= 0)) { item = store.findReplacement(key->id); - if (item.isEmpty() || item.getRefData().getCount() < 1) + if (item.isEmpty() || item.getCellRef().getCount() < 1) { MWBase::Environment::get().getWindowManager()->messageBox("#{sQuickMenu5} " + key->name); diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index c2acea93ce..7d57988d97 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -75,7 +75,7 @@ namespace MWGui mChargeLabel->setCaptionWithReplacing("#{sCharges} " + MyGUI::utility::toString(creature->mData.mSoul)); - bool toolBoxVisible = (gem.getRefData().getCount() != 0); + bool toolBoxVisible = gem.getCellRef().getCount() != 0; mGemBox->setVisible(toolBoxVisible); mGemBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); diff --git a/apps/openmw/mwgui/referenceinterface.cpp b/apps/openmw/mwgui/referenceinterface.cpp index de7c93d862..6dad6b8543 100644 --- a/apps/openmw/mwgui/referenceinterface.cpp +++ b/apps/openmw/mwgui/referenceinterface.cpp @@ -2,14 +2,14 @@ namespace MWGui { - ReferenceInterface::ReferenceInterface() {} + ReferenceInterface::ReferenceInterface() = default; - ReferenceInterface::~ReferenceInterface() {} + ReferenceInterface::~ReferenceInterface() = default; void ReferenceInterface::checkReferenceAvailable() { // check if count of the reference has become 0 - if (!mPtr.isEmpty() && mPtr.getRefData().getCount() == 0) + if (!mPtr.isEmpty() && mPtr.getCellRef().getCount() == 0) { mPtr = MWWorld::Ptr(); onReferenceUnavailable(); diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 63b51d7d2c..c1602b8407 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -86,7 +86,7 @@ namespace MWGui mUsesLabel->setCaptionWithReplacing("#{sUses} " + MyGUI::utility::toString(uses)); mQualityLabel->setCaptionWithReplacing("#{sQuality} " + qualityStr.str()); - bool toolBoxVisible = (mRepair.getTool().getRefData().getCount() != 0); + bool toolBoxVisible = (mRepair.getTool().getCellRef().getCount() != 0); mToolBox->setVisible(toolBoxVisible); mToolBox->setUserString("Hidden", toolBoxVisible ? "false" : "true"); @@ -142,7 +142,7 @@ namespace MWGui void Repair::onRepairItem(MyGUI::Widget* /*sender*/, const MWWorld::Ptr& ptr) { - if (!mRepair.getTool().getRefData().getCount()) + if (!mRepair.getTool().getCellRef().getCount()) return; mRepair.repair(ptr); diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp index f340d072e0..385464da24 100644 --- a/apps/openmw/mwgui/spellmodel.cpp +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -137,7 +137,7 @@ namespace MWGui newSpell.mItem = item; newSpell.mId = item.getCellRef().getRefId(); newSpell.mName = item.getClass().getName(item); - newSpell.mCount = item.getRefData().getCount(); + newSpell.mCount = item.getCellRef().getCount(); newSpell.mType = Spell::Type_EnchantedItem; newSpell.mSelected = invStore.getSelectedEnchantItem() == it; diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index a13cfe4d77..bdc19a260d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -120,7 +120,7 @@ namespace MWGui tooltipSize = createToolTip(info, checkOwned()); } else - tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true); + tooltipSize = getToolTipViaPtr(mFocusObject.getCellRef().getCount(), true); MyGUI::IntPoint tooltipPosition = MyGUI::InputManager::getInstance().getMousePosition(); position(tooltipPosition, tooltipSize, viewSize); @@ -187,7 +187,7 @@ namespace MWGui if (mFocusObject.isEmpty()) return; - tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false, checkOwned()); + tooltipSize = getToolTipViaPtr(mFocusObject.getCellRef().getCount(), false, checkOwned()); } else if (type == "ItemModelIndex") { @@ -211,7 +211,7 @@ namespace MWGui mFocusObject = item; if (!mFocusObject.isEmpty()) - tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), false); + tooltipSize = getToolTipViaPtr(mFocusObject.getCellRef().getCount(), false); } else if (type == "Spell") { @@ -306,7 +306,7 @@ namespace MWGui { if (!mFocusObject.isEmpty()) { - MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getRefData().getCount(), true, checkOwned()); + MyGUI::IntSize tooltipSize = getToolTipViaPtr(mFocusObject.getCellRef().getCount(), true, checkOwned()); setCoord(viewSize.width / 2 - tooltipSize.width / 2, std::max(0, int(mFocusToolTipY * viewSize.height - tooltipSize.height)), tooltipSize.width, diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index e43354bfaf..62dcc69330 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -121,7 +121,7 @@ namespace MWLua cell.mStore->load(); ObjectIdList res = std::make_shared>(); auto visitor = [&](const MWWorld::Ptr& ptr) { - if (ptr.getRefData().isDeleted()) + if (ptr.mRef->isDeleted()) return true; MWBase::Environment::get().getWorldModel()->registerPtr(ptr); if (getLiveCellRefType(ptr.mRef) == ptr.getType()) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index baf13edac4..43092af3dc 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -140,9 +140,8 @@ namespace MWLua mObjectLists.update(); - std::erase_if(mActiveLocalScripts, [](const LocalScripts* l) { - return l->getPtrOrEmpty().isEmpty() || l->getPtrOrEmpty().getRefData().isDeleted(); - }); + std::erase_if(mActiveLocalScripts, + [](const LocalScripts* l) { return l->getPtrOrEmpty().isEmpty() || l->getPtrOrEmpty().mRef->isDeleted(); }); mGlobalScripts.statsNextFrame(); for (LocalScripts* scripts : mActiveLocalScripts) diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index e938d90e5e..f45decea3c 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -124,7 +124,7 @@ namespace MWLua newPtr = cls.moveToCell(ptr, *destCell, toPos(pos, rot)); ptr.getCellRef().unsetRefNum(); ptr.getRefData().setLuaScripts(nullptr); - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); ESM::RefId script = cls.getScript(newPtr); if (!script.empty()) world->getLocalScripts().add(script, newPtr); @@ -256,7 +256,7 @@ namespace MWLua [types = getTypeToPackageTable(context.mLua->sol())]( const ObjectT& o) mutable { return types[getLiveCellRefType(o.ptr().mRef)]; }); - objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getRefData().getCount(); }); + objectT["count"] = sol::readonly_property([](const ObjectT& o) { return o.ptr().getCellRef().getCount(); }); objectT[sol::meta_function::equal_to] = [](const ObjectT& a, const ObjectT& b) { return a.id() == b.id(); }; objectT[sol::meta_function::to_string] = &ObjectT::toString; objectT["sendEvent"] = [context](const ObjectT& dest, std::string eventName, const sol::object& eventData) { @@ -340,10 +340,10 @@ namespace MWLua auto isEnabled = [](const ObjectT& o) { return o.ptr().getRefData().isEnabled(); }; auto setEnabled = [context](const GObject& object, bool enable) { - if (enable && object.ptr().getRefData().isDeleted()) + if (enable && object.ptr().mRef->isDeleted()) throw std::runtime_error("Object is removed"); context.mLuaManager->addAction([object, enable] { - if (object.ptr().getRefData().isDeleted()) + if (object.ptr().mRef->isDeleted()) return; if (object.ptr().isInCell()) { @@ -417,20 +417,20 @@ namespace MWLua using DelayedRemovalFn = std::function; auto removeFn = [](const MWWorld::Ptr ptr, int countToRemove) -> std::optional { - int rawCount = ptr.getRefData().getCount(false); + int rawCount = ptr.getCellRef().getCount(false); int currentCount = std::abs(rawCount); int signedCountToRemove = (rawCount < 0 ? -1 : 1) * countToRemove; if (countToRemove <= 0 || countToRemove > currentCount) throw std::runtime_error("Can't remove " + std::to_string(countToRemove) + " of " + std::to_string(currentCount) + " items"); - ptr.getRefData().setCount(rawCount - signedCountToRemove); // Immediately change count + ptr.getCellRef().setCount(rawCount - signedCountToRemove); // Immediately change count if (!ptr.getContainerStore() && currentCount > countToRemove) return std::nullopt; // Delayed action to trigger side effects return [signedCountToRemove](MWWorld::Ptr ptr) { // Restore the original count - ptr.getRefData().setCount(ptr.getRefData().getCount(false) + signedCountToRemove); + ptr.getCellRef().setCount(ptr.getCellRef().getCount(false) + signedCountToRemove); // And now remove properly if (ptr.getContainerStore()) ptr.getContainerStore()->remove(ptr, std::abs(signedCountToRemove), false); @@ -443,7 +443,7 @@ namespace MWLua }; objectT["remove"] = [removeFn, context](const GObject& object, sol::optional count) { std::optional delayed - = removeFn(object.ptr(), count.value_or(object.ptr().getRefData().getCount())); + = removeFn(object.ptr(), count.value_or(object.ptr().getCellRef().getCount())); if (delayed.has_value()) context.mLuaManager->addAction([fn = *delayed, object] { fn(object.ptr()); }); }; @@ -463,7 +463,7 @@ namespace MWLua }; objectT["moveInto"] = [removeFn, context](const GObject& object, const sol::object& dest) { const MWWorld::Ptr& ptr = object.ptr(); - int count = ptr.getRefData().getCount(); + int count = ptr.getCellRef().getCount(); MWWorld::Ptr destPtr; if (dest.is()) destPtr = dest.as().ptr(); @@ -474,9 +474,9 @@ namespace MWLua std::optional delayedRemovalFn = removeFn(ptr, count); context.mLuaManager->addAction([item = object, count, cont = GObject(destPtr), delayedRemovalFn] { const MWWorld::Ptr& oldPtr = item.ptr(); - auto& refData = oldPtr.getRefData(); + auto& refData = oldPtr.getCellRef(); refData.setCount(count); // temporarily undo removal to run ContainerStore::add - refData.enable(); + oldPtr.getRefData().enable(); cont.ptr().getClass().getContainerStore(cont.ptr()).add(oldPtr, count, false); refData.setCount(0); if (delayedRemovalFn.has_value()) @@ -487,7 +487,7 @@ namespace MWLua const osg::Vec3f& pos, const sol::object& options) { MWWorld::CellStore* cell = findCell(cellOrName, pos); MWWorld::Ptr ptr = object.ptr(); - int count = ptr.getRefData().getCount(); + int count = ptr.getCellRef().getCount(); if (count == 0) throw std::runtime_error("Object is either removed or already in the process of teleporting"); osg::Vec3f rot = ptr.getRefData().getPosition().asRotationVec3(); @@ -508,9 +508,9 @@ namespace MWLua context.mLuaManager->addAction( [object, cell, pos, rot, count, delayedRemovalFn, placeOnGround] { MWWorld::Ptr oldPtr = object.ptr(); - oldPtr.getRefData().setCount(count); + oldPtr.getCellRef().setCount(count); MWWorld::Ptr newPtr = oldPtr.getClass().moveToCell(oldPtr, *cell); - oldPtr.getRefData().setCount(0); + oldPtr.getCellRef().setCount(0); newPtr.getRefData().disable(); teleportNotPlayer(newPtr, cell, pos, rot, placeOnGround); delayedRemovalFn(oldPtr); @@ -522,10 +522,10 @@ namespace MWLua [cell, pos, rot, placeOnGround] { teleportPlayer(cell, pos, rot, placeOnGround); }); else { - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); context.mLuaManager->addAction( [object, cell, pos, rot, count, placeOnGround] { - object.ptr().getRefData().setCount(count); + object.ptr().getCellRef().setCount(count); teleportNotPlayer(object.ptr(), cell, pos, rot, placeOnGround); }, "TeleportAction"); diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 370e9e7f69..a4449f6fb0 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -37,7 +37,7 @@ namespace MWLua itemPtr = MWBase::Environment::get().getWorldModel()->getPtr(std::get(item)); if (old_it != store.end() && *old_it == itemPtr) return { old_it, true }; // already equipped - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 + if (itemPtr.isEmpty() || itemPtr.getCellRef().getCount() == 0 || itemPtr.getContainerStore() != static_cast(&store)) { Log(Debug::Warning) << "Object" << std::get(item).toString() << " is not in inventory"; @@ -51,7 +51,7 @@ namespace MWLua if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) return { old_it, true }; // already equipped itemPtr = store.search(recordId); - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) + if (itemPtr.isEmpty() || itemPtr.getCellRef().getCount() == 0) { Log(Debug::Warning) << "There is no object with recordId='" << stringId << "' in inventory"; return { store.end(), false }; diff --git a/apps/openmw/mwlua/types/item.cpp b/apps/openmw/mwlua/types/item.cpp index b616075496..648229a5e5 100644 --- a/apps/openmw/mwlua/types/item.cpp +++ b/apps/openmw/mwlua/types/item.cpp @@ -15,7 +15,7 @@ namespace MWLua item["setEnchantmentCharge"] = [](const GObject& object, float charge) { object.ptr().getCellRef().setEnchantmentCharge(charge); }; item["isRestocking"] - = [](const Object& object) -> bool { return object.ptr().getRefData().getCount(false) < 0; }; + = [](const Object& object) -> bool { return object.ptr().getCellRef().getCount(false) < 0; }; addItemDataBindings(item, context); } diff --git a/apps/openmw/mwmechanics/aiactivate.cpp b/apps/openmw/mwmechanics/aiactivate.cpp index 31abda44c2..be4fe5e674 100644 --- a/apps/openmw/mwmechanics/aiactivate.cpp +++ b/apps/openmw/mwmechanics/aiactivate.cpp @@ -30,7 +30,7 @@ namespace MWMechanics // Stop if the target doesn't exist // Really we should be checking whether the target is currently registered with the MechanicsManager - if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) + if (target == MWWorld::Ptr() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()) return true; // Turn to target and move to it directly, without pathfinding. diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 5285fb31dd..674a660ff6 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -115,7 +115,7 @@ namespace MWMechanics if (target.isEmpty()) return true; - if (!target.getRefData().getCount() + if (!target.getCellRef().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently // registered with the MechanicsManager || target.getClass().getCreatureStats(target).isDead()) @@ -478,8 +478,7 @@ namespace MWMechanics MWWorld::Ptr AiCombat::getTarget() const { - if (mCachedTarget.isEmpty() || mCachedTarget.getRefData().isDeleted() - || !mCachedTarget.getRefData().isEnabled()) + if (mCachedTarget.isEmpty() || mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled()) { mCachedTarget = MWBase::Environment::get().getWorld()->searchPtrViaActorId(mTargetActorId); } diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index 5c07c18166..b4779dc900 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -99,7 +99,7 @@ namespace MWMechanics // Target is not here right now, wait for it to return // Really we should be checking whether the target is currently registered with the MechanicsManager - if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) + if (target == MWWorld::Ptr() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()) return false; actor.getClass().getCreatureStats(actor).setDrawState(DrawState::Nothing); diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 183c30bfb7..bdf96983ae 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -63,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const { if (!mCachedTarget.isEmpty()) { - if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) + if (mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled()) mCachedTarget = MWWorld::Ptr(); else return mCachedTarget; diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 699e96cd32..461db45133 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -38,7 +38,7 @@ namespace MWMechanics // Stop if the target doesn't exist // Really we should be checking whether the target is currently registered with the MechanicsManager - if (target == MWWorld::Ptr() || !target.getRefData().getCount() || !target.getRefData().isEnabled()) + if (target == MWWorld::Ptr() || !target.getCellRef().getCount() || !target.getRefData().isEnabled()) return true; if (isTargetMagicallyHidden(target) @@ -83,7 +83,7 @@ namespace MWMechanics { if (!mCachedTarget.isEmpty()) { - if (mCachedTarget.getRefData().isDeleted() || !mCachedTarget.getRefData().isEnabled()) + if (mCachedTarget.mRef->isDeleted() || !mCachedTarget.getRefData().isEnabled()) mCachedTarget = MWWorld::Ptr(); else return mCachedTarget; diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index f8e0046e19..aea3e36632 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -289,7 +289,7 @@ void MWMechanics::Alchemy::removeIngredients() { iter->getContainerStore()->remove(*iter, 1); - if (iter->getRefData().getCount() < 1) + if (iter->getCellRef().getCount() < 1) *iter = MWWorld::Ptr(); } @@ -369,7 +369,7 @@ int MWMechanics::Alchemy::countPotionsToBrew() const for (TIngredientsIterator iter(beginIngredients()); iter != endIngredients(); ++iter) if (!iter->isEmpty()) { - int count = iter->getRefData().getCount(); + int count = iter->getCellRef().getCount(); if ((count > 0 && count < toBrew) || toBrew < 0) toBrew = count; } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index d4fd63309f..59b1392dc9 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1022,7 +1022,7 @@ namespace MWMechanics if (stolenIt == mStolenItems.end()) continue; OwnerMap& owners = stolenIt->second; - int itemCount = it->getRefData().getCount(); + int itemCount = it->getCellRef().getCount(); for (OwnerMap::iterator ownerIt = owners.begin(); ownerIt != owners.end();) { int toRemove = std::min(itemCount, ownerIt->second); @@ -1034,7 +1034,7 @@ namespace MWMechanics ++ownerIt; } - int toMove = it->getRefData().getCount() - itemCount; + int toMove = it->getCellRef().getCount() - itemCount; containerStore.add(*it, toMove); store.remove(*it, toMove); @@ -1084,15 +1084,21 @@ namespace MWMechanics } } - if (!(item.getCellRef().getRefId() == MWWorld::ContainerStore::sGoldId)) + const bool isGold = item.getClass().isGold(item); + if (!isGold) { if (victim.isEmpty() - || (victim.getClass().isActor() && victim.getRefData().getCount() > 0 + || (victim.getClass().isActor() && victim.getCellRef().getCount() > 0 && !victim.getClass().getCreatureStats(victim).isDead())) mStolenItems[item.getCellRef().getRefId()][owner] += count; } if (alarm) - commitCrime(ptr, victim, OT_Theft, ownerCellRef->getFaction(), item.getClass().getValue(item) * count); + { + int value = count; + if (!isGold) + value *= item.getClass().getValue(item); + commitCrime(ptr, victim, OT_Theft, ownerCellRef->getFaction(), value); + } } bool MechanicsManager::commitCrime(const MWWorld::Ptr& player, const MWWorld::Ptr& victim, OffenseType type, diff --git a/apps/openmw/mwmechanics/recharge.cpp b/apps/openmw/mwmechanics/recharge.cpp index 9c42e4088c..6e16436bcc 100644 --- a/apps/openmw/mwmechanics/recharge.cpp +++ b/apps/openmw/mwmechanics/recharge.cpp @@ -38,7 +38,7 @@ namespace MWMechanics bool rechargeItem(const MWWorld::Ptr& item, const MWWorld::Ptr& gem) { - if (!gem.getRefData().getCount()) + if (!gem.getCellRef().getCount()) return false; MWWorld::Ptr player = MWMechanics::getPlayer(); @@ -87,7 +87,7 @@ namespace MWMechanics player.getClass().skillUsageSucceeded(player, ESM::Skill::Enchant, 0); gem.getContainerStore()->remove(gem, 1); - if (gem.getRefData().getCount() == 0) + if (gem.getCellRef().getCount() == 0) { std::string message = MWBase::Environment::get() .getESMStore() diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index a4541ab5a3..e31a1eb711 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -426,7 +426,7 @@ namespace MWRender const auto& weaponType = MWMechanics::getWeaponType(type); if (weaponType->mWeaponClass == ESM::WeaponType::Thrown) { - ammoCount = ammo->getRefData().getCount(); + ammoCount = ammo->getCellRef().getCount(); osg::Group* throwingWeaponNode = getBoneByName(weaponType->mAttachBone); if (throwingWeaponNode && throwingWeaponNode->getNumChildren()) ammoCount--; @@ -439,7 +439,7 @@ namespace MWRender if (ammo == inv.end()) return; - ammoCount = ammo->getRefData().getCount(); + ammoCount = ammo->getCellRef().getCount(); bool arrowAttached = isArrowAttached(); if (arrowAttached) ammoCount--; @@ -514,7 +514,7 @@ namespace MWRender ItemLightMap::iterator iter = mItemLights.find(item); if (iter != mItemLights.end()) { - if (!item.getRefData().getCount()) + if (!item.getCellRef().getCount()) { removeHiddenItemLight(item); } diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 4a0d302cd8..3822a247bd 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -109,7 +109,7 @@ namespace MWScript return; if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") - item = ESM::RefId::stringRefId("gold_001"); + item = MWWorld::ContainerStore::sGoldId; // Check if "item" can be placed in a container MWWorld::ManualRef manualRef(*MWBase::Environment::get().getESMStore(), item, 1); @@ -195,7 +195,7 @@ namespace MWScript runtime.pop(); if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") - item = ESM::RefId::stringRefId("gold_001"); + item = MWWorld::ContainerStore::sGoldId; MWWorld::ContainerStore& store = ptr.getClass().getContainerStore(ptr); @@ -231,7 +231,7 @@ namespace MWScript return; if (item == "gold_005" || item == "gold_010" || item == "gold_025" || item == "gold_100") - item = ESM::RefId::stringRefId("gold_001"); + item = MWWorld::ContainerStore::sGoldId; // Explicit calls to non-unique actors affect the base record if (!R::implicit && ptr.getClass().isActor() @@ -460,7 +460,7 @@ namespace MWScript it != invStore.cend(); ++it) { if (it->getCellRef().getSoul() == name) - count += it->getRefData().getCount(); + count += it->getCellRef().getCount(); } runtime.push(count); } diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index b8dc047737..9d75334f00 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -715,7 +715,7 @@ namespace MWScript MWWorld::ConstContainerStoreIterator it = store.getSlot(slot); if (it != store.end() && it->getCellRef().getRefId() == item) { - numNotEquipped -= it->getRefData().getCount(); + numNotEquipped -= it->getCellRef().getCount(); } } @@ -724,7 +724,7 @@ namespace MWScript MWWorld::ContainerStoreIterator it = store.getSlot(slot); if (it != store.end() && it->getCellRef().getRefId() == item) { - int numToRemove = std::min(amount - numNotEquipped, it->getRefData().getCount()); + int numToRemove = std::min(amount - numNotEquipped, it->getCellRef().getCount()); store.unequipItemQuantity(*it, numToRemove); numNotEquipped += numToRemove; } @@ -1418,7 +1418,7 @@ namespace MWScript if (ptr.getRefData().isDeletedByContentFile()) msg << "[Deleted by content file]" << std::endl; - if (!ptr.getRefData().getCount()) + if (!ptr.getCellRef().getCount()) msg << "[Deleted]" << std::endl; msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; diff --git a/apps/openmw/mwworld/actionharvest.cpp b/apps/openmw/mwworld/actionharvest.cpp index 708260d395..30f316c2db 100644 --- a/apps/openmw/mwworld/actionharvest.cpp +++ b/apps/openmw/mwworld/actionharvest.cpp @@ -37,7 +37,7 @@ namespace MWWorld if (!it->getClass().showsInInventory(*it)) continue; - int itemCount = it->getRefData().getCount(); + int itemCount = it->getCellRef().getCount(); // Note: it is important to check for crime before move an item from container. Otherwise owner check will // not work for a last item in the container - empty harvested containers are considered as "allowed to // use". diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index 83d6e729c9..1f37499e8f 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -30,10 +30,12 @@ namespace MWWorld } } - MWBase::Environment::get().getMechanicsManager()->itemTaken( - actor, getTarget(), MWWorld::Ptr(), getTarget().getRefData().getCount()); - MWWorld::Ptr newitem - = *actor.getClass().getContainerStore(actor).add(getTarget(), getTarget().getRefData().getCount()); + int count = getTarget().getCellRef().getCount(); + if (getTarget().getClass().isGold(getTarget())) + count *= getTarget().getClass().getValue(getTarget()); + + MWBase::Environment::get().getMechanicsManager()->itemTaken(actor, getTarget(), MWWorld::Ptr(), count); + MWWorld::Ptr newitem = *actor.getClass().getContainerStore(actor).add(getTarget(), count); MWBase::Environment::get().getWorld()->deleteObject(getTarget()); setTarget(newitem); } diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index b7a3713eba..fda7157010 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -377,17 +377,19 @@ namespace MWWorld } } - void CellRef::setGoldValue(int value) + void CellRef::setCount(int value) { - if (value != getGoldValue()) + if (value != getCount(false)) { mChanged = true; std::visit(ESM::VisitOverload{ - [&](ESM4::Reference& /*ref*/) {}, - [&](ESM4::ActorCharacter&) {}, - [&](ESM::CellRef& ref) { ref.mGoldValue = value; }, + [&](ESM4::Reference& ref) { ref.mCount = value; }, + [&](ESM4::ActorCharacter& ref) { ref.mCount = value; }, + [&](ESM::CellRef& ref) { ref.mCount = value; }, }, mCellRef.mVariant); + if (value == 0) + MWBase::Environment::get().getWorld()->removeRefScript(this); } } diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 92bc355db4..4dcac4def5 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -231,18 +231,20 @@ namespace MWWorld } void setTrap(const ESM::RefId& trap); - // This is 5 for Gold_005 references, 100 for Gold_100 and so on. - int getGoldValue() const + int getCount(bool absolute = true) const { struct Visitor { - int operator()(const ESM::CellRef& ref) { return ref.mGoldValue; } - int operator()(const ESM4::Reference& /*ref*/) { return 0; } - int operator()(const ESM4::ActorCharacter&) { throw std::logic_error("Not applicable"); } + int operator()(const ESM::CellRef& ref) { return ref.mCount; } + int operator()(const ESM4::Reference& ref) { return ref.mCount; } + int operator()(const ESM4::ActorCharacter& ref) { return ref.mCount; } }; - return std::visit(Visitor(), mCellRef.mVariant); + int count = std::visit(Visitor(), mCellRef.mVariant); + if (absolute) + return std::abs(count); + return count; } - void setGoldValue(int value); + void setCount(int value); // Write the content of this CellRef into the given ObjectState void writeState(ESM::ObjectState& state) const; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 930f26c1cc..fd24bb7271 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -152,7 +152,7 @@ namespace if (toIgnore.find(&*iter) != toIgnore.end()) continue; - if (actor.getClass().getCreatureStats(actor).matchesActorId(actorId) && actor.getRefData().getCount() > 0) + if (actor.getClass().getCreatureStats(actor).matchesActorId(actorId) && actor.getCellRef().getCount() > 0) return actor; } @@ -175,7 +175,7 @@ namespace // Reference that came from a content file and has not been changed -> ignore continue; } - if (liveCellRef.mData.getCount() == 0 && !liveCellRef.mRef.hasContentFile()) + if (liveCellRef.mRef.getCount() == 0 && !liveCellRef.mRef.hasContentFile()) { // Deleted reference that did not come from a content file -> ignore continue; @@ -201,8 +201,8 @@ namespace { for (auto& item : state.mInventory.mItems) { - if (item.mCount > 0 && baseItem.mItem == item.mRef.mRefID) - item.mCount = -item.mCount; + if (item.mRef.mCount > 0 && baseItem.mItem == item.mRef.mRefID) + item.mRef.mCount = -item.mRef.mCount; } } } @@ -298,9 +298,9 @@ namespace // instance is invalid. But non-storable item are always stored in saves together with their original cell. // If a non-storable item references a content file, but is not found in this content file, // we should drop it. Likewise if this stack is empty. - if (!MWWorld::ContainerStore::isStorableType() || !state.mCount) + if (!MWWorld::ContainerStore::isStorableType() || !state.mRef.mCount) { - if (state.mCount) + if (state.mRef.mCount) Log(Debug::Warning) << "Warning: Dropping reference to " << state.mRef.mRefID << " (invalid content file link)"; return; @@ -676,7 +676,7 @@ namespace MWWorld MWWorld::Ptr actor(base, this); if (!actor.getClass().isActor()) continue; - if (actor.getClass().getCreatureStats(actor).matchesActorId(id) && actor.getRefData().getCount() > 0) + if (actor.getClass().getCreatureStats(actor).matchesActorId(id) && actor.getCellRef().getCount() > 0) return actor; } @@ -1042,7 +1042,7 @@ namespace MWWorld for (const auto& [base, store] : mMovedToAnotherCell) { ESM::RefNum refNum = base->mRef.getRefNum(); - if (base->mData.isDeleted() && !refNum.hasContentFile()) + if (base->isDeleted() && !refNum.hasContentFile()) continue; // filtered out in writeReferenceCollection ESM::RefId movedTo = store->getCell()->getId(); @@ -1168,20 +1168,18 @@ namespace MWWorld { if (mState == State_Loaded) { - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& creature : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + Ptr ptr = getCurrentPtr(&creature); + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } } - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& npc : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + Ptr ptr = getCurrentPtr(&npc); + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { MWBase::Environment::get().getMechanicsManager()->restoreDynamicStats(ptr, hours, true); } @@ -1196,29 +1194,26 @@ namespace MWWorld if (mState == State_Loaded) { - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& creature : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + Ptr ptr = getCurrentPtr(&creature); + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& npc : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + Ptr ptr = getCurrentPtr(&npc); + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); } } - for (CellRefList::List::iterator it(get().mList.begin()); - it != get().mList.end(); ++it) + for (MWWorld::LiveCellRef& container : get().mList) { - Ptr ptr = getCurrentPtr(&*it); - if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getRefData().getCount() > 0 + Ptr ptr = getCurrentPtr(&container); + if (!ptr.isEmpty() && ptr.getRefData().getCustomData() != nullptr && ptr.getCellRef().getCount() > 0 && ptr.getClass().getContainerStore(ptr).isResolved()) { ptr.getClass().getContainerStore(ptr).rechargeItems(duration); @@ -1262,7 +1257,7 @@ namespace MWWorld } forEachType([](Ptr ptr) { // no need to clearCorpse, handled as part of get() - if (!ptr.getRefData().isDeleted()) + if (!ptr.mRef->isDeleted()) ptr.getClass().respawn(ptr); return true; }); @@ -1290,7 +1285,7 @@ namespace MWWorld for (auto& item : list) { Ptr ptr = getCurrentPtr(&item); - if (!ptr.isEmpty() && ptr.getRefData().getCount() > 0) + if (!ptr.isEmpty() && ptr.getCellRef().getCount() > 0) { checkItem(ptr); } diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index c80cf56d6a..0c6527ce22 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -118,7 +118,7 @@ namespace MWWorld /// scripting compatibility, and the fact that objects may be "un-deleted" in the original game). static bool isAccessible(const MWWorld::RefData& refdata, const MWWorld::CellRef& cref) { - return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || refdata.getCount() > 0); + return !refdata.isDeletedByContentFile() && (cref.hasContentFile() || cref.getCount() > 0); } /// Moves object from this cell to the given cell. diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 5fbda6d570..d5062d6add 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -373,7 +373,7 @@ namespace MWWorld { Ptr newPtr = copyToCellImpl(ptr, cell); newPtr.getCellRef().unsetRefNum(); // This RefNum is only valid within the original cell of the reference - newPtr.getRefData().setCount(count); + newPtr.getCellRef().setCount(count); newPtr.getRefData().setLuaScripts(nullptr); MWBase::Environment::get().getWorldModel()->registerPtr(newPtr); return newPtr; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index b55a524a48..d30ea21494 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -51,7 +51,7 @@ namespace for (const MWWorld::LiveCellRef& liveCellRef : cellRefList.mList) { - if (const int count = liveCellRef.mData.getCount(); count > 0) + if (const int count = liveCellRef.mRef.getCount(); count > 0) sum += count * liveCellRef.mBase->mData.mWeight; } @@ -65,7 +65,7 @@ namespace for (MWWorld::LiveCellRef& liveCellRef : list.mList) { - if ((liveCellRef.mBase->mId == id) && liveCellRef.mData.getCount()) + if ((liveCellRef.mBase->mId == id) && liveCellRef.mRef.getCount()) { MWWorld::Ptr ptr(&liveCellRef, nullptr); ptr.setContainerStore(store); @@ -132,7 +132,7 @@ void MWWorld::ContainerStore::storeStates( { for (const LiveCellRef& liveCellRef : collection.mList) { - if (liveCellRef.mData.getCount() == 0) + if (liveCellRef.mRef.getCount() == 0) continue; ESM::ObjectState state; storeState(liveCellRef, state); @@ -192,7 +192,7 @@ int MWWorld::ContainerStore::count(const ESM::RefId& id) const int total = 0; for (const auto&& iter : *this) if (iter.getCellRef().getRefId() == id) - total += iter.getRefData().getCount(); + total += iter.getCellRef().getCount(); return total; } @@ -219,9 +219,9 @@ void MWWorld::ContainerStore::setContListener(MWWorld::ContainerStoreListener* l MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr& ptr, int count) { resolve(); - if (ptr.getRefData().getCount() <= count) + if (ptr.getCellRef().getCount() <= count) return end(); - MWWorld::ContainerStoreIterator it = addNewStack(ptr, subtractItems(ptr.getRefData().getCount(false), count)); + MWWorld::ContainerStoreIterator it = addNewStack(ptr, subtractItems(ptr.getCellRef().getCount(false), count)); MWWorld::Ptr newPtr = *it; newPtr.getCellRef().unsetRefNum(); @@ -232,7 +232,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::unstack(const Ptr& ptr, if (!script.empty()) MWBase::Environment::get().getWorld()->getLocalScripts().add(script, *it); - remove(ptr, ptr.getRefData().getCount() - count); + remove(ptr, ptr.getCellRef().getCount() - count); return it; } @@ -257,9 +257,9 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::restack(const MWWorld:: { if (stacks(*iter, item)) { - iter->getRefData().setCount( - addItems(iter->getRefData().getCount(false), item.getRefData().getCount(false))); - item.getRefData().setCount(0); + iter->getCellRef().setCount( + addItems(iter->getCellRef().getCount(false), item.getCellRef().getCount(false))); + item.getCellRef().setCount(0); retval = iter; break; } @@ -385,22 +385,24 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp(const Ptr& ptr, // gold needs special handling: when it is inserted into a container, the base object automatically becomes Gold_001 // this ensures that gold piles of different sizes stack with each other (also, several scripts rely on Gold_001 for // detecting player gold) + // Note that adding 1 gold_100 is equivalent to adding 1 gold_001. Morrowind.exe resolves gold in leveled lists to + // gold_001 and TESCS disallows adding gold other than gold_001 to inventories. If a content file defines a + // container containing gold_100 anyway, the item is not turned to gold_001 until the player puts it down in the + // world and picks it up again. We just turn it into gold_001 here and ignore that oddity. if (ptr.getClass().isGold(ptr)) { - int realCount = count * ptr.getClass().getValue(ptr); - for (MWWorld::ContainerStoreIterator iter(begin(type)); iter != end(); ++iter) { if (iter->getCellRef().getRefId() == MWWorld::ContainerStore::sGoldId) { - iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), realCount)); + iter->getCellRef().setCount(addItems(iter->getCellRef().getCount(false), count)); flagAsModified(); return iter; } } - MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, realCount); - return addNewStack(ref.getPtr(), realCount); + MWWorld::ManualRef ref(esmStore, MWWorld::ContainerStore::sGoldId, count); + return addNewStack(ref.getPtr(), count); } // determine whether to stack or not @@ -414,7 +416,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addImp(const Ptr& ptr, if (stacks(*iter, ptr)) { // stack - iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count)); + iter->getCellRef().setCount(addItems(iter->getCellRef().getCount(false), count)); flagAsModified(); return iter; @@ -480,7 +482,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::addNewStack(const Const break; } - it->getRefData().setCount(count); + it->getCellRef().setCount(count); flagAsModified(); return it; @@ -562,7 +564,7 @@ int MWWorld::ContainerStore::remove(const Ptr& item, int count, bool equipReplac resolve(); int toRemove = count; - RefData& itemRef = item.getRefData(); + CellRef& itemRef = item.getCellRef(); if (itemRef.getCount() <= toRemove) { @@ -667,7 +669,7 @@ void MWWorld::ContainerStore::addInitialItemImp( void MWWorld::ContainerStore::clear() { for (auto&& iter : *this) - iter.getRefData().setCount(0); + iter.getCellRef().setCount(0); flagAsModified(); mModified = true; @@ -690,7 +692,7 @@ void MWWorld::ContainerStore::resolve() if (!mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); Misc::Rng::Generator prng{ mSeed }; fill(container.get()->mBase->mInventory, ESM::RefId(), prng); addScripts(*this, container.mCell); @@ -712,7 +714,7 @@ MWWorld::ResolutionHandle MWWorld::ContainerStore::resolveTemporarily() if (!mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); Misc::Rng::Generator prng{ mSeed }; fill(container.get()->mBase->mInventory, ESM::RefId(), prng); addScripts(*this, container.mCell); @@ -729,7 +731,7 @@ void MWWorld::ContainerStore::unresolve() if (mResolved && !container.isEmpty() && container.getType() == ESM::REC_CONT) { for (const auto&& ptr : *this) - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); fillNonRandom(container.get()->mBase->mInventory, ESM::RefId(), mSeed); addScripts(*this, container.mCell); mResolved = false; @@ -1332,7 +1334,7 @@ MWWorld::ContainerStoreIteratorBase& MWWorld::ContainerStoreIteratorBas { if (incIterator()) nextType(); - } while (mType != -1 && !(**this).getRefData().getCount()); + } while (mType != -1 && !(**this).getCellRef().getCount()); return *this; } @@ -1384,7 +1386,7 @@ MWWorld::ContainerStoreIteratorBase::ContainerStoreIteratorBase(int mas { nextType(); - if (mType == -1 || (**this).getRefData().getCount()) + if (mType == -1 || (**this).getCellRef().getCount()) return; ++*this; diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 095f5d3cc1..f48f4e6e31 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -79,11 +79,11 @@ void MWWorld::InventoryStore::readEquipmentState( slot = allowedSlots.first.front(); // unstack if required - if (!allowedSlots.second && iter->getRefData().getCount() > 1) + if (!allowedSlots.second && iter->getCellRef().getCount() > 1) { - int count = iter->getRefData().getCount(false); + int count = iter->getCellRef().getCount(false); MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, count > 0 ? 1 : -1); - iter->getRefData().setCount(subtractItems(count, 1)); + iter->getCellRef().setCount(subtractItems(count, 1)); mSlots[slot] = newIter; } else @@ -171,7 +171,7 @@ void MWWorld::InventoryStore::equip(int slot, const ContainerStoreIterator& iter // unstack item pointed to by iterator if required if (iterator != end() && !slots_.second - && iterator->getRefData().getCount() > 1) // if slots.second is true, item can stay stacked when equipped + && iterator->getCellRef().getCount() > 1) // if slots.second is true, item can stay stacked when equipped { unstack(*iterator); } @@ -355,7 +355,7 @@ void MWWorld::InventoryStore::autoEquipWeapon(TSlots& slots_) { if (!itemsSlots.second) { - if (weapon->getRefData().getCount() > 1) + if (weapon->getCellRef().getCount() > 1) { unstack(*weapon); } @@ -478,7 +478,7 @@ void MWWorld::InventoryStore::autoEquipArmor(TSlots& slots_) if (!itemsSlots.second) // if itemsSlots.second is true, item can stay stacked when equipped { // unstack item pointed to by iterator if required - if (iter->getRefData().getCount() > 1) + if (iter->getCellRef().getCount() > 1) { unstack(*iter); } @@ -590,7 +590,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, bool equipReplac int retCount = ContainerStore::remove(item, count, equipReplacement, resolve); bool wasEquipped = false; - if (!item.getRefData().getCount()) + if (!item.getCellRef().getCount()) { for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) { @@ -618,7 +618,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, bool equipReplac autoEquip(); } - if (item.getRefData().getCount() == 0 && mSelectedEnchantItem != end() && *mSelectedEnchantItem == item) + if (item.getCellRef().getCount() == 0 && mSelectedEnchantItem != end() && *mSelectedEnchantItem == item) { mSelectedEnchantItem = end(); } @@ -643,7 +643,7 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipSlot(int slot, b // empty this slot mSlots[slot] = end(); - if (it->getRefData().getCount()) + if (it->getCellRef().getCount()) { retval = restack(*it); @@ -690,10 +690,10 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con throw std::runtime_error("attempt to unequip an item that is not currently equipped"); if (count <= 0) throw std::runtime_error("attempt to unequip nothing (count <= 0)"); - if (count > item.getRefData().getCount()) + if (count > item.getCellRef().getCount()) throw std::runtime_error("attempt to unequip more items than equipped"); - if (count == item.getRefData().getCount()) + if (count == item.getCellRef().getCount()) return unequipItem(item); // Move items to an existing stack if possible, otherwise split count items out into a new stack. @@ -702,13 +702,13 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::unequipItemQuantity(con { if (stacks(*iter, item) && !isEquipped(*iter)) { - iter->getRefData().setCount(addItems(iter->getRefData().getCount(false), count)); - item.getRefData().setCount(subtractItems(item.getRefData().getCount(false), count)); + iter->getCellRef().setCount(addItems(iter->getCellRef().getCount(false), count)); + item.getCellRef().setCount(subtractItems(item.getCellRef().getCount(false), count)); return iter; } } - return unstack(item, item.getRefData().getCount() - count); + return unstack(item, item.getCellRef().getCount() - count); } MWWorld::InventoryStoreListener* MWWorld::InventoryStore::getInvListener() const diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index d4e2ac40c0..61b838bbf0 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -106,6 +106,11 @@ unsigned int MWWorld::LiveCellRefBase::getType() const return mClass->getType(); } +bool MWWorld::LiveCellRefBase::isDeleted() const +{ + return mData.isDeletedByContentFile() || mRef.getCount(false) == 0; +} + namespace MWWorld { std::string makeDynamicCastErrorMessage(const LiveCellRefBase* value, std::string_view recordType) diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 1e4d1441f5..c95dd589b2 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -59,6 +59,9 @@ namespace MWWorld template static LiveCellRef* dynamicCast(LiveCellRefBase* value); + /// Returns true if the object was either deleted by the content file or by gameplay. + bool isDeleted() const; + protected: void loadImp(const ESM::ObjectState& state); ///< Load state into a LiveCellRef, that has already been initialised with base and diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index 8d9a282791..955e1a91f8 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -24,7 +24,7 @@ namespace bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getRefData().isDeleted()) + if (ptr.mRef->isDeleted()) return true; const ESM::RefId& script = ptr.getClass().getScript(ptr); @@ -152,10 +152,10 @@ void MWWorld::LocalScripts::clearCell(CellStore* cell) } } -void MWWorld::LocalScripts::remove(RefData* ref) +void MWWorld::LocalScripts::remove(const MWWorld::CellRef* ref) { for (auto iter = mScripts.begin(); iter != mScripts.end(); ++iter) - if (&(iter->second.getRefData()) == ref) + if (&(iter->second.getCellRef()) == ref) { if (iter == mIter) ++mIter; diff --git a/apps/openmw/mwworld/localscripts.hpp b/apps/openmw/mwworld/localscripts.hpp index 4bd2abeb84..09a913e655 100644 --- a/apps/openmw/mwworld/localscripts.hpp +++ b/apps/openmw/mwworld/localscripts.hpp @@ -41,7 +41,7 @@ namespace MWWorld void clearCell(CellStore* cell); ///< Remove all scripts belonging to \a cell. - void remove(RefData* ref); + void remove(const MWWorld::CellRef* ref); void remove(const Ptr& ptr); ///< Remove script for given reference (ignored if reference does not have a script listed). diff --git a/apps/openmw/mwworld/manualref.cpp b/apps/openmw/mwworld/manualref.cpp index e9fd02e6f5..c6c0444754 100644 --- a/apps/openmw/mwworld/manualref.cpp +++ b/apps/openmw/mwworld/manualref.cpp @@ -101,5 +101,5 @@ MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const ESM::RefId& throw std::logic_error("failed to create manual cell ref for " + name.toDebugString() + " (unknown type)"); } - mPtr.getRefData().setCount(count); + mPtr.getCellRef().setCount(count); } diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 3cb08be5fa..0584a9fe94 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -434,7 +434,7 @@ namespace MWWorld MWWorld::Ptr caster = magicBoltState.getCaster(); if (!caster.isEmpty() && caster.getClass().isActor()) { - if (caster.getRefData().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) + if (caster.getCellRef().getCount() <= 0 || caster.getClass().getCreatureStats(caster).isDead()) { cleanupMagicBolt(magicBoltState); continue; diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 1421b51a2a..25715a26f1 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -10,7 +10,7 @@ namespace MWWorld std::string Ptr::toString() const { std::string res = "object"; - if (getRefData().isDeleted()) + if (mRef->isDeleted()) res = "deleted object"; res.append(getCellRef().getRefNum().toString()); res.append(" ("); diff --git a/apps/openmw/mwworld/refdata.cpp b/apps/openmw/mwworld/refdata.cpp index 87c085ebd4..f7ba76da21 100644 --- a/apps/openmw/mwworld/refdata.cpp +++ b/apps/openmw/mwworld/refdata.cpp @@ -37,7 +37,6 @@ namespace MWWorld mBaseNode = refData.mBaseNode; mLocals = refData.mLocals; mEnabled = refData.mEnabled; - mCount = refData.mCount; mPosition = refData.mPosition; mChanged = refData.mChanged; mDeletedByContentFile = refData.mDeletedByContentFile; @@ -62,7 +61,6 @@ namespace MWWorld , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mCount(1) , mCustomData(nullptr) , mChanged(false) , mFlags(0) @@ -79,7 +77,6 @@ namespace MWWorld , mDeletedByContentFile(false) , mEnabled(true) , mPhysicsPostponed(false) - , mCount(1) , mPosition(cellRef.mPos) , mCustomData(nullptr) , mChanged(false) @@ -92,7 +89,6 @@ namespace MWWorld , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mCount(ref.mCount) , mPosition(ref.mPos) , mCustomData(nullptr) , mChanged(false) @@ -105,7 +101,6 @@ namespace MWWorld , mDeletedByContentFile(ref.mFlags & ESM4::Rec_Deleted) , mEnabled(!(ref.mFlags & ESM4::Rec_Disabled)) , mPhysicsPostponed(false) - , mCount(mDeletedByContentFile ? 0 : 1) , mPosition(ref.mPos) , mCustomData(nullptr) , mChanged(false) @@ -118,7 +113,6 @@ namespace MWWorld , mDeletedByContentFile(deletedByContentFile) , mEnabled(objectState.mEnabled != 0) , mPhysicsPostponed(false) - , mCount(objectState.mCount) , mPosition(objectState.mPosition) , mAnimationState(objectState.mAnimationState) , mCustomData(nullptr) @@ -153,7 +147,6 @@ namespace MWWorld objectState.mHasLocals = mLocals.write(objectState.mLocals, scriptId); objectState.mEnabled = mEnabled; - objectState.mCount = mCount; objectState.mPosition = mPosition; objectState.mFlags = mFlags; @@ -205,39 +198,17 @@ namespace MWWorld return mBaseNode; } - int RefData::getCount(bool absolute) const - { - if (absolute) - return std::abs(mCount); - return mCount; - } - void RefData::setLocals(const ESM::Script& script) { if (mLocals.configure(script) && !mLocals.isEmpty()) mChanged = true; } - void RefData::setCount(int count) - { - if (count == 0) - MWBase::Environment::get().getWorld()->removeRefScript(this); - - mChanged = true; - - mCount = count; - } - void RefData::setDeletedByContentFile(bool deleted) { mDeletedByContentFile = deleted; } - bool RefData::isDeleted() const - { - return mDeletedByContentFile || mCount == 0; - } - bool RefData::isDeletedByContentFile() const { return mDeletedByContentFile; diff --git a/apps/openmw/mwworld/refdata.hpp b/apps/openmw/mwworld/refdata.hpp index a6a45623f5..ae80a0d64e 100644 --- a/apps/openmw/mwworld/refdata.hpp +++ b/apps/openmw/mwworld/refdata.hpp @@ -58,9 +58,6 @@ namespace MWWorld bool mPhysicsPostponed : 1; private: - /// 0: deleted - int mCount; - ESM::Position mPosition; ESM::AnimationState mAnimationState; @@ -110,26 +107,15 @@ namespace MWWorld /// Set base node (can be a null pointer). void setBaseNode(osg::ref_ptr base); - int getCount(bool absolute = true) const; - void setLocals(const ESM::Script& script); MWLua::LocalScripts* getLuaScripts() { return mLuaScripts.get(); } void setLuaScripts(std::shared_ptr&&); - void setCount(int count); - ///< Set object count (an object pile is a simple object with a count >1). - /// - /// \warning Do not call setCount() to add or remove objects from a - /// container or an actor's inventory. Call ContainerStore::add() or - /// ContainerStore::remove() instead. - /// This flag is only used for content stack loading and will not be stored in the savegame. /// If the object was deleted by gameplay, then use setCount(0) instead. void setDeletedByContentFile(bool deleted); - /// Returns true if the object was either deleted by the content file or by gameplay. - bool isDeleted() const; /// Returns true if the object was deleted by a content file. bool isDeletedByContentFile() const; diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 036287d3a9..d8875b513e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -226,7 +226,7 @@ namespace { for (MWWorld::Ptr& ptr : mToInsert) { - if (!ptr.getRefData().isDeleted() && ptr.getRefData().isEnabled()) + if (!ptr.mRef->isDeleted() && ptr.getRefData().isEnabled()) { try { @@ -648,7 +648,7 @@ namespace MWWorld if (ptr.mRef->mData.mPhysicsPostponed) { ptr.mRef->mData.mPhysicsPostponed = false; - if (ptr.mRef->mData.isEnabled() && ptr.mRef->mData.getCount() > 0) + if (ptr.mRef->mData.isEnabled() && ptr.mRef->mRef.getCount() > 0) { std::string model = getModel(ptr); if (!model.empty()) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 07334396b7..1b6af6038e 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -685,7 +685,7 @@ namespace MWWorld return mStore.get().find("sDefaultCellname")->mValue.getString(); } - void World::removeRefScript(MWWorld::RefData* ref) + void World::removeRefScript(const MWWorld::CellRef* ref) { mLocalScripts.remove(ref); } @@ -827,7 +827,7 @@ namespace MWWorld reference.getRefData().enable(); if (mWorldScene->getActiveCells().find(reference.getCell()) != mWorldScene->getActiveCells().end() - && reference.getRefData().getCount()) + && reference.getCellRef().getCount()) mWorldScene->addObjectToScene(reference); if (reference.getCellRef().getRefNum().hasContentFile()) @@ -879,7 +879,7 @@ namespace MWWorld } if (mWorldScene->getActiveCells().find(reference.getCell()) != mWorldScene->getActiveCells().end() - && reference.getRefData().getCount()) + && reference.getCellRef().getCount()) { mWorldScene->removeObjectFromScene(reference); mWorldScene->addPostponedPhysicsObjects(); @@ -1039,12 +1039,12 @@ namespace MWWorld void World::deleteObject(const Ptr& ptr) { - if (!ptr.getRefData().isDeleted() && ptr.getContainerStore() == nullptr) + if (!ptr.mRef->isDeleted() && ptr.getContainerStore() == nullptr) { if (ptr == getPlayerPtr()) throw std::runtime_error("can not delete player object"); - ptr.getRefData().setCount(0); + ptr.getCellRef().setCount(0); if (ptr.isInCell() && mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end() @@ -1061,9 +1061,9 @@ namespace MWWorld { if (!ptr.getCellRef().hasContentFile()) return; - if (ptr.getRefData().isDeleted()) + if (ptr.mRef->isDeleted()) { - ptr.getRefData().setCount(1); + ptr.getCellRef().setCount(1); if (mWorldScene->getActiveCells().find(ptr.getCell()) != mWorldScene->getActiveCells().end() && ptr.getRefData().isEnabled()) { @@ -1392,7 +1392,7 @@ namespace MWWorld MWWorld::Ptr World::placeObject(const MWWorld::ConstPtr& ptr, MWWorld::CellStore* cell, const ESM::Position& pos) { - return copyObjectToCell(ptr, cell, pos, ptr.getRefData().getCount(), false); + return copyObjectToCell(ptr, cell, pos, ptr.getCellRef().getCount(), false); } MWWorld::Ptr World::safePlaceObject(const ConstPtr& ptr, const ConstPtr& referenceObject, @@ -1443,7 +1443,7 @@ namespace MWWorld ipos.rot[1] = 0; } - MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getRefData().getCount(), false); + MWWorld::Ptr placed = copyObjectToCell(ptr, referenceCell, ipos, ptr.getCellRef().getCount(), false); adjustPosition(placed, true); // snap to ground return placed; } @@ -1893,7 +1893,7 @@ namespace MWWorld { MWWorld::LiveCellRef& ref = *static_cast*>(ptr.getBase()); - if (!ref.mData.isEnabled() || ref.mData.isDeleted()) + if (!ref.mData.isEnabled() || ref.isDeleted()) return true; if (ref.mRef.getTeleport()) @@ -2541,7 +2541,7 @@ namespace MWWorld bool operator()(const MWWorld::Ptr& ptr) { - if (ptr.getRefData().isDeleted()) + if (ptr.mRef->isDeleted()) return true; // vanilla Morrowind does not allow to sell items from containers with zero capacity @@ -3337,7 +3337,7 @@ namespace MWWorld >= mSquaredDist) return true; - if (!ptr.getRefData().isEnabled() || ptr.getRefData().isDeleted()) + if (!ptr.getRefData().isEnabled() || ptr.mRef->isDeleted()) return true; // Consider references inside containers as well (except if we are looking for a Creature, they cannot be in diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 4b9a0ccb98..b5d56753b0 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -275,7 +275,7 @@ namespace MWWorld std::string_view getCellName(const MWWorld::Cell& cell) const override; std::string_view getCellName(const ESM::Cell* cell) const override; - void removeRefScript(MWWorld::RefData* ref) override; + void removeRefScript(const MWWorld::CellRef* ref) override; //< Remove the script attached to ref from mLocalScripts Ptr getPtr(const ESM::RefId& name, bool activeOnly) override; diff --git a/apps/openmw_test_suite/esm3/testsaveload.cpp b/apps/openmw_test_suite/esm3/testsaveload.cpp index 96796defd4..ff68d0d4f1 100644 --- a/apps/openmw_test_suite/esm3/testsaveload.cpp +++ b/apps/openmw_test_suite/esm3/testsaveload.cpp @@ -292,7 +292,7 @@ namespace ESM record.mFactionRank = std::numeric_limits::max(); record.mChargeInt = std::numeric_limits::max(); record.mEnchantmentCharge = std::numeric_limits::max(); - record.mGoldValue = std::numeric_limits::max(); + record.mCount = std::numeric_limits::max(); record.mTeleport = true; generateArray(record.mDoorDest.pos); generateArray(record.mDoorDest.rot); @@ -317,7 +317,7 @@ namespace ESM EXPECT_EQ(record.mFactionRank, result.mFactionRank); EXPECT_EQ(record.mChargeInt, result.mChargeInt); EXPECT_EQ(record.mEnchantmentCharge, result.mEnchantmentCharge); - EXPECT_EQ(record.mGoldValue, result.mGoldValue); + EXPECT_EQ(record.mCount, result.mCount); EXPECT_EQ(record.mTeleport, result.mTeleport); EXPECT_EQ(record.mDoorDest, result.mDoorDest); EXPECT_EQ(record.mDestCell, result.mDestCell); diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index 42edec8f1f..93a2ece669 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -107,7 +107,7 @@ namespace ESM getHTOrSkip(cellRef.mChargeInt); break; case fourCC("NAM9"): - getHTOrSkip(cellRef.mGoldValue); + getHTOrSkip(cellRef.mCount); break; case fourCC("DODT"): if constexpr (load) @@ -219,8 +219,8 @@ namespace ESM if (mChargeInt != -1) esm.writeHNT("INTV", mChargeInt); - if (mGoldValue > 1) - esm.writeHNT("NAM9", mGoldValue); + if (mCount != 1) + esm.writeHNT("NAM9", mCount); if (!inInventory && mTeleport) { @@ -259,7 +259,7 @@ namespace ESM mChargeInt = -1; mChargeIntRemainder = 0.0f; mEnchantmentCharge = -1; - mGoldValue = 1; + mCount = 1; mDestCell.clear(); mLockLevel = 0; mIsLocked = false; diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 55e5afcbf5..84b6ae1d18 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -65,8 +65,7 @@ namespace ESM // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float mEnchantmentCharge; - // This is 5 for Gold_005 references, 100 for Gold_100 and so on. - int32_t mGoldValue; + int32_t mCount; // For doors - true if this door teleports to somewhere else, false // if it should open through animation. diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index b460c15247..1489926fbd 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -24,7 +24,8 @@ namespace ESM inline constexpr FormatVersion MaxNameIsRefIdOnlyFormatVersion = 25; inline constexpr FormatVersion MaxUseEsmCellIdFormatVersion = 26; inline constexpr FormatVersion MaxActiveSpellSlotIndexFormatVersion = 27; - inline constexpr FormatVersion CurrentSaveGameFormatVersion = 30; + inline constexpr FormatVersion MaxOldCountFormatVersion = 30; + inline constexpr FormatVersion CurrentSaveGameFormatVersion = 31; inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index 4175159ad5..1947be23e9 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -24,7 +24,7 @@ namespace ESM state.mRef.loadId(esm, true); state.load(esm); - if (state.mCount == 0) + if (state.mRef.mCount == 0) continue; mItems.push_back(state); @@ -43,7 +43,7 @@ namespace ESM if (!esm.applyContentFileMapping(state.mRef.mRefNum)) state.mRef.mRefNum = FormId(); // content file removed; unset refnum, but keep object. - if (state.mCount == 0) + if (state.mRef.mCount == 0) continue; mItems.push_back(state); @@ -117,8 +117,8 @@ namespace ESM const int count = entry.second; for (auto& item : mItems) { - if (item.mCount == count && id == item.mRef.mRefID) - item.mCount = -count; + if (item.mRef.mCount == count && id == item.mRef.mRefID) + item.mRef.mCount = -count; } } } diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index a46200944a..7d26f431d6 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -29,8 +29,11 @@ namespace ESM mEnabled = 1; esm.getHNOT(mEnabled, "ENAB"); - mCount = 1; - esm.getHNOT(mCount, "COUN"); + if (mVersion <= MaxOldCountFormatVersion) + { + mRef.mCount = 1; + esm.getHNOT(mRef.mCount, "COUN"); + } mPosition = mRef.mPos; esm.getHNOT("POS_", mPosition.pos, mPosition.rot); @@ -60,9 +63,6 @@ namespace ESM if (!mEnabled && !inInventory) esm.writeHNT("ENAB", mEnabled); - if (mCount != 1) - esm.writeHNT("COUN", mCount); - if (!inInventory && mPosition != mRef.mPos) { std::array pos; @@ -85,7 +85,6 @@ namespace ESM mRef.blank(); mHasLocals = 0; mEnabled = false; - mCount = 1; for (int i = 0; i < 3; ++i) { mPosition.pos[i] = 0; diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index 4c09d16d18..b3f7bd3d45 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -32,7 +32,6 @@ namespace ESM Locals mLocals; LuaScripts mLuaScripts; unsigned char mEnabled; - int32_t mCount; Position mPosition; uint32_t mFlags; @@ -46,7 +45,6 @@ namespace ESM ObjectState() : mHasLocals(0) , mEnabled(0) - , mCount(0) , mFlags(0) , mHasCustomState(true) { diff --git a/components/esm4/loadachr.cpp b/components/esm4/loadachr.cpp index 1a6d47497e..dc181dda4b 100644 --- a/components/esm4/loadachr.cpp +++ b/components/esm4/loadachr.cpp @@ -82,6 +82,11 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) reader.getFormId(mEsp.parent); reader.get(mEsp.flags); break; + case ESM4::SUB_XCNT: + { + reader.get(mCount); + break; + } case ESM4::SUB_XRGD: // ragdoll case ESM4::SUB_XRGB: // ragdoll biped case ESM4::SUB_XHRS: // horse formId @@ -113,7 +118,6 @@ void ESM4::ActorCharacter::load(ESM4::Reader& reader) case ESM4::SUB_XATO: // FONV case ESM4::SUB_MNAM: // FO4 case ESM4::SUB_XATP: // FO4 - case ESM4::SUB_XCNT: // FO4 case ESM4::SUB_XEMI: // FO4 case ESM4::SUB_XFVC: // FO4 case ESM4::SUB_XHLT: // FO4 diff --git a/components/esm4/loadachr.hpp b/components/esm4/loadachr.hpp index dd867bbafd..8abb47c8bc 100644 --- a/components/esm4/loadachr.hpp +++ b/components/esm4/loadachr.hpp @@ -57,6 +57,8 @@ namespace ESM4 EnableParent mEsp; + std::int32_t mCount = 1; + void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; diff --git a/components/esm4/loadrefr.cpp b/components/esm4/loadrefr.cpp index a193907ac4..fb26e39546 100644 --- a/components/esm4/loadrefr.cpp +++ b/components/esm4/loadrefr.cpp @@ -268,6 +268,11 @@ void ESM4::Reference::load(ESM4::Reader& reader) break; } + case ESM4::SUB_XCNT: + { + reader.get(mCount); + break; + } // lighting case ESM4::SUB_LNAM: // lighting template formId case ESM4::SUB_XLIG: // struct, FOV, fade, etc @@ -279,7 +284,6 @@ void ESM4::Reference::load(ESM4::Reader& reader) // case ESM4::SUB_XPCI: // formId case ESM4::SUB_XLCM: - case ESM4::SUB_XCNT: case ESM4::SUB_ONAM: case ESM4::SUB_VMAD: case ESM4::SUB_XPRM: diff --git a/components/esm4/loadrefr.hpp b/components/esm4/loadrefr.hpp index af04a791c8..ec76928827 100644 --- a/components/esm4/loadrefr.hpp +++ b/components/esm4/loadrefr.hpp @@ -97,7 +97,7 @@ namespace ESM4 EnableParent mEsp; - std::uint32_t mCount = 1; // only if > 1 + std::int32_t mCount = 1; // only if > 1 ESM::FormId mAudioLocation; From 94409ce1722d33dfc2656fa8e5dbfbcb11a58a37 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 31 Dec 2023 14:56:59 +0100 Subject: [PATCH 162/231] Add missing UniversalId::mClass initialization --- apps/opencs/model/world/universalid.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 10648a1ecc..9daf87e20a 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -221,6 +221,23 @@ namespace return std::to_string(value); } + + CSMWorld::UniversalId::Class getClassByType(CSMWorld::UniversalId::Type type) + { + if (const auto it + = std::find_if(std::begin(sIdArg), std::end(sIdArg), [&](const TypeData& v) { return v.mType == type; }); + it != std::end(sIdArg)) + return it->mClass; + if (const auto it = std::find_if( + std::begin(sIndexArg), std::end(sIndexArg), [&](const TypeData& v) { return v.mType == type; }); + it != std::end(sIndexArg)) + return it->mClass; + if (const auto it + = std::find_if(std::begin(sNoArg), std::end(sNoArg), [&](const TypeData& v) { return v.mType == type; }); + it != std::end(sNoArg)) + return it->mClass; + throw std::logic_error("invalid UniversalId type: " + std::to_string(type)); + } } CSMWorld::UniversalId::UniversalId(const std::string& universalId) @@ -330,7 +347,8 @@ CSMWorld::UniversalId::UniversalId(Type type, ESM::RefId id) } CSMWorld::UniversalId::UniversalId(Type type, const UniversalId& id) - : mType(type) + : mClass(getClassByType(type)) + , mType(type) , mValue(id.mValue) { } From b835114ce214f40ae0279db818ee667ff5d45915 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 1 Jan 2024 12:48:12 +0100 Subject: [PATCH 163/231] Prevent input type assertion --- components/compiler/scanner.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index d5cea61e7f..34b122413e 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -70,7 +70,11 @@ namespace Compiler && mData[3] == 0; } - bool isDigit() const { return std::isdigit(mData[0]) && mData[1] == 0 && mData[2] == 0 && mData[3] == 0; } + bool isDigit() const + { + return std::isdigit(static_cast(mData[0])) && mData[1] == 0 && mData[2] == 0 + && mData[3] == 0; + } bool isMinusSign() const { @@ -85,7 +89,8 @@ namespace Compiler if (isMinusSign()) return false; - return std::isalpha(mData[0]) || mData[1] != 0 || mData[2] != 0 || mData[3] != 0; + return std::isalpha(static_cast(mData[0])) || mData[1] != 0 || mData[2] != 0 + || mData[3] != 0; } void appendTo(std::string& str) const From 5bd5c8401801eaeb4972ef7def503cb57500f7dd Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Dec 2023 16:55:32 +0100 Subject: [PATCH 164/231] Replace missing NPC races and default animations --- apps/openmw/mwworld/esmstore.cpp | 62 +++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 0b661b4442..137c9cf026 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -17,8 +17,10 @@ #include #include #include +#include #include "../mwmechanics/spelllist.hpp" +#include "../mwrender/actorutil.hpp" namespace { @@ -83,12 +85,22 @@ namespace throw std::runtime_error("List of NPC classes is empty!"); } + const ESM::RefId& getDefaultRace(const MWWorld::Store& races) + { + auto it = races.begin(); + if (it != races.end()) + return it->mId; + throw std::runtime_error("List of NPC races is empty!"); + } + std::vector getNPCsToReplace(const MWWorld::Store& factions, - const MWWorld::Store& classes, const MWWorld::Store& scripts, - const std::unordered_map& npcs) + const MWWorld::Store& classes, const MWWorld::Store& races, + const MWWorld::Store& scripts, const std::unordered_map& npcs) { // Cache first class from store - we will use it if current class is not found const ESM::RefId& defaultCls = getDefaultClass(classes); + // Same for races + const ESM::RefId& defaultRace = getDefaultRace(races); // Validate NPCs for non-existing class and faction. // We will replace invalid entries by fixed ones @@ -113,8 +125,7 @@ namespace } } - const ESM::RefId& npcClass = npc.mClass; - const ESM::Class* cls = classes.search(npcClass); + const ESM::Class* cls = classes.search(npc.mClass); if (!cls) { Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent class " @@ -123,6 +134,41 @@ namespace changed = true; } + const ESM::Race* race = races.search(npc.mRace); + if (race) + { + // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it + // will match the NPC's race) until the NPC's race is changed. If the player record contains a default + // non-beast race animation and the player selects a beast race in chargen, animations aren't applied + // properly. Morrowind.exe handles this gracefully, so we clear the animation here to force the default. + if (!npc.mModel.empty() && npc.mId == "player") + { + const bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; + const std::string& defaultModel = MWRender::getActorSkeleton(false, !npc.isMale(), isBeast, false); + std::string model = Misc::ResourceHelpers::correctMeshPath(npc.mModel); + if (model.size() == defaultModel.size()) + { + std::replace(model.begin(), model.end(), '/', '\\'); + std::string normalizedDefault = defaultModel; + std::replace(normalizedDefault.begin(), normalizedDefault.end(), '/', '\\'); + if (Misc::StringUtils::ciEqual(normalizedDefault, model)) + { + npc.mModel.clear(); + changed = true; + } + } + } + } + else + { + Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent race " << npc.mRace + << ", using " << defaultRace << " race as replacement."; + npc.mRace = defaultRace; + // Remove animations that might be race specific + npc.mModel.clear(); + changed = true; + } + if (!npc.mScript.empty() && !scripts.search(npc.mScript)) { Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent script " @@ -580,8 +626,8 @@ namespace MWWorld void ESMStore::validate() { auto& npcs = getWritable(); - std::vector npcsToReplace = getNPCsToReplace( - getWritable(), getWritable(), getWritable(), npcs.mStatic); + std::vector npcsToReplace = getNPCsToReplace(getWritable(), getWritable(), + getWritable(), getWritable(), npcs.mStatic); for (const ESM::NPC& npc : npcsToReplace) { @@ -623,8 +669,8 @@ namespace MWWorld auto& npcs = getWritable(); auto& scripts = getWritable(); - std::vector npcsToReplace = getNPCsToReplace( - getWritable(), getWritable(), getWritable(), npcs.mDynamic); + std::vector npcsToReplace = getNPCsToReplace(getWritable(), getWritable(), + getWritable(), getWritable(), npcs.mDynamic); for (const ESM::NPC& npc : npcsToReplace) npcs.insert(npc); From 4636ab3f3ede63940551c5f010ab2c560cd50c1c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Dec 2023 17:14:34 +0100 Subject: [PATCH 165/231] Update cmakelists --- apps/openmw_test_suite/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 967511953d..421e9e82f1 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -7,6 +7,7 @@ file(GLOB UNITTEST_SRC_FILES ../openmw/mwworld/store.cpp ../openmw/mwworld/esmstore.cpp ../openmw/mwworld/timestamp.cpp + ../openmw/mwrender/actorutil.cpp mwworld/test_store.cpp mwworld/testduration.cpp From 84c15344ee2103c2f445799ea58c20177f16d52b Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 29 Dec 2023 21:55:22 +0100 Subject: [PATCH 166/231] Address feedback --- apps/openmw/mwworld/esmstore.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 137c9cf026..d5d2c03b8b 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "../mwmechanics/spelllist.hpp" #include "../mwrender/actorutil.hpp" @@ -140,22 +141,17 @@ namespace // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it // will match the NPC's race) until the NPC's race is changed. If the player record contains a default // non-beast race animation and the player selects a beast race in chargen, animations aren't applied - // properly. Morrowind.exe handles this gracefully, so we clear the animation here to force the default. + // properly. Morrowind.exe handles this gracefully, so we clear the animation here to force the default + // values to be used. if (!npc.mModel.empty() && npc.mId == "player") { const bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; const std::string& defaultModel = MWRender::getActorSkeleton(false, !npc.isMale(), isBeast, false); std::string model = Misc::ResourceHelpers::correctMeshPath(npc.mModel); - if (model.size() == defaultModel.size()) + if (VFS::Path::pathEqual(defaultModel, model)) { - std::replace(model.begin(), model.end(), '/', '\\'); - std::string normalizedDefault = defaultModel; - std::replace(normalizedDefault.begin(), normalizedDefault.end(), '/', '\\'); - if (Misc::StringUtils::ciEqual(normalizedDefault, model)) - { - npc.mModel.clear(); - changed = true; - } + npc.mModel.clear(); + changed = true; } } } From 03c791e61ac832a280d3326760f918835b6ca75c Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 1 Jan 2024 13:58:55 +0100 Subject: [PATCH 167/231] Move animation handling to NpcAnimation --- CHANGELOG.md | 1 + apps/openmw/mwrender/actorutil.cpp | 8 ++++++++ apps/openmw/mwrender/actorutil.hpp | 2 ++ apps/openmw/mwrender/npcanimation.cpp | 14 +++++++++++--- apps/openmw/mwworld/esmstore.cpp | 26 +------------------------- apps/openmw_test_suite/CMakeLists.txt | 1 - 6 files changed, 23 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f16ac81b..eff35bfa53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ Bug #6657: Distant terrain tiles become black when using FWIW mod Bug #6661: Saved games that have no preview screenshot cause issues or crashes Bug #6716: mwscript comparison operator handling is too restrictive + Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW Bug #6807: Ultimate Galleon is not working properly Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack diff --git a/apps/openmw/mwrender/actorutil.cpp b/apps/openmw/mwrender/actorutil.cpp index 6cef42d60f..8da921e532 100644 --- a/apps/openmw/mwrender/actorutil.cpp +++ b/apps/openmw/mwrender/actorutil.cpp @@ -1,6 +1,7 @@ #include "actorutil.hpp" #include +#include namespace MWRender { @@ -29,4 +30,11 @@ namespace MWRender return Settings::models().mXbaseanim1st; } } + + bool isDefaultActorSkeleton(std::string_view model) + { + return VFS::Path::pathEqual(Settings::models().mBaseanimkna.get(), model) + || VFS::Path::pathEqual(Settings::models().mBaseanimfemale.get(), model) + || VFS::Path::pathEqual(Settings::models().mBaseanim.get(), model); + } } diff --git a/apps/openmw/mwrender/actorutil.hpp b/apps/openmw/mwrender/actorutil.hpp index bbffc4ad24..3107bf0183 100644 --- a/apps/openmw/mwrender/actorutil.hpp +++ b/apps/openmw/mwrender/actorutil.hpp @@ -2,10 +2,12 @@ #define OPENMW_APPS_OPENMW_MWRENDER_ACTORUTIL_H #include +#include namespace MWRender { const std::string& getActorSkeleton(bool firstPerson, bool female, bool beast, bool werewolf); + bool isDefaultActorSkeleton(std::string_view model); } #endif diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 900d0d9ae1..e17a1f34a3 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -493,8 +493,16 @@ namespace MWRender std::string smodel = defaultSkeleton; if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) - smodel = Misc::ResourceHelpers::correctActorModelPath( - Misc::ResourceHelpers::correctMeshPath(mNpc->mModel), mResourceSystem->getVFS()); + { + // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it + // will match the NPC's race) until the NPC's race is changed. If the player record contains a default + // non-beast race animation and the player selects a beast race in chargen, animations aren't applied + // properly. Morrowind.exe appears to handle an NPC using any of the base animations as not having custom + // animations. + std::string model = Misc::ResourceHelpers::correctMeshPath(mNpc->mModel); + if (!isDefaultActorSkeleton(model)) + smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); + } setObjectRoot(smodel, true, true, false); @@ -511,7 +519,7 @@ namespace MWRender addAnimSource(smodel, smodel); - if (!isWerewolf && mNpc->mRace.contains("argonian")) + if (!isWerewolf && isBeast && mNpc->mRace.contains("argonian")) addAnimSource("meshes\\xargonian_swimkna.nif", smodel); } else diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index d5d2c03b8b..7ecaaa217d 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -17,11 +17,8 @@ #include #include #include -#include -#include #include "../mwmechanics/spelllist.hpp" -#include "../mwrender/actorutil.hpp" namespace { @@ -136,32 +133,11 @@ namespace } const ESM::Race* race = races.search(npc.mRace); - if (race) - { - // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it - // will match the NPC's race) until the NPC's race is changed. If the player record contains a default - // non-beast race animation and the player selects a beast race in chargen, animations aren't applied - // properly. Morrowind.exe handles this gracefully, so we clear the animation here to force the default - // values to be used. - if (!npc.mModel.empty() && npc.mId == "player") - { - const bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; - const std::string& defaultModel = MWRender::getActorSkeleton(false, !npc.isMale(), isBeast, false); - std::string model = Misc::ResourceHelpers::correctMeshPath(npc.mModel); - if (VFS::Path::pathEqual(defaultModel, model)) - { - npc.mModel.clear(); - changed = true; - } - } - } - else + if (!race) { Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent race " << npc.mRace << ", using " << defaultRace << " race as replacement."; npc.mRace = defaultRace; - // Remove animations that might be race specific - npc.mModel.clear(); changed = true; } diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 421e9e82f1..967511953d 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -7,7 +7,6 @@ file(GLOB UNITTEST_SRC_FILES ../openmw/mwworld/store.cpp ../openmw/mwworld/esmstore.cpp ../openmw/mwworld/timestamp.cpp - ../openmw/mwrender/actorutil.cpp mwworld/test_store.cpp mwworld/testduration.cpp From ef4e5b45e3ad4f77774f7f1817ad4fc4f8cef8ef Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 1 Jan 2024 15:06:32 +0100 Subject: [PATCH 168/231] Don't add custom anim source if it's a default animation but keep the skeleton --- apps/openmw/mwrender/npcanimation.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index e17a1f34a3..1559ebdd5d 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -492,16 +492,12 @@ namespace MWRender getActorSkeleton(is1stPerson, isFemale, isBeast, isWerewolf), mResourceSystem->getVFS()); std::string smodel = defaultSkeleton; + bool isBase = !isWerewolf; if (!is1stPerson && !isWerewolf && !mNpc->mModel.empty()) { - // TESCS sometimes writes the default animation nif to the animation subrecord. This harmless (as it - // will match the NPC's race) until the NPC's race is changed. If the player record contains a default - // non-beast race animation and the player selects a beast race in chargen, animations aren't applied - // properly. Morrowind.exe appears to handle an NPC using any of the base animations as not having custom - // animations. std::string model = Misc::ResourceHelpers::correctMeshPath(mNpc->mModel); - if (!isDefaultActorSkeleton(model)) - smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); + isBase = isDefaultActorSkeleton(model); + smodel = Misc::ResourceHelpers::correctActorModelPath(model, mResourceSystem->getVFS()); } setObjectRoot(smodel, true, true, false); @@ -511,13 +507,14 @@ namespace MWRender if (!is1stPerson) { const std::string& base = Settings::models().mXbaseanim; - if (smodel != base && !isWerewolf) + if (!isWerewolf) addAnimSource(base, smodel); if (smodel != defaultSkeleton && base != defaultSkeleton) addAnimSource(defaultSkeleton, smodel); - addAnimSource(smodel, smodel); + if (!isBase) + addAnimSource(smodel, smodel); if (!isWerewolf && isBeast && mNpc->mRace.contains("argonian")) addAnimSource("meshes\\xargonian_swimkna.nif", smodel); @@ -525,10 +522,11 @@ namespace MWRender else { const std::string& base = Settings::models().mXbaseanim1st; - if (smodel != base && !isWerewolf) + if (!isWerewolf) addAnimSource(base, smodel); - addAnimSource(smodel, smodel); + if (!isBase) + addAnimSource(smodel, smodel); mObjectRoot->setNodeMask(Mask_FirstPerson); mObjectRoot->addCullCallback(new OverrideFieldOfViewCallback(mFirstPersonFieldOfView)); From 93e50cc7aaa85df29071ab32bb2c8d14fb620489 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 1 Jan 2024 14:56:48 +0400 Subject: [PATCH 169/231] Improve format version in content selector --- apps/esmtool/esmtool.cpp | 4 ++-- components/contentselector/model/contentmodel.cpp | 12 ++++++------ components/contentselector/model/esmfile.cpp | 5 ++--- components/contentselector/model/esmfile.hpp | 8 ++++---- components/esm/esmcommon.hpp | 6 ------ components/esm3/esmreader.hpp | 4 ++-- components/esm3/esmwriter.cpp | 4 ++-- components/esm3/loadregn.cpp | 3 ++- components/esm3/loadtes3.cpp | 4 ++-- components/esm3/loadtes3.hpp | 7 ++----- 10 files changed, 24 insertions(+), 33 deletions(-) diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index a36996ff4f..13f222ed72 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -341,7 +341,7 @@ namespace { std::cout << "Author: " << esm.getAuthor() << '\n' << "Description: " << esm.getDesc() << '\n' - << "File format version: " << esm.getFVer() << '\n'; + << "File format version: " << esm.esmVersionF() << '\n'; std::vector masterData = esm.getGameFiles(); if (!masterData.empty()) { @@ -508,7 +508,7 @@ namespace ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(info.encoding)); esm.setEncoder(&encoder); esm.setHeader(data.mHeader); - esm.setVersion(ESM::VER_13); + esm.setVersion(ESM::VER_130); esm.setRecordCount(recordCount); std::fstream save(info.outname, std::fstream::out | std::fstream::binary); diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index f8ecc67998..9b7bb11f09 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -489,7 +489,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf fileReader.setEncoder(&encoder); fileReader.open(std::move(stream), filepath); file->setAuthor(QString::fromUtf8(fileReader.getAuthor().c_str())); - file->setFormat(fileReader.getFormatVersion()); + file->setFormat(QString::number(fileReader.esmVersionF())); file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str())); for (const auto& master : fileReader.getGameFiles()) file->addGameFile(QString::fromUtf8(master.name.c_str())); @@ -505,11 +505,11 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf case ESM::Format::Tes4: { ToUTF8::StatelessUtf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding.toStdString())); - ESM4::Reader reader(std::move(stream), filepath, nullptr, &encoder, true); - file->setAuthor(QString::fromUtf8(reader.getAuthor().c_str())); - file->setFormat(reader.esmVersion()); - file->setDescription(QString::fromUtf8(reader.getDesc().c_str())); - for (const auto& master : reader.getGameFiles()) + ESM4::Reader fileReader(std::move(stream), filepath, nullptr, &encoder, true); + file->setAuthor(QString::fromUtf8(fileReader.getAuthor().c_str())); + file->setFormat(QString::number(fileReader.esmVersionF())); + file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str())); + for (const auto& master : fileReader.getGameFiles()) file->addGameFile(QString::fromUtf8(master.name.c_str())); break; } diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 75a0adb45e..7c62299048 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -26,7 +26,7 @@ void ContentSelectorModel::EsmFile::setDate(const QDateTime& modified) mModified = modified; } -void ContentSelectorModel::EsmFile::setFormat(int format) +void ContentSelectorModel::EsmFile::setFormat(const QString& format) { mVersion = format; } @@ -51,8 +51,7 @@ QByteArray ContentSelectorModel::EsmFile::encodedData() const QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); - stream << mFileName << mAuthor << QString::number(mVersion) << mModified.toString() << mPath << mDescription - << mGameFiles; + stream << mFileName << mAuthor << mVersion << mModified.toString() << mPath << mDescription << mGameFiles; return encodedData; } diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index 5a04ec8b38..a65c778294 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -40,7 +40,7 @@ namespace ContentSelectorModel void setAuthor(const QString& author); void setSize(const int size); void setDate(const QDateTime& modified); - void setFormat(const int format); + void setFormat(const QString& format); void setFilePath(const QString& path); void setGameFiles(const QStringList& gameFiles); void setDescription(const QString& description); @@ -51,7 +51,7 @@ namespace ContentSelectorModel QString fileName() const { return mFileName; } QString author() const { return mAuthor; } QDateTime modified() const { return mModified; } - ESM::FormatVersion formatVersion() const { return mVersion; } + QString formatVersion() const { return mVersion; } QString filePath() const { return mPath; } /// @note Contains file names, not paths. @@ -76,7 +76,7 @@ namespace ContentSelectorModel private: QString mTooltipTemlate = tr( "Author: %1
" - "Version: %2
" + "Format version: %2
" "Modified: %3
" "Path:
%4
" "
Description:
%5
" @@ -85,7 +85,7 @@ namespace ContentSelectorModel QString mFileName; QString mAuthor; QDateTime mModified; - ESM::FormatVersion mVersion = ESM::DefaultFormatVersion; + QString mVersion = QString::number(ESM::DefaultFormatVersion); QString mPath; QStringList mGameFiles; QString mDescription; diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index e92ae06806..69b877c9c9 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -13,12 +13,6 @@ namespace ESM { - enum Version - { - VER_12 = 0x3f99999a, - VER_13 = 0x3fa66666 - }; - enum RecordFlag { // This flag exists, but is not used to determine if a record has been deleted while loading diff --git a/components/esm3/esmreader.hpp b/components/esm3/esmreader.hpp index bafc89a74c..461f154001 100644 --- a/components/esm3/esmreader.hpp +++ b/components/esm3/esmreader.hpp @@ -53,9 +53,9 @@ namespace ESM * *************************************************************************/ - int getVer() const { return mHeader.mData.version; } + int getVer() const { return mHeader.mData.version.ui; } int getRecordCount() const { return mHeader.mData.records; } - float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } + float esmVersionF() const { return (mHeader.mData.version.f); } const std::string& getAuthor() const { return mHeader.mData.author; } const std::string& getDesc() const { return mHeader.mData.desc; } const std::vector& getGameFiles() const { return mHeader.mMaster; } diff --git a/components/esm3/esmwriter.cpp b/components/esm3/esmwriter.cpp index 66788aa924..ad64ced0a4 100644 --- a/components/esm3/esmwriter.cpp +++ b/components/esm3/esmwriter.cpp @@ -84,12 +84,12 @@ namespace ESM unsigned int ESMWriter::getVersion() const { - return mHeader.mData.version; + return mHeader.mData.version.ui; } void ESMWriter::setVersion(unsigned int ver) { - mHeader.mData.version = ver; + mHeader.mData.version.ui = ver; } void ESMWriter::setType(int type) diff --git a/components/esm3/loadregn.cpp b/components/esm3/loadregn.cpp index 5148a446c2..63e785882e 100644 --- a/components/esm3/loadregn.cpp +++ b/components/esm3/loadregn.cpp @@ -2,6 +2,7 @@ #include "esmreader.hpp" #include "esmwriter.hpp" +#include namespace ESM { @@ -83,7 +84,7 @@ namespace ESM esm.writeHNOCString("FNAM", mName); - if (esm.getVersion() == VER_12) + if (esm.getVersion() == VER_120) esm.writeHNT("WEAT", mData.mProbabilities, mData.mProbabilities.size() - 2); else esm.writeHNT("WEAT", mData.mProbabilities); diff --git a/components/esm3/loadtes3.cpp b/components/esm3/loadtes3.cpp index b6fbe76553..eeb2d38761 100644 --- a/components/esm3/loadtes3.cpp +++ b/components/esm3/loadtes3.cpp @@ -9,7 +9,7 @@ namespace ESM void Header::blank() { - mData.version = VER_13; + mData.version.ui = VER_130; mData.type = 0; mData.author.clear(); mData.desc.clear(); @@ -26,7 +26,7 @@ namespace ESM if (esm.isNextSub("HEDR")) { esm.getSubHeader(); - esm.getT(mData.version); + esm.getT(mData.version.ui); esm.getT(mData.type); mData.author = esm.getMaybeFixedStringSize(32); mData.desc = esm.getMaybeFixedStringSize(256); diff --git a/components/esm3/loadtes3.hpp b/components/esm3/loadtes3.hpp index 2f7493e15f..54fd303485 100644 --- a/components/esm3/loadtes3.hpp +++ b/components/esm3/loadtes3.hpp @@ -3,6 +3,7 @@ #include +#include "components/esm/common.hpp" #include "components/esm/esmcommon.hpp" #include "components/esm3/formatversion.hpp" @@ -13,11 +14,7 @@ namespace ESM struct Data { - /* File format version. This is actually a float, the supported - versions are 1.2 and 1.3. These correspond to: - 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 - */ - uint32_t version; + ESM::ESMVersion version; int32_t type; // 0=esp, 1=esm, 32=ess (unused) std::string author; // Author's name std::string desc; // File description From 04b714198a56256b266b21cd250031767c2b7198 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Jan 2024 20:59:33 +0300 Subject: [PATCH 170/231] Manually clamp controller time (#7523) --- components/nifosg/controller.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 54e9e2bb16..f480152a73 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -57,7 +57,13 @@ namespace NifOsg } case Nif::NiTimeController::ExtrapolationMode::Constant: default: - return std::clamp(time, mStartTime, mStopTime); + { + if (time < mStartTime) + return mStartTime; + if (time > mStopTime) + return mStopTime; + return time; + } } } From 2fbdde34c619af2b9b1c4f88fdfb6f466d9d35c4 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Jan 2024 21:55:55 +0300 Subject: [PATCH 171/231] Set paged refs' base node to null (#6335) --- apps/openmw/mwphysics/actor.cpp | 6 +++++- apps/openmw/mwphysics/object.cpp | 3 +++ apps/openmw/mwworld/scene.cpp | 4 +--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index dec055d68f..e1efe6d242 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -102,7 +102,11 @@ namespace MWPhysics updateScaleUnsafe(); if (!mRotationallyInvariant) - mRotation = mPtr.getRefData().getBaseNode()->getAttitude(); + { + const SceneUtil::PositionAttitudeTransform* baseNode = mPtr.getRefData().getBaseNode(); + if (baseNode) + mRotation = baseNode->getAttitude(); + } addCollisionMask(getCollisionMask()); updateCollisionObjectPositionUnsafe(); diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 53529ec729..9c97ac7c32 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -110,6 +110,9 @@ namespace MWPhysics if (mShapeInstance->mAnimatedShapes.empty()) return false; + if (!mPtr.getRefData().getBaseNode()) + return false; + assert(mShapeInstance->mCollisionShape->isCompound()); btCompoundShape* compound = static_cast(mShapeInstance->mCollisionShape.get()); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index d8875b513e..72b2dc3022 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -115,9 +115,7 @@ namespace if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); else - ptr.getRefData().setBaseNode( - new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend - // on basenode + ptr.getRefData().setBaseNode(nullptr); setNodeRotation(ptr, rendering, rotation); if (ptr.getClass().useAnim()) From 0705175b9b71ca067e7cdf36e52c8d58ee47af3e Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 31 Dec 2023 19:55:32 +0400 Subject: [PATCH 172/231] Remove qmake leftover --- CI/before_script.msvc.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index cdac794a7e..e11ceb499d 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -902,7 +902,6 @@ printf "Qt ${QT_VER}... " fi cd $QT_SDK - add_cmake_opts -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" for CONFIGURATION in ${CONFIGURATIONS[@]}; do if [ $CONFIGURATION == "Debug" ]; then DLLSUFFIX="d" From 5f2cbf810dda4962f0297d0bb60127a4dfe8fb9f Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 13:38:55 +0100 Subject: [PATCH 173/231] Check if controls are on for Use action --- files/data/scripts/omw/playercontrols.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index f14724f0a2..71a6094aaa 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -195,7 +195,7 @@ end)) local startUse = false input.registerActionHandler('Use', async:callback(function(value) - if value then startUse = true end + if value and controlsAllowed() then startUse = true end end)) local function processAttacking() if Actor.stance(self) == Actor.STANCE.Spell then From 1d8ee7984f1fef4e8cd2a4b377271bb1a50f7859 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 13:45:45 +0100 Subject: [PATCH 174/231] Make input.triggers[] consistent with input.actions[] --- components/lua/inputactions.cpp | 8 ++++++++ components/lua/inputactions.hpp | 6 +----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/components/lua/inputactions.cpp b/components/lua/inputactions.cpp index c21fbcf112..69c91921eb 100644 --- a/components/lua/inputactions.cpp +++ b/components/lua/inputactions.cpp @@ -265,6 +265,14 @@ namespace LuaUtil mHandlers.push_back({}); } + std::optional Registry::operator[](std::string_view key) + { + auto iter = mIds.find(key); + if (iter == mIds.end()) + return std::nullopt; + return mInfo[iter->second]; + } + void Registry::registerHandler(std::string_view key, const LuaUtil::Callback& callback) { Id id = safeIdByKey(key); diff --git a/components/lua/inputactions.hpp b/components/lua/inputactions.hpp index ac3907b55d..abc6cf73fa 100644 --- a/components/lua/inputactions.hpp +++ b/components/lua/inputactions.hpp @@ -126,11 +126,7 @@ namespace LuaUtil::InputTrigger return std::nullopt; return it->first; } - std::optional operator[](std::string_view key) - { - Id id = safeIdByKey(key); - return mInfo[id]; - } + std::optional operator[](std::string_view key); void insert(Info info); void registerHandler(std::string_view key, const LuaUtil::Callback& callback); void activate(std::string_view key); From c1e8e88914572dd0e9b5258ed2cd339157f8fdd5 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 13:47:04 +0100 Subject: [PATCH 175/231] Fix input trigger docs --- files/lua_api/openmw/input.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/files/lua_api/openmw/input.lua b/files/lua_api/openmw/input.lua index 563a4ab1f5..0a85602bcc 100644 --- a/files/lua_api/openmw/input.lua +++ b/files/lua_api/openmw/input.lua @@ -402,10 +402,10 @@ --- -- @type TriggerInfo --- @field [parent=#Actioninfo] #string key --- @field [parent=#Actioninfo] #string l10n Localization context containing the name and description keys --- @field [parent=#Actioninfo] #string name Localization key of the trigger's name --- @field [parent=#Actioninfo] #string description Localization key of the trigger's description +-- @field [parent=#TriggerInfo] #string key +-- @field [parent=#TriggerInfo] #string l10n Localization context containing the name and description keys +-- @field [parent=#TriggerInfo] #string name Localization key of the trigger's name +-- @field [parent=#TriggerInfo] #string description Localization key of the trigger's description --- -- Map of all currently registered triggers From e9b48e35c0b36c0a9ba50bd0b844aee37deeb2f9 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 13:54:02 +0100 Subject: [PATCH 176/231] Coverity defects --- apps/openmw/mwlua/inputbindings.cpp | 8 ++++---- components/lua/inputactions.cpp | 4 ++-- components/lua/inputactions.hpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 8763dce28d..33b19f3b4d 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -76,7 +76,7 @@ namespace MWLua inputActions[sol::meta_function::pairs] = pairs; } - auto actionInfo = context.mLua->sol().new_usertype("ActionInfo", "key", + context.mLua->sol().new_usertype("ActionInfo", "key", sol::property([](const LuaUtil::InputAction::Info& info) { return info.mKey; }), "name", sol::property([](const LuaUtil::InputAction::Info& info) { return info.mName; }), "description", sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDescription; }), "type", @@ -102,7 +102,7 @@ namespace MWLua inputTriggers[sol::meta_function::pairs] = pairs; } - auto triggerInfo = context.mLua->sol().new_usertype("TriggerInfo", "key", + context.mLua->sol().new_usertype("TriggerInfo", "key", sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mKey; }), "name", sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mName; }), "description", sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mDescription; }), "l10n", @@ -127,7 +127,7 @@ namespace MWLua parsedOptions.mName = options["name"].get(); parsedOptions.mDescription = options["description"].get(); parsedOptions.mDefaultValue = options["defaultValue"].get(); - manager->inputActions().insert(parsedOptions); + manager->inputActions().insert(std::move(parsedOptions)); }; api["bindAction"] = [manager = context.mLuaManager]( std::string_view key, const sol::table& callback, sol::table dependencies) { @@ -164,7 +164,7 @@ namespace MWLua parsedOptions.mL10n = options["l10n"].get(); parsedOptions.mName = options["name"].get(); parsedOptions.mDescription = options["description"].get(); - manager->inputTriggers().insert(parsedOptions); + manager->inputTriggers().insert(std::move(parsedOptions)); }; api["registerTriggerHandler"] = [manager = context.mLuaManager](std::string_view key, const sol::table& callback) { diff --git a/components/lua/inputactions.cpp b/components/lua/inputactions.cpp index 69c91921eb..7c7551ba60 100644 --- a/components/lua/inputactions.cpp +++ b/components/lua/inputactions.cpp @@ -113,7 +113,7 @@ namespace LuaUtil } } - void Registry::insert(Info info) + void Registry::insert(const Info& info) { if (mIds.find(info.mKey) != mIds.end()) throw std::domain_error(Misc::StringUtils::format("Action key \"%s\" is already in use", info.mKey)); @@ -251,7 +251,7 @@ namespace LuaUtil return it->second; } - void Registry::insert(Info info) + void Registry::insert(const Info& info) { if (mIds.find(info.mKey) != mIds.end()) throw std::domain_error(Misc::StringUtils::format("Trigger key \"%s\" is already in use", info.mKey)); diff --git a/components/lua/inputactions.hpp b/components/lua/inputactions.hpp index abc6cf73fa..d05bb71f2c 100644 --- a/components/lua/inputactions.hpp +++ b/components/lua/inputactions.hpp @@ -60,7 +60,7 @@ namespace LuaUtil::InputAction { public: using ConstIterator = std::vector::const_iterator; - void insert(Info info); + void insert(const Info& info); size_t size() const { return mKeys.size(); } std::optional firstKey() const { return mKeys.empty() ? std::nullopt : std::optional(mKeys[0]); } std::optional nextKey(std::string_view key) const; @@ -127,7 +127,7 @@ namespace LuaUtil::InputTrigger return it->first; } std::optional operator[](std::string_view key); - void insert(Info info); + void insert(const Info& info); void registerHandler(std::string_view key, const LuaUtil::Callback& callback); void activate(std::string_view key); void clear() From 9d1a3f243d0c3a514195467d323e0812ebf571f0 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 14:31:13 +0100 Subject: [PATCH 177/231] Use the same controls check for startUse and processAttacking --- files/data/scripts/omw/playercontrols.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 71a6094aaa..289d3144dc 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -195,7 +195,7 @@ end)) local startUse = false input.registerActionHandler('Use', async:callback(function(value) - if value and controlsAllowed() then startUse = true end + if value and combatAllowed() then startUse = true end end)) local function processAttacking() if Actor.stance(self) == Actor.STANCE.Spell then From e734191b56db15677f1cb9413487a5345e3f6db6 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 2 Jan 2024 14:40:03 +0100 Subject: [PATCH 178/231] Add clarifying comment --- files/data/scripts/omw/playercontrols.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 289d3144dc..ec7d0d238e 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -198,6 +198,8 @@ input.registerActionHandler('Use', async:callback(function(value) if value and combatAllowed() then startUse = true end end)) local function processAttacking() + -- for spell-casting, set controls.use to true for exactly one frame + -- otherwise spell casting is attempted every frame while Use is true if Actor.stance(self) == Actor.STANCE.Spell then self.controls.use = startUse and 1 or 0 else From fb16871c805a0a0ce7e34fae8d8d0f79843da72a Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 2 Jan 2024 18:30:35 +0400 Subject: [PATCH 179/231] Avoid redundant string copies --- apps/openmw/mwclass/activator.cpp | 2 +- apps/openmw/mwclass/apparatus.cpp | 2 +- apps/openmw/mwclass/armor.cpp | 2 +- apps/openmw/mwclass/book.cpp | 2 +- apps/openmw/mwclass/clothing.cpp | 2 +- apps/openmw/mwclass/container.cpp | 2 +- apps/openmw/mwclass/creature.cpp | 2 +- apps/openmw/mwclass/door.cpp | 2 +- apps/openmw/mwclass/ingredient.cpp | 2 +- apps/openmw/mwclass/light.cpp | 2 +- apps/openmw/mwclass/lockpick.cpp | 2 +- apps/openmw/mwclass/misc.cpp | 2 +- apps/openmw/mwclass/potion.cpp | 2 +- apps/openmw/mwclass/probe.cpp | 2 +- apps/openmw/mwclass/repair.cpp | 2 +- apps/openmw/mwclass/weapon.cpp | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index a0ee260260..01437b2abd 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -110,7 +110,7 @@ namespace MWClass text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index 10687171a0..1ff7ef5bd6 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -105,7 +105,7 @@ namespace MWClass text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 4006f21ce7..28bb1ff35c 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -265,7 +265,7 @@ namespace MWClass if (!info.enchant.empty()) info.remainingEnchantCharge = static_cast(ptr.getCellRef().getEnchantmentCharge()); - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 55de7a64ab..d731f56394 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -127,7 +127,7 @@ namespace MWClass info.enchant = ref->mBase->mEnchant; - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 0614c92eb9..32a0b62729 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -172,7 +172,7 @@ namespace MWClass if (!info.enchant.empty()) info.remainingEnchantCharge = static_cast(ptr.getCellRef().getEnchantmentCharge()); - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index a985fe6d71..28779f971f 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -271,7 +271,7 @@ namespace MWClass text += "\nYou can not use evidence chests"; } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 810a7840d4..bb9c1bc277 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -599,7 +599,7 @@ namespace MWClass std::string text; if (MWBase::Environment::get().getWindowManager()->getFullHelp()) text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index 695bea5f10..99acfcf4df 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -293,7 +293,7 @@ namespace MWClass text += MWGui::ToolTips::getCellRefString(ptr.getCellRef()); text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index 3e07a24610..5225170be7 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -145,7 +145,7 @@ namespace MWClass } info.effects = list; - info.text = text; + info.text = std::move(text); info.isIngredient = true; return info; diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index 92ba8e1512..6e34e3c2bd 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -177,7 +177,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 6c46f2e66f..d3c3d479e4 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -122,7 +122,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index fa91b607f2..0f26dfd2df 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -167,7 +167,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 5811ec10db..7cf0c54f5c 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -118,7 +118,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 7a6c00824d..96c94339bb 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -121,7 +121,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 0d38271aab..cf4a42be70 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -123,7 +123,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index 7e4c47993c..2c9a9b5c7a 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -243,7 +243,7 @@ namespace MWClass text += MWGui::ToolTips::getMiscString(ref->mBase->mScript.getRefIdString(), "Script"); } - info.text = text; + info.text = std::move(text); return info; } From 14942d7541732e5a77d2b834389b482ea99ccbde Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 2 Jan 2024 18:50:32 +0300 Subject: [PATCH 180/231] Shorten global map marker notes like vanilla (bug #7619) --- CHANGELOG.md | 1 + apps/openmw/mwgui/tooltips.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f16ac81b..14f2d3d5ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves Bug #7611: Beast races' idle animations slide after turning or jumping in place + Bug #7619: Long map notes may get cut off Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7636: Animations bug out when switching between 1st and 3rd person, while playing a scripted animation diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index bdc19a260d..9ee7d08f31 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -481,10 +481,13 @@ namespace MWGui MyGUI::IntCoord(padding.left + 8 + 4, totalSize.height + padding.top, 300 - padding.left - 8 - 4, 300 - totalSize.height), MyGUI::Align::Default); - edit->setEditMultiLine(true); - edit->setEditWordWrap(true); - edit->setCaption(note); - edit->setSize(edit->getWidth(), edit->getTextSize().height); + constexpr size_t maxLength = 60; + std::string shortenedNote = note.substr(0, std::min(maxLength, note.find('\n'))); + if (shortenedNote.size() < note.size()) + shortenedNote += " ..."; + edit->setCaption(shortenedNote); + MyGUI::IntSize noteTextSize = edit->getTextSize(); + edit->setSize(std::max(edit->getWidth(), noteTextSize.width), noteTextSize.height); icon->setPosition(icon->getLeft(), (edit->getTop() + edit->getBottom()) / 2 - icon->getHeight() / 2); totalSize.height += std::max(edit->getHeight(), icon->getHeight()); totalSize.width = std::max(totalSize.width, edit->getWidth() + 8 + 4); From 73104189843cbfccff5f71bf49b8ed03294baf53 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 2 Jan 2024 21:53:00 +0300 Subject: [PATCH 181/231] Downgrade Settings GUI mode to a modal (bug #6758) --- CHANGELOG.md | 1 + apps/openmw/mwbase/windowmanager.hpp | 2 ++ apps/openmw/mwgui/mainmenu.cpp | 5 ++++- apps/openmw/mwgui/mode.hpp | 1 - apps/openmw/mwgui/settingswindow.cpp | 6 ++++-- apps/openmw/mwgui/settingswindow.hpp | 2 +- apps/openmw/mwgui/windowmanagerimp.cpp | 5 ++++- apps/openmw/mwgui/windowmanagerimp.hpp | 1 + apps/openmw/mwinput/mousemanager.cpp | 4 +++- apps/openmw/mwlua/uibindings.cpp | 1 - 10 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eff35bfa53..1c5c021a0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Bug #6661: Saved games that have no preview screenshot cause issues or crashes Bug #6716: mwscript comparison operator handling is too restrictive Bug #6754: Beast to Non-beast transformation mod is not working on OpenMW + Bug #6758: Main menu background video can be stopped by opening the options menu Bug #6807: Ultimate Galleon is not working properly Bug #6893: Lua: Inconsistent behavior with actors affected by Disable and SetDelete commands Bug #6894: Added item combines with equipped stack instead of creating a new unequipped stack diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index 0c60fe9778..4711994b55 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -77,6 +77,7 @@ namespace MWGui class JailScreen; class MessageBox; class PostProcessorHud; + class SettingsWindow; enum ShowInDialogueMode { @@ -156,6 +157,7 @@ namespace MWBase virtual MWGui::ConfirmationDialog* getConfirmationDialog() = 0; virtual MWGui::TradeWindow* getTradeWindow() = 0; virtual MWGui::PostProcessorHud* getPostProcessorHud() = 0; + virtual MWGui::SettingsWindow* getSettingsWindow() = 0; /// Make the player use an item, while updating GUI state accordingly virtual void useItem(const MWWorld::Ptr& item, bool force = false) = 0; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index 37e835f1a4..d0c55f432e 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -18,6 +18,7 @@ #include "backgroundimage.hpp" #include "confirmationdialog.hpp" #include "savegamedialog.hpp" +#include "settingswindow.hpp" #include "videowidget.hpp" namespace MWGui @@ -97,7 +98,9 @@ namespace MWGui winMgr->removeGuiMode(GM_MainMenu); } else if (name == "options") - winMgr->pushGuiMode(GM_Settings); + { + winMgr->getSettingsWindow()->setVisible(true); + } else if (name == "credits") winMgr->playVideo("mw_credits.bik", true); else if (name == "exitgame") diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index 63f81e8b47..44f9743743 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -6,7 +6,6 @@ namespace MWGui enum GuiMode { GM_None, - GM_Settings, // Settings window GM_Inventory, // Inventory mode GM_Container, GM_Companion, diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 1060b3a20f..fbd54586df 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -240,7 +240,7 @@ namespace MWGui } SettingsWindow::SettingsWindow() - : WindowBase("openmw_settings_window.layout") + : WindowModal("openmw_settings_window.layout") , mKeyboardMode(true) , mCurrentPage(-1) { @@ -450,7 +450,7 @@ namespace MWGui void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) { - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Settings); + setVisible(false); } void SettingsWindow::onResolutionSelected(MyGUI::ListBox* _sender, size_t index) @@ -1041,6 +1041,8 @@ namespace MWGui void SettingsWindow::onOpen() { + WindowModal::onOpen(); + highlightCurrentResolution(); updateControlsBox(); updateLightSettings(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 1f96f7de54..47951ef121 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -7,7 +7,7 @@ namespace MWGui { - class SettingsWindow : public WindowBase + class SettingsWindow : public WindowModal { public: SettingsWindow(); diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 3bd779a186..63d974ff35 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -408,7 +408,6 @@ namespace MWGui mSettingsWindow = settingsWindow.get(); mWindows.push_back(std::move(settingsWindow)); trackWindow(mSettingsWindow, makeSettingsWindowSettingValues()); - mGuiModeStates[GM_Settings] = GuiModeState(mSettingsWindow); auto confirmationDialog = std::make_unique(); mConfirmationDialog = confirmationDialog.get(); @@ -1475,6 +1474,10 @@ namespace MWGui { return mPostProcessorHud; } + MWGui::SettingsWindow* WindowManager::getSettingsWindow() + { + return mSettingsWindow; + } void WindowManager::useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions) { diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 7ee0554a26..b378a76ff8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -182,6 +182,7 @@ namespace MWGui MWGui::ConfirmationDialog* getConfirmationDialog() override; MWGui::TradeWindow* getTradeWindow() override; MWGui::PostProcessorHud* getPostProcessorHud() override; + MWGui::SettingsWindow* getSettingsWindow() override; /// Make the player use an item, while updating GUI state accordingly void useItem(const MWWorld::Ptr& item, bool bypassBeastRestrictions = false) override; diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index 363a78fadd..c91dfaedac 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -13,6 +13,8 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwgui/settingswindow.hpp" + #include "../mwworld/player.hpp" #include "actions.hpp" @@ -156,7 +158,7 @@ namespace MWInput // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible // Also do not trigger bindings when input controls are disabled, e.g. during save loading - if (MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Settings + if (!MWBase::Environment::get().getWindowManager()->getSettingsWindow()->isVisible() && !input->controlsDisabled()) mBindingsManager->mousePressed(arg, id); } diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 98a653949d..843917275c 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -47,7 +47,6 @@ namespace MWLua } const std::unordered_map modeToName{ - { MWGui::GM_Settings, "SettingsMenu" }, { MWGui::GM_Inventory, "Interface" }, { MWGui::GM_Container, "Container" }, { MWGui::GM_Companion, "Companion" }, From 8d3efd27ba094a651b4d61221f151b88e839d9f9 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 3 Jan 2024 09:10:03 +0400 Subject: [PATCH 182/231] Add missing initialization --- apps/openmw/mwrender/pingpongcanvas.hpp | 2 +- components/nifosg/controller.cpp | 2 +- components/nifosg/controller.hpp | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index f7212a3f18..5a37b7fbc9 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -61,7 +61,7 @@ namespace MWRender bool mPostprocessing = false; fx::DispatchArray mPasses; - fx::FlagsType mMask; + fx::FlagsType mMask = 0; osg::ref_ptr mFallbackProgram; osg::ref_ptr mMultiviewResolveProgram; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 54e9e2bb16..7a93865aca 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -442,7 +442,7 @@ namespace NifOsg } } - MaterialColorController::MaterialColorController() {} + MaterialColorController::MaterialColorController() = default; MaterialColorController::MaterialColorController( const Nif::NiMaterialColorController* ctrl, const osg::Material* baseMaterial) diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index df6fdb2a24..51cf1cd428 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -347,7 +347,9 @@ namespace NifOsg private: Vec3Interpolator mData; - Nif::NiMaterialColorController::TargetColor mTargetColor; + Nif::NiMaterialColorController::TargetColor mTargetColor{ + Nif::NiMaterialColorController::TargetColor::Ambient + }; osg::ref_ptr mBaseMaterial; }; From 7ffb2bc3c40c3a2798cc6b649d9d81e666d039d4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 3 Jan 2024 20:34:44 +0400 Subject: [PATCH 183/231] Use error messages instead of unhandled exceptions --- apps/launcher/maindialog.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 177d4fe88c..5d558ef38f 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -118,13 +118,14 @@ Launcher::FirstRunDialogResult Launcher::MainDialog::showFirstRunDialog() const auto& userConfigDir = mCfgMgr.getUserConfigPath(); if (!exists(userConfigDir)) { - if (!create_directories(userConfigDir)) + std::error_code ec; + if (!create_directories(userConfigDir, ec)) { - cfgError(tr("Error opening OpenMW configuration file"), + cfgError(tr("Error creating OpenMW configuration directory: code %0").arg(ec.value()), tr("
Could not create directory %0

" - "Please make sure you have the right permissions " - "and try again.
") - .arg(Files::pathToQString(canonical(userConfigDir)))); + "%1
") + .arg(Files::pathToQString(userConfigDir)) + .arg(QString(ec.message().c_str()))); return FirstRunDialogResultFailure; } } @@ -457,13 +458,14 @@ bool Launcher::MainDialog::writeSettings() if (!exists(userPath)) { - if (!create_directories(userPath)) + std::error_code ec; + if (!create_directories(userPath, ec)) { - cfgError(tr("Error creating OpenMW configuration directory"), - tr("
Could not create %0

" - "Please make sure you have the right permissions " - "and try again.
") - .arg(Files::pathToQString(userPath))); + cfgError(tr("Error creating OpenMW configuration directory: code %0").arg(ec.value()), + tr("
Could not create directory %0

" + "%1
") + .arg(Files::pathToQString(userPath)) + .arg(QString(ec.message().c_str()))); return false; } } From 72fa4924dc9afa6974ae36808a482bad3d85a624 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 3 Jan 2024 22:55:00 +0100 Subject: [PATCH 184/231] Use settings values to declare enum settings --- apps/opencs/model/prefs/enumsetting.cpp | 78 ++++---------- apps/opencs/model/prefs/enumsetting.hpp | 36 ++----- apps/opencs/model/prefs/enumvalueview.hpp | 15 +++ apps/opencs/model/prefs/state.cpp | 120 +++++----------------- apps/opencs/model/prefs/state.hpp | 3 +- apps/opencs/model/prefs/values.hpp | 98 ++++++++---------- 6 files changed, 112 insertions(+), 238 deletions(-) create mode 100644 apps/opencs/model/prefs/enumvalueview.hpp diff --git a/apps/opencs/model/prefs/enumsetting.cpp b/apps/opencs/model/prefs/enumsetting.cpp index 938da874e1..aaa4c28c61 100644 --- a/apps/opencs/model/prefs/enumsetting.cpp +++ b/apps/opencs/model/prefs/enumsetting.cpp @@ -15,38 +15,10 @@ #include "category.hpp" #include "state.hpp" -CSMPrefs::EnumValue::EnumValue(const std::string& value, const std::string& tooltip) - : mValue(value) - , mTooltip(tooltip) -{ -} - -CSMPrefs::EnumValue::EnumValue(const char* value) - : mValue(value) -{ -} - -CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const EnumValues& values) -{ - mValues.insert(mValues.end(), values.mValues.begin(), values.mValues.end()); - return *this; -} - -CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const EnumValue& value) -{ - mValues.push_back(value); - return *this; -} - -CSMPrefs::EnumValues& CSMPrefs::EnumValues::add(const std::string& value, const std::string& tooltip) -{ - mValues.emplace_back(value, tooltip); - return *this; -} - -CSMPrefs::EnumSetting::EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) +CSMPrefs::EnumSetting::EnumSetting(Category* parent, QMutex* mutex, std::string_view key, const QString& label, + std::span values, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) + , mValues(values) , mWidget(nullptr) { } @@ -57,45 +29,28 @@ CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::setTooltip(const std::string& tool return *this; } -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValues(const EnumValues& values) -{ - mValues.add(values); - return *this; -} - -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const EnumValue& value) -{ - mValues.add(value); - return *this; -} - -CSMPrefs::EnumSetting& CSMPrefs::EnumSetting::addValue(const std::string& value, const std::string& tooltip) -{ - mValues.add(value, tooltip); - return *this; -} - CSMPrefs::SettingWidgets CSMPrefs::EnumSetting::makeWidgets(QWidget* parent) { QLabel* label = new QLabel(getLabel(), parent); mWidget = new QComboBox(parent); - size_t index = 0; - const std::string value = getValue(); - - for (size_t i = 0; i < mValues.mValues.size(); ++i) + for (std::size_t i = 0; i < mValues.size(); ++i) { - if (value == mValues.mValues[i].mValue) - index = i; + const EnumValueView& v = mValues[i]; - mWidget->addItem(QString::fromUtf8(mValues.mValues[i].mValue.c_str())); + mWidget->addItem(QString::fromUtf8(v.mValue.data(), static_cast(v.mValue.size()))); - if (!mValues.mValues[i].mTooltip.empty()) - mWidget->setItemData( - static_cast(i), QString::fromUtf8(mValues.mValues[i].mTooltip.c_str()), Qt::ToolTipRole); + if (!v.mTooltip.empty()) + mWidget->setItemData(static_cast(i), + QString::fromUtf8(v.mTooltip.data(), static_cast(v.mTooltip.size())), Qt::ToolTipRole); } + const std::string value = getValue(); + const std::size_t index = std::find_if(mValues.begin(), mValues.end(), [&](const EnumValueView& v) { + return v.mValue == value; + }) - mValues.begin(); + mWidget->setCurrentIndex(static_cast(index)); if (!mTooltip.empty()) @@ -117,6 +72,9 @@ void CSMPrefs::EnumSetting::updateWidget() void CSMPrefs::EnumSetting::valueChanged(int value) { - setValue(mValues.mValues.at(value).mValue); + if (value < 0 || static_cast(value) >= mValues.size()) + throw std::logic_error("Invalid enum setting \"" + getKey() + "\" value index: " + std::to_string(value)); + + setValue(std::string(mValues[value].mValue)); getParent()->getState()->update(*this); } diff --git a/apps/opencs/model/prefs/enumsetting.hpp b/apps/opencs/model/prefs/enumsetting.hpp index 51241d593f..00953f914e 100644 --- a/apps/opencs/model/prefs/enumsetting.hpp +++ b/apps/opencs/model/prefs/enumsetting.hpp @@ -1,10 +1,13 @@ #ifndef CSM_PREFS_ENUMSETTING_H #define CSM_PREFS_ENUMSETTING_H +#include #include +#include #include #include +#include "enumvalueview.hpp" #include "setting.hpp" class QComboBox; @@ -13,47 +16,20 @@ namespace CSMPrefs { class Category; - struct EnumValue - { - std::string mValue; - std::string mTooltip; - - EnumValue(const std::string& value, const std::string& tooltip = ""); - - EnumValue(const char* value); - }; - - struct EnumValues - { - std::vector mValues; - - EnumValues& add(const EnumValues& values); - - EnumValues& add(const EnumValue& value); - - EnumValues& add(const std::string& value, const std::string& tooltip); - }; - class EnumSetting final : public TypedSetting { Q_OBJECT std::string mTooltip; - EnumValues mValues; + std::span mValues; QComboBox* mWidget; public: - explicit EnumSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + explicit EnumSetting(Category* parent, QMutex* mutex, std::string_view key, const QString& label, + std::span values, Settings::Index& index); EnumSetting& setTooltip(const std::string& tooltip); - EnumSetting& addValues(const EnumValues& values); - - EnumSetting& addValue(const EnumValue& value); - - EnumSetting& addValue(const std::string& value, const std::string& tooltip); - /// Return label, input widget. SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/enumvalueview.hpp b/apps/opencs/model/prefs/enumvalueview.hpp new file mode 100644 index 0000000000..f46d250a81 --- /dev/null +++ b/apps/opencs/model/prefs/enumvalueview.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_ENUMVALUEVIEW_H +#define OPENMW_APPS_OPENCS_MODEL_PREFS_ENUMVALUEVIEW_H + +#include + +namespace CSMPrefs +{ + struct EnumValueView + { + std::string_view mValue; + std::string_view mTooltip; + }; +} + +#endif diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index c78a70b63e..b05a4b7473 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -59,13 +59,7 @@ void CSMPrefs::State::declare() declareInt(mValues->mWindows.mMinimumWidth, "Minimum subview width") .setTooltip("Minimum width of subviews.") .setRange(50, 10000); - EnumValue scrollbarOnly("Scrollbar Only", - "Simple addition of scrollbars, the view window " - "does not grow automatically."); - declareEnum("mainwindow-scrollbar", "Horizontal scrollbar mode for main window.", scrollbarOnly) - .addValue(scrollbarOnly) - .addValue("Grow Only", "The view window grows as subviews are added. No scrollbars.") - .addValue("Grow then Scroll", "The view window grows. The scrollbar appears once it cannot grow any further."); + declareEnum(mValues->mWindows.mMainwindowScrollbar, "Horizontal scrollbar mode for main window."); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) declareBool(mValues->mWindows.mGrowLimit, "Grow Limit Screen") .setTooltip( @@ -75,34 +69,15 @@ void CSMPrefs::State::declare() #endif declareCategory("Records"); - EnumValue iconAndText("Icon and Text"); - EnumValues recordValues; - recordValues.add(iconAndText).add("Icon Only").add("Text Only"); - declareEnum("status-format", "Modification status display format", iconAndText).addValues(recordValues); - declareEnum("type-format", "ID type display format", iconAndText).addValues(recordValues); + declareEnum(mValues->mRecords.mStatusFormat, "Modification status display format"); + declareEnum(mValues->mRecords.mTypeFormat, "ID type display format"); declareCategory("ID Tables"); - EnumValue inPlaceEdit("Edit in Place", "Edit the clicked cell"); - EnumValue editRecord("Edit Record", "Open a dialogue subview for the clicked record"); - EnumValue view("View", "Open a scene subview for the clicked record (not available everywhere)"); - EnumValue editRecordAndClose("Edit Record and Close"); - EnumValues doubleClickValues; - doubleClickValues.add(inPlaceEdit) - .add(editRecord) - .add(view) - .add("Revert") - .add("Delete") - .add(editRecordAndClose) - .add("View and Close", "Open a scene subview for the clicked record and close the table subview"); - declareEnum("double", "Double Click", inPlaceEdit).addValues(doubleClickValues); - declareEnum("double-s", "Shift Double Click", editRecord).addValues(doubleClickValues); - declareEnum("double-c", "Control Double Click", view).addValues(doubleClickValues); - declareEnum("double-sc", "Shift Control Double Click", editRecordAndClose).addValues(doubleClickValues); - EnumValue jumpAndSelect("Jump and Select", "Scroll new record into view and make it the selection"); - declareEnum("jump-to-added", "Action on adding or cloning a record", jumpAndSelect) - .addValue(jumpAndSelect) - .addValue("Jump Only", "Scroll new record into view") - .addValue("No Jump", "No special action"); + declareEnum(mValues->mIdTables.mDouble, "Double Click"); + declareEnum(mValues->mIdTables.mDoubleS, "Shift Double Click"); + declareEnum(mValues->mIdTables.mDoubleC, "Control Double Click"); + declareEnum(mValues->mIdTables.mDoubleSc, "Shift Control Double Click"); + declareEnum(mValues->mIdTables.mJumpToAdded, "Action on adding or cloning a record"); declareBool( mValues->mIdTables.mExtendedConfig, "Manually specify affected record types for an extended delete/revert") .setTooltip( @@ -120,18 +95,10 @@ void CSMPrefs::State::declare() declareBool(mValues->mIdDialogues.mToolbar, "Show toolbar"); declareCategory("Reports"); - EnumValue actionNone("None"); - EnumValue actionEdit("Edit", "Open a table or dialogue suitable for addressing the listed report"); - EnumValue actionRemove("Remove", "Remove the report from the report table"); - EnumValue actionEditAndRemove("Edit And Remove", - "Open a table or dialogue suitable for addressing the listed report, then remove the report from the report " - "table"); - EnumValues reportValues; - reportValues.add(actionNone).add(actionEdit).add(actionRemove).add(actionEditAndRemove); - declareEnum("double", "Double Click", actionEdit).addValues(reportValues); - declareEnum("double-s", "Shift Double Click", actionRemove).addValues(reportValues); - declareEnum("double-c", "Control Double Click", actionEditAndRemove).addValues(reportValues); - declareEnum("double-sc", "Shift Control Double Click", actionNone).addValues(reportValues); + declareEnum(mValues->mReports.mDouble, "Double Click"); + declareEnum(mValues->mReports.mDoubleS, "Shift Double Click"); + declareEnum(mValues->mReports.mDoubleC, "Control Double Click"); + declareEnum(mValues->mReports.mDoubleSc, "Shift Control Double Click"); declareBool(mValues->mReports.mIgnoreBaseRecords, "Ignore base records in verifier"); declareCategory("Search & Replace"); @@ -152,11 +119,7 @@ void CSMPrefs::State::declare() declareInt(mValues->mScripts.mTabWidth, "Tab Width") .setTooltip("Number of characters for tab width") .setRange(1, 10); - EnumValue warningsNormal("Normal", "Report warnings as warning"); - declareEnum("warnings", "Warning Mode", warningsNormal) - .addValue("Ignore", "Do not report warning") - .addValue(warningsNormal) - .addValue("Strict", "Promote warning to an error"); + declareEnum(mValues->mScripts.mWarnings, "Warning Mode"); declareBool(mValues->mScripts.mToolbar, "Show toolbar"); declareInt(mValues->mScripts.mCompileDelay, "Delay between updating of source errors") .setTooltip("Delay in milliseconds") @@ -244,32 +207,6 @@ void CSMPrefs::State::declare() declareBool(mValues->mTooltips.mSceneHideBasic, "Hide basic 3D scenes tooltips"); declareInt(mValues->mTooltips.mSceneDelay, "Tooltip delay in milliseconds").setMin(1); - EnumValue createAndInsert("Create cell and insert"); - EnumValue showAndInsert("Show cell and insert"); - EnumValue dontInsert("Discard"); - EnumValue insertAnyway("Insert anyway"); - EnumValues insertOutsideCell; - insertOutsideCell.add(createAndInsert).add(dontInsert).add(insertAnyway); - EnumValues insertOutsideVisibleCell; - insertOutsideVisibleCell.add(showAndInsert).add(dontInsert).add(insertAnyway); - - EnumValue createAndLandEdit("Create cell and land, then edit"); - EnumValue showAndLandEdit("Show cell and edit"); - EnumValue dontLandEdit("Discard"); - EnumValues landeditOutsideCell; - landeditOutsideCell.add(createAndLandEdit).add(dontLandEdit); - EnumValues landeditOutsideVisibleCell; - landeditOutsideVisibleCell.add(showAndLandEdit).add(dontLandEdit); - - EnumValue SelectOnly("Select only"); - EnumValue SelectAdd("Add to selection"); - EnumValue SelectRemove("Remove from selection"); - EnumValue selectInvert("Invert selection"); - EnumValues primarySelectAction; - primarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert); - EnumValues secondarySelectAction; - secondarySelectAction.add(SelectOnly).add(SelectAdd).add(SelectRemove).add(selectInvert); - declareCategory("3D Scene Editing"); declareDouble(mValues->mSceneEditing.mGridsnapMovement, "Grid snap size"); declareDouble(mValues->mSceneEditing.mGridsnapRotation, "Angle snap size"); @@ -278,15 +215,13 @@ void CSMPrefs::State::declare() .setTooltip( "If an instance drop can not be placed against another object at the " "insert point, it will be placed by this distance from the insert point instead"); - declareEnum("outside-drop", "Handling drops outside of cells", createAndInsert).addValues(insertOutsideCell); - declareEnum("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert) - .addValues(insertOutsideVisibleCell); - declareEnum("outside-landedit", "Handling terrain edit outside of cells", createAndLandEdit) - .setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record.") - .addValues(landeditOutsideCell); - declareEnum("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit) - .setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible.") - .addValues(landeditOutsideVisibleCell); + declareEnum(mValues->mSceneEditing.mOutsideDrop, "Handling drops outside of cells"); + declareEnum(mValues->mSceneEditing.mOutsideVisibleDrop, "Handling drops outside of visible cells"); + declareEnum(mValues->mSceneEditing.mOutsideLandedit, "Handling terrain edit outside of cells") + .setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record."); + declareEnum(mValues->mSceneEditing.mOutsideVisibleLandedit, "Handling terrain edit outside of visible cells") + .setTooltip( + "Behavior of terrain editing, if land editing brush reaches an area that is not currently visible."); declareInt(mValues->mSceneEditing.mTexturebrushMaximumsize, "Maximum texture brush size").setMin(1); declareInt(mValues->mSceneEditing.mShapebrushMaximumsize, "Maximum height edit brush size") .setTooltip("Setting for the slider range of brush size in terrain height editing.") @@ -303,16 +238,14 @@ void CSMPrefs::State::declare() .setTooltip( "When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); - declareEnum("primary-select-action", "Action for primary select", SelectOnly) + declareEnum(mValues->mSceneEditing.mPrimarySelectAction, "Action for primary select") .setTooltip( "Selection can be chosen between select only, add to selection, remove from selection and invert " - "selection.") - .addValues(primarySelectAction); - declareEnum("secondary-select-action", "Action for secondary select", SelectAdd) + "selection."); + declareEnum(mValues->mSceneEditing.mSecondarySelectAction, "Action for secondary select") .setTooltip( "Selection can be chosen between select only, add to selection, remove from selection and invert " - "selection.") - .addValues(secondarySelectAction); + "selection."); declareCategory("Key Bindings"); @@ -531,12 +464,13 @@ CSMPrefs::BoolSetting& CSMPrefs::State::declareBool(Settings::SettingValue return *setting; } -CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(const std::string& key, const QString& label, EnumValue default_) +CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(EnumSettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); - CSMPrefs::EnumSetting* setting = new CSMPrefs::EnumSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + CSMPrefs::EnumSetting* setting = new CSMPrefs::EnumSetting( + &mCurrentCategory->second, &mMutex, value.getValue().mName, label, value.getEnumValues(), *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index eaf829de7e..f3e580ea5a 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -32,6 +32,7 @@ namespace CSMPrefs class ModifierSetting; class Setting; class StringSetting; + class EnumSettingValue; struct Values; /// \brief User settings state @@ -69,7 +70,7 @@ namespace CSMPrefs BoolSetting& declareBool(Settings::SettingValue& value, const QString& label); - EnumSetting& declareEnum(const std::string& key, const QString& label, EnumValue default_); + EnumSetting& declareEnum(EnumSettingValue& value, const QString& label); ColourSetting& declareColour(const std::string& key, const QString& label, QColor default_); diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 80d2f8f182..474d0e1fb9 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H #define OPENMW_APPS_OPENCS_MODEL_PREFS_VALUES_H +#include "enumvalueview.hpp" + #include #include @@ -15,12 +17,6 @@ namespace CSMPrefs { - struct EnumValueView - { - std::string_view mValue; - std::string_view mTooltip; - }; - class EnumSanitizer final : public Settings::Sanitizer { public: @@ -51,6 +47,26 @@ namespace CSMPrefs return std::make_unique(values); } + class EnumSettingValue + { + public: + explicit EnumSettingValue(Settings::Index& index, std::string_view category, std::string_view name, + std::span values, std::size_t defaultValueIndex) + : mValue( + index, category, name, std::string(values[defaultValueIndex].mValue), makeEnumSanitizerString(values)) + , mEnumValues(values) + { + } + + Settings::SettingValue& getValue() { return mValue; } + + std::span getEnumValues() const { return mEnumValues; } + + private: + Settings::SettingValue mValue; + std::span mEnumValues; + }; + struct WindowsCategory : Settings::WithIndex { using Settings::WithIndex::WithIndex; @@ -72,8 +88,7 @@ namespace CSMPrefs Settings::SettingValue mMaxSubviews{ mIndex, sName, "max-subviews", 256 }; Settings::SettingValue mHideSubview{ mIndex, sName, "hide-subview", false }; Settings::SettingValue mMinimumWidth{ mIndex, sName, "minimum-width", 325 }; - Settings::SettingValue mMainwindowScrollbar{ mIndex, sName, "mainwindow-scrollbar", - std::string(sMainwindowScrollbarValues[0].mValue), makeEnumSanitizerString(sMainwindowScrollbarValues) }; + EnumSettingValue mMainwindowScrollbar{ mIndex, sName, "mainwindow-scrollbar", sMainwindowScrollbarValues, 0 }; Settings::SettingValue mGrowLimit{ mIndex, sName, "grow-limit", false }; }; @@ -89,10 +104,8 @@ namespace CSMPrefs EnumValueView{ "Text Only", "" }, }; - Settings::SettingValue mStatusFormat{ mIndex, sName, "status-format", - std::string(sRecordValues[0].mValue), makeEnumSanitizerString(sRecordValues) }; - Settings::SettingValue mTypeFormat{ mIndex, sName, "type-format", - std::string(sRecordValues[0].mValue), makeEnumSanitizerString(sRecordValues) }; + EnumSettingValue mStatusFormat{ mIndex, sName, "status-format", sRecordValues, 0 }; + EnumSettingValue mTypeFormat{ mIndex, sName, "type-format", sRecordValues, 0 }; }; struct IdTablesCategory : Settings::WithIndex @@ -118,16 +131,11 @@ namespace CSMPrefs EnumValueView{ "No Jump", "No special action" }, }; - Settings::SettingValue mDouble{ mIndex, sName, "double", std::string(sDoubleClickValues[0].mValue), - makeEnumSanitizerString(sDoubleClickValues) }; - Settings::SettingValue mDoubleS{ mIndex, sName, "double-s", - std::string(sDoubleClickValues[1].mValue), makeEnumSanitizerString(sDoubleClickValues) }; - Settings::SettingValue mDoubleC{ mIndex, sName, "double-c", - std::string(sDoubleClickValues[2].mValue), makeEnumSanitizerString(sDoubleClickValues) }; - Settings::SettingValue mDoubleSc{ mIndex, sName, "double-sc", - std::string(sDoubleClickValues[5].mValue), makeEnumSanitizerString(sDoubleClickValues) }; - Settings::SettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", - std::string(sJumpAndSelectValues[0].mValue), makeEnumSanitizerString(sJumpAndSelectValues) }; + EnumSettingValue mDouble{ mIndex, sName, "double", sDoubleClickValues, 0 }; + EnumSettingValue mDoubleS{ mIndex, sName, "double-s", sDoubleClickValues, 1 }; + EnumSettingValue mDoubleC{ mIndex, sName, "double-c", sDoubleClickValues, 2 }; + EnumSettingValue mDoubleSc{ mIndex, sName, "double-sc", sDoubleClickValues, 5 }; + EnumSettingValue mJumpToAdded{ mIndex, sName, "jump-to-added", sJumpAndSelectValues, 0 }; Settings::SettingValue mExtendedConfig{ mIndex, sName, "extended-config", false }; Settings::SettingValue mSubviewNewWindow{ mIndex, sName, "subview-new-window", false }; }; @@ -156,14 +164,10 @@ namespace CSMPrefs "report table" }, }; - Settings::SettingValue mDouble{ mIndex, sName, "double", std::string(sReportValues[1].mValue), - makeEnumSanitizerString(sReportValues) }; - Settings::SettingValue mDoubleS{ mIndex, sName, "double-s", std::string(sReportValues[2].mValue), - makeEnumSanitizerString(sReportValues) }; - Settings::SettingValue mDoubleC{ mIndex, sName, "double-c", std::string(sReportValues[3].mValue), - makeEnumSanitizerString(sReportValues) }; - Settings::SettingValue mDoubleSc{ mIndex, sName, "double-sc", std::string(sReportValues[0].mValue), - makeEnumSanitizerString(sReportValues) }; + EnumSettingValue mDouble{ mIndex, sName, "double", sReportValues, 1 }; + EnumSettingValue mDoubleS{ mIndex, sName, "double-s", sReportValues, 2 }; + EnumSettingValue mDoubleC{ mIndex, sName, "double-c", sReportValues, 3 }; + EnumSettingValue mDoubleSc{ mIndex, sName, "double-sc", sReportValues, 0 }; Settings::SettingValue mIgnoreBaseRecords{ mIndex, sName, "ignore-base-records", false }; }; @@ -194,8 +198,7 @@ namespace CSMPrefs Settings::SettingValue mWrapLines{ mIndex, sName, "wrap-lines", false }; Settings::SettingValue mMonoFont{ mIndex, sName, "mono-font", true }; Settings::SettingValue mTabWidth{ mIndex, sName, "tab-width", 4 }; - Settings::SettingValue mWarnings{ mIndex, sName, "warnings", std::string(sWarningValues[1].mValue), - makeEnumSanitizerString(sWarningValues) }; + EnumSettingValue mWarnings{ mIndex, sName, "warnings", sWarningValues, 1 }; Settings::SettingValue mToolbar{ mIndex, sName, "toolbar", true }; Settings::SettingValue mCompileDelay{ mIndex, sName, "compile-delay", 100 }; Settings::SettingValue mErrorHeight{ mIndex, sName, "error-height", 100 }; @@ -310,14 +313,7 @@ namespace CSMPrefs EnumValueView{ "Discard", "" }, }; - static constexpr std::array sPrimarySelectAction{ - EnumValueView{ "Select only", "" }, - EnumValueView{ "Add to selection", "" }, - EnumValueView{ "Remove from selection", "" }, - EnumValueView{ "Invert selection", "" }, - }; - - static constexpr std::array sSecondarySelectAction{ + static constexpr std::array sSelectAction{ EnumValueView{ "Select only", "" }, EnumValueView{ "Add to selection", "" }, EnumValueView{ "Remove from selection", "" }, @@ -328,16 +324,12 @@ namespace CSMPrefs Settings::SettingValue mGridsnapRotation{ mIndex, sName, "gridsnap-rotation", 15 }; Settings::SettingValue mGridsnapScale{ mIndex, sName, "gridsnap-scale", 0.25 }; Settings::SettingValue mDistance{ mIndex, sName, "distance", 50 }; - Settings::SettingValue mOutsideDrop{ mIndex, sName, "outside-drop", - std::string(sInsertOutsideCellValues[0].mValue), makeEnumSanitizerString(sInsertOutsideCellValues) }; - Settings::SettingValue mOutsideVisibleDrop{ mIndex, sName, "outside-visible-drop", - std::string(sInsertOutsideVisibleCellValues[0].mValue), - makeEnumSanitizerString(sInsertOutsideVisibleCellValues) }; - Settings::SettingValue mOutsideLandedit{ mIndex, sName, "outside-landedit", - std::string(sLandEditOutsideCellValues[0].mValue), makeEnumSanitizerString(sLandEditOutsideCellValues) }; - Settings::SettingValue mOutsideVisibleLandedit{ mIndex, sName, "outside-visible-landedit", - std::string(sLandEditOutsideVisibleCellValues[0].mValue), - makeEnumSanitizerString(sLandEditOutsideVisibleCellValues) }; + EnumSettingValue mOutsideDrop{ mIndex, sName, "outside-drop", sInsertOutsideCellValues, 0 }; + EnumSettingValue mOutsideVisibleDrop{ mIndex, sName, "outside-visible-drop", sInsertOutsideVisibleCellValues, + 0 }; + EnumSettingValue mOutsideLandedit{ mIndex, sName, "outside-landedit", sLandEditOutsideCellValues, 0 }; + EnumSettingValue mOutsideVisibleLandedit{ mIndex, sName, "outside-visible-landedit", + sLandEditOutsideVisibleCellValues, 0 }; Settings::SettingValue mTexturebrushMaximumsize{ mIndex, sName, "texturebrush-maximumsize", 50 }; Settings::SettingValue mShapebrushMaximumsize{ mIndex, sName, "shapebrush-maximumsize", 100 }; Settings::SettingValue mLandeditPostSmoothpainting{ mIndex, sName, "landedit-post-smoothpainting", @@ -345,10 +337,8 @@ namespace CSMPrefs Settings::SettingValue mLandeditPostSmoothstrength{ mIndex, sName, "landedit-post-smoothstrength", 0.25 }; Settings::SettingValue mOpenListView{ mIndex, sName, "open-list-view", false }; - Settings::SettingValue mPrimarySelectAction{ mIndex, sName, "primary-select-action", - std::string(sPrimarySelectAction[0].mValue), makeEnumSanitizerString(sPrimarySelectAction) }; - Settings::SettingValue mSecondarySelectAction{ mIndex, sName, "secondary-select-action", - std::string(sPrimarySelectAction[1].mValue), makeEnumSanitizerString(sPrimarySelectAction) }; + EnumSettingValue mPrimarySelectAction{ mIndex, sName, "primary-select-action", sSelectAction, 0 }; + EnumSettingValue mSecondarySelectAction{ mIndex, sName, "secondary-select-action", sSelectAction, 1 }; }; struct KeyBindingsCategory : Settings::WithIndex From 3ba03782c0c4241241feb2bed6c0cabd6439b56a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 4 Jan 2024 00:50:35 +0300 Subject: [PATCH 185/231] Silence OSG shininess limit warnings --- components/nifosg/nifloader.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5234e700f2..2f7574d68b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2599,7 +2599,10 @@ namespace NifOsg emissiveMult = matprop->mEmissiveMult; mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->mSpecular, 1.f)); - mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->mGlossiness); + // NIFs may provide specular exponents way above OpenGL's limit. + // They can't be used properly, but we don't need OSG to constantly harass us about it. + float glossiness = std::clamp(matprop->mGlossiness, 0.f, 128.f); + mat->setShininess(osg::Material::FRONT_AND_BACK, glossiness); if (!matprop->mController.empty()) { @@ -2714,7 +2717,8 @@ namespace NifOsg mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha); mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f)); mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f)); - mat->setShininess(osg::Material::FRONT_AND_BACK, shaderprop->mGlossiness); + float glossiness = std::clamp(shaderprop->mGlossiness, 0.f, 128.f); + mat->setShininess(osg::Material::FRONT_AND_BACK, glossiness); emissiveMult = shaderprop->mEmissiveMult; specStrength = shaderprop->mSpecStrength; specEnabled = shaderprop->specular(); From 2c45d316fb05a2ad74ef6da94ac028cc50e8b33d Mon Sep 17 00:00:00 2001 From: uramer Date: Fri, 5 Jan 2024 11:21:12 +0000 Subject: [PATCH 186/231] Missing newlines in MWUI documentation --- files/data/scripts/omw/mwui/init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/files/data/scripts/omw/mwui/init.lua b/files/data/scripts/omw/mwui/init.lua index 560d8af704..5ab2d3cb23 100644 --- a/files/data/scripts/omw/mwui/init.lua +++ b/files/data/scripts/omw/mwui/init.lua @@ -77,6 +77,7 @@ require('scripts.omw.mwui.space')(templates) --- -- Same as box, but with a semi-transparent background -- @field [parent=#Templates] openmw.ui#Template boxTransparent + --- -- Same as box, but with a solid background -- @field [parent=#Templates] openmw.ui#Template boxSolid @@ -100,6 +101,7 @@ require('scripts.omw.mwui.space')(templates) --- -- Same as box, but with a semi-transparent background -- @field [parent=#Templates] openmw.ui#Template boxTransparentThick + --- -- Same as box, but with a solid background -- @field [parent=#Templates] openmw.ui#Template boxSolidThick From 8e17aff6a67b6422205568fbee3ec3703d2fc6d5 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 5 Jan 2024 22:17:46 +0100 Subject: [PATCH 187/231] Fix MagicSchoolData documentation --- files/lua_api/openmw/core.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index f42f51b9b3..890532fc13 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -871,6 +871,7 @@ -- @field #MagicSchoolData school Optional magic school -- @field #string attribute The id of the skill's governing attribute +--- -- @type MagicSchoolData -- @field #string name Human-readable name -- @field #string areaSound VFS path to the area sound From 594bd6e136a9d04ce6c395e00aa4f6ef119d86e3 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Jan 2024 02:30:10 +0100 Subject: [PATCH 188/231] Use walking speed for swimming actor with water walking for pathfinding This will make them find shorter paths nearby shores. --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aipackage.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf0743fab..0dbd4e463f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,6 +121,7 @@ Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves Bug #7742: Governing attribute training limit should use the modified attribute + Bug #7758: Water walking is not taken into account to compute path cost on the water Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index bdf96983ae..0eec5f195a 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -492,8 +492,6 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P const DetourNavigator::Flags flags = getNavigatorFlags(actor); const MWWorld::Class& actorClass = actor.getClass(); - const float swimSpeed = (flags & DetourNavigator::Flag_swim) == 0 ? 0.0f : actorClass.getSwimSpeed(actor); - const float walkSpeed = [&] { if ((flags & DetourNavigator::Flag_walk) == 0) return 0.0f; @@ -502,6 +500,14 @@ DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::P return actorClass.getRunSpeed(actor); }(); + const float swimSpeed = [&] { + if ((flags & DetourNavigator::Flag_swim) == 0) + return 0.0f; + if (hasWaterWalking(actor)) + return walkSpeed; + return actorClass.getSwimSpeed(actor); + }(); + const float maxSpeed = std::max(swimSpeed, walkSpeed); if (maxSpeed == 0) From 903299ce50ed52222b45212c131e3010bcac2b37 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 6 Jan 2024 02:33:57 +0100 Subject: [PATCH 189/231] Avoid recomputing navigator flags when getting area costs --- apps/openmw/mwmechanics/aicombat.cpp | 4 ++-- apps/openmw/mwmechanics/aipackage.cpp | 8 +++++--- apps/openmw/mwmechanics/aipackage.hpp | 2 +- apps/openmw/mwmechanics/aiwander.cpp | 8 +++++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 674a660ff6..12072df2ac 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -283,8 +283,8 @@ namespace MWMechanics const MWBase::World* world = MWBase::Environment::get().getWorld(); // Try to build path to the target. const auto agentBounds = world->getPathfindingAgentBounds(actor); - const auto navigatorFlags = getNavigatorFlags(actor); - const auto areaCosts = getAreaCosts(actor); + const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor); + const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags); const ESM::Pathgrid* pathgrid = world->getStore().get().search(*actor.getCell()->getCell()); const auto& pathGridGraph = getPathGridGraph(pathgrid); mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, agentBounds, diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 0eec5f195a..a265c70cf4 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -157,8 +157,10 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& { const ESM::Pathgrid* pathgrid = world->getStore().get().search(*actor.getCell()->getCell()); + const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor); + const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags); mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(pathgrid), - agentBounds, getNavigatorFlags(actor), getAreaCosts(actor), endTolerance, pathType); + agentBounds, navigatorFlags, areaCosts, endTolerance, pathType); mRotateOnTheRunChecks = 3; // give priority to go directly on target if there is minimal opportunity @@ -486,10 +488,10 @@ DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld:: return result; } -DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts(const MWWorld::Ptr& actor) const +DetourNavigator::AreaCosts MWMechanics::AiPackage::getAreaCosts( + const MWWorld::Ptr& actor, DetourNavigator::Flags flags) const { DetourNavigator::AreaCosts costs; - const DetourNavigator::Flags flags = getNavigatorFlags(actor); const MWWorld::Class& actorClass = actor.getClass(); const float walkSpeed = [&] { diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index fa018609e4..9e13ee9cd5 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -150,7 +150,7 @@ namespace MWMechanics DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const; - DetourNavigator::AreaCosts getAreaCosts(const MWWorld::Ptr& actor) const; + DetourNavigator::AreaCosts getAreaCosts(const MWWorld::Ptr& actor, DetourNavigator::Flags flags) const; const AiPackageTypeId mTypeId; const Options mOptions; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 30aad2e89a..be2601dc37 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -224,8 +224,10 @@ namespace MWMechanics { const auto agentBounds = MWBase::Environment::get().getWorld()->getPathfindingAgentBounds(actor); constexpr float endTolerance = 0; + const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor); + const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags); mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(), getPathGridGraph(pathgrid), - agentBounds, getNavigatorFlags(actor), getAreaCosts(actor), endTolerance, PathType::Full); + agentBounds, navigatorFlags, areaCosts, endTolerance, PathType::Full); } if (mPathFinder.isPathConstructed()) @@ -367,8 +369,8 @@ namespace MWMechanics const auto world = MWBase::Environment::get().getWorld(); const auto agentBounds = world->getPathfindingAgentBounds(actor); const auto navigator = world->getNavigator(); - const auto navigatorFlags = getNavigatorFlags(actor); - const auto areaCosts = getAreaCosts(actor); + const DetourNavigator::Flags navigatorFlags = getNavigatorFlags(actor); + const DetourNavigator::AreaCosts areaCosts = getAreaCosts(actor, navigatorFlags); auto& prng = MWBase::Environment::get().getWorld()->getPrng(); do From c93b6dca0ae6ee607a9c1fae5106a0ac24cef9ea Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 5 Jan 2024 19:36:02 -0600 Subject: [PATCH 190/231] Fix(CS): Add record type to selection groups to fix #7759 --- apps/opencs/model/world/data.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/opencs/model/world/data.cpp b/apps/opencs/model/world/data.cpp index ea4c8966d8..6322a77e66 100644 --- a/apps/opencs/model/world/data.cpp +++ b/apps/opencs/model/world/data.cpp @@ -622,6 +622,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data mSelectionGroups.addColumn(new StringIdColumn); mSelectionGroups.addColumn(new RecordStateColumn); + mSelectionGroups.addColumn(new FixedRecordTypeColumn(UniversalId::Type_SelectionGroup)); mSelectionGroups.addColumn(new SelectionGroupColumn); mMetaData.appendBlankRecord(ESM::RefId::stringRefId("sys::meta")); From 3ff1bae372a67a9b18131e670e9c3ce0410b8f12 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 5 Jan 2024 19:36:25 -0600 Subject: [PATCH 191/231] Cleanup(CS): More consistent names for selection group configs --- apps/opencs/model/prefs/values.hpp | 42 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/opencs/model/prefs/values.hpp b/apps/opencs/model/prefs/values.hpp index 80d2f8f182..7abe00d023 100644 --- a/apps/opencs/model/prefs/values.hpp +++ b/apps/opencs/model/prefs/values.hpp @@ -479,27 +479,27 @@ namespace CSMPrefs Settings::SettingValue mSceneRenderStats{ mIndex, sName, "scene-render-stats", "F3" }; Settings::SettingValue mSceneClearSelection{ mIndex, sName, "scene-clear-selection", "Space" }; Settings::SettingValue mSceneUnhideAll{ mIndex, sName, "scene-unhide-all", "Alt+H" }; - Settings::SettingValue mSceneToggleHidden{ mIndex, sName, "scene-toggle-visibility", "H" }; - Settings::SettingValue mSceneSelectGroup1{ mIndex, sName, "scene-group-1", "1" }; - Settings::SettingValue mSceneSaveGroup1{ mIndex, sName, "scene-save-1", "Ctrl+1" }; - Settings::SettingValue mSceneSelectGroup2{ mIndex, sName, "scene-group-2", "2" }; - Settings::SettingValue mSceneSaveGroup2{ mIndex, sName, "scene-save-2", "Ctrl+2" }; - Settings::SettingValue mSceneSelectGroup3{ mIndex, sName, "scene-group-3", "3" }; - Settings::SettingValue mSceneSaveGroup3{ mIndex, sName, "scene-save-3", "Ctrl+3" }; - Settings::SettingValue mSceneSelectGroup4{ mIndex, sName, "scene-group-4", "4" }; - Settings::SettingValue mSceneSaveGroup4{ mIndex, sName, "scene-save-4", "Ctrl+4" }; - Settings::SettingValue mSceneSelectGroup5{ mIndex, sName, "scene-group-5", "5" }; - Settings::SettingValue mSceneSaveGroup5{ mIndex, sName, "scene-save-5", "Ctrl+5" }; - Settings::SettingValue mSceneSelectGroup6{ mIndex, sName, "scene-group-6", "6" }; - Settings::SettingValue mSceneSaveGroup6{ mIndex, sName, "scene-save-6", "Ctrl+6" }; - Settings::SettingValue mSceneSelectGroup7{ mIndex, sName, "scene-group-7", "7" }; - Settings::SettingValue mSceneSaveGroup7{ mIndex, sName, "scene-save-7", "Ctrl+7" }; - Settings::SettingValue mSceneSelectGroup8{ mIndex, sName, "scene-group-8", "8" }; - Settings::SettingValue mSceneSaveGroup8{ mIndex, sName, "scene-save-8", "Ctrl+8" }; - Settings::SettingValue mSceneSelectGroup9{ mIndex, sName, "scene-group-9", "9" }; - Settings::SettingValue mSceneSaveGroup9{ mIndex, sName, "scene-save-9", "Ctrl+9" }; - Settings::SettingValue mSceneSelectGroup10{ mIndex, sName, "scene-group-0", "0" }; - Settings::SettingValue mSceneSaveGroup10{ mIndex, sName, "scene-save-0", "Ctrl+0" }; + Settings::SettingValue mSceneToggleVisibility{ mIndex, sName, "scene-toggle-visibility", "H" }; + Settings::SettingValue mSceneGroup0{ mIndex, sName, "scene-group-0", "0" }; + Settings::SettingValue mSceneSave0{ mIndex, sName, "scene-save-0", "Ctrl+0" }; + Settings::SettingValue mSceneGroup1{ mIndex, sName, "scene-group-1", "1" }; + Settings::SettingValue mSceneSave1{ mIndex, sName, "scene-save-1", "Ctrl+1" }; + Settings::SettingValue mSceneGroup2{ mIndex, sName, "scene-group-2", "2" }; + Settings::SettingValue mSceneSave2{ mIndex, sName, "scene-save-2", "Ctrl+2" }; + Settings::SettingValue mSceneGroup3{ mIndex, sName, "scene-group-3", "3" }; + Settings::SettingValue mSceneSave3{ mIndex, sName, "scene-save-3", "Ctrl+3" }; + Settings::SettingValue mSceneGroup4{ mIndex, sName, "scene-group-4", "4" }; + Settings::SettingValue mSceneSave4{ mIndex, sName, "scene-save-4", "Ctrl+4" }; + Settings::SettingValue mSceneGroup5{ mIndex, sName, "scene-group-5", "5" }; + Settings::SettingValue mSceneSave5{ mIndex, sName, "scene-save-5", "Ctrl+5" }; + Settings::SettingValue mSceneGroup6{ mIndex, sName, "scene-group-6", "6" }; + Settings::SettingValue mSceneSave6{ mIndex, sName, "scene-save-6", "Ctrl+6" }; + Settings::SettingValue mSceneGroup7{ mIndex, sName, "scene-group-7", "7" }; + Settings::SettingValue mSceneSave7{ mIndex, sName, "scene-save-7", "Ctrl+7" }; + Settings::SettingValue mSceneGroup8{ mIndex, sName, "scene-group-8", "8" }; + Settings::SettingValue mSceneSave8{ mIndex, sName, "scene-save-8", "Ctrl+8" }; + Settings::SettingValue mSceneGroup9{ mIndex, sName, "scene-group-9", "9" }; + Settings::SettingValue mSceneSave9{ mIndex, sName, "scene-save-9", "Ctrl+9" }; Settings::SettingValue mFreeForward{ mIndex, sName, "free-forward", "W" }; Settings::SettingValue mFreeBackward{ mIndex, sName, "free-backward", "S" }; Settings::SettingValue mFreeLeft{ mIndex, sName, "free-left", "A" }; From c563219b6187a6661ff00a4fa08d119e4496b6cc Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Fri, 5 Jan 2024 19:36:51 -0600 Subject: [PATCH 192/231] Cleanup(CS): Pass const ref when applicable for selection groups --- apps/opencs/view/render/cell.cpp | 2 +- apps/opencs/view/render/cell.hpp | 2 +- apps/opencs/view/render/object.cpp | 2 +- apps/opencs/view/render/object.hpp | 2 +- apps/opencs/view/render/pagedworldspacewidget.cpp | 2 +- apps/opencs/view/render/pagedworldspacewidget.hpp | 2 +- apps/opencs/view/render/unpagedworldspacewidget.cpp | 2 +- apps/opencs/view/render/unpagedworldspacewidget.hpp | 2 +- apps/opencs/view/render/worldspacewidget.hpp | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index b0c8425ad3..f2c851cc72 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -612,7 +612,7 @@ osg::ref_ptr CSVRender::Cell::getSnapTarget(unsigned int ele return result; } -void CSVRender::Cell::selectFromGroup(const std::vector group) +void CSVRender::Cell::selectFromGroup(const std::vector& group) { for (const auto& [_, object] : mObjects) { diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 52e01c631a..5bfce47904 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -148,7 +148,7 @@ namespace CSVRender // already selected void selectAllWithSameParentId(int elementMask); - void selectFromGroup(const std::vector group); + void selectFromGroup(const std::vector& group); void unhideAll(); diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index 0e114ac06e..4ad1aaca15 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -484,7 +484,7 @@ CSVRender::Object::~Object() mParentNode->removeChild(mRootNode); } -void CSVRender::Object::setSelected(bool selected, osg::Vec4f color) +void CSVRender::Object::setSelected(bool selected, const osg::Vec4f& color) { mSelected = selected; diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 9b18b99561..5c73b12211 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -139,7 +139,7 @@ namespace CSVRender ~Object(); /// Mark the object as selected, selected objects show an outline effect - void setSelected(bool selected, osg::Vec4f color = osg::Vec4f(1, 1, 1, 1)); + void setSelected(bool selected, const osg::Vec4f& color = osg::Vec4f(1, 1, 1, 1)); bool getSelected() const; diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index fab706e3ca..3d5c6fe565 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -875,7 +875,7 @@ std::vector> CSVRender::PagedWorldspaceWidget:: return result; } -void CSVRender::PagedWorldspaceWidget::selectGroup(std::vector group) const +void CSVRender::PagedWorldspaceWidget::selectGroup(const std::vector& group) const { for (const auto& [_, cell] : mCells) cell->selectFromGroup(group); diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 3d2ab97e89..744cc7ccb9 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -163,7 +163,7 @@ namespace CSVRender std::vector> getSelection(unsigned int elementMask) const override; - void selectGroup(const std::vector group) const override; + void selectGroup(const std::vector& group) const override; void unhideAll() const override; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index ea99294c28..899918c3b9 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -199,7 +199,7 @@ std::vector> CSVRender::UnpagedWorldspaceWidget return mCell->getSelection(elementMask); } -void CSVRender::UnpagedWorldspaceWidget::selectGroup(const std::vector group) const +void CSVRender::UnpagedWorldspaceWidget::selectGroup(const std::vector& group) const { mCell->selectFromGroup(group); } diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 22a0475fe2..89c916415d 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -93,7 +93,7 @@ namespace CSVRender std::vector> getSelection(unsigned int elementMask) const override; - void selectGroup(const std::vector group) const override; + void selectGroup(const std::vector& group) const override; void unhideAll() const override; diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index a6d87440f1..505d985ffa 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -201,7 +201,7 @@ namespace CSVRender virtual std::vector> getSelection(unsigned int elementMask) const = 0; - virtual void selectGroup(const std::vector) const = 0; + virtual void selectGroup(const std::vector&) const = 0; virtual void unhideAll() const = 0; From 74a6c81d53beca47794969bef862f4c510fbaad4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 6 Jan 2024 14:14:29 +0100 Subject: [PATCH 193/231] Make ActorActiveEffects:getEffect return an empty value and strip expired effects from __pairs --- apps/openmw/mwlua/magicbindings.cpp | 14 ++++++++------ files/lua_api/openmw/types.lua | 6 +++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/magicbindings.cpp b/apps/openmw/mwlua/magicbindings.cpp index 0a34c008a7..3d57ab24fc 100644 --- a/apps/openmw/mwlua/magicbindings.cpp +++ b/apps/openmw/mwlua/magicbindings.cpp @@ -769,8 +769,13 @@ namespace MWLua sol::state_view lua(ts); self.reset(); return sol::as_function([lua, self]() mutable -> std::pair { - if (!self.isEnd()) + while (!self.isEnd()) { + if (self.mIterator->second.getBase() == 0 && self.mIterator->second.getModifier() == 0.f) + { + self.advance(); + continue; + } ActiveEffect effect = ActiveEffect{ self.mIterator->first, self.mIterator->second }; auto result = sol::make_object(lua, effect); @@ -778,10 +783,7 @@ namespace MWLua self.advance(); return { key, result }; } - else - { - return { sol::lua_nil, sol::lua_nil }; - } + return { sol::lua_nil, sol::lua_nil }; }); }; @@ -823,7 +825,7 @@ namespace MWLua if (auto* store = effects.getStore()) if (auto effect = store->get(key)) return ActiveEffect{ key, effect.value() }; - return sol::nullopt; + return ActiveEffect{ key, MWMechanics::EffectParam() }; }; // types.Actor.activeEffects(o):removeEffect(id, ?arg) diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index a350d4dbea..ad30994fe8 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -210,14 +210,14 @@ -- end -- @usage -- Check for a specific effect -- local effect = Actor.activeEffects(self):getEffect(core.magic.EFFECT_TYPE.Telekinesis) --- if effect then +-- if effect.magnitude ~= 0 then -- print(effect.id..', attribute='..tostring(effect.affectedAttribute)..', skill='..tostring(effect.affectedSkill)..', magnitude='..tostring(effect.magnitude)) -- else -- print('No Telekinesis effect') -- end -- @usage -- Check for a specific effect targeting a specific attribute. -- local effect = Actor.activeEffects(self):getEffect(core.magic.EFFECT_TYPE.FortifyAttribute, core.ATTRIBUTE.Luck) --- if effect then +-- if effect.magnitude ~= 0 then -- print(effect.id..', attribute='..tostring(effect.affectedAttribute)..', skill='..tostring(effect.affectedSkill)..', magnitude='..tostring(effect.magnitude)) -- else -- print('No Fortify Luck effect') @@ -229,7 +229,7 @@ -- @param self -- @param #string effectId effect ID -- @param #string extraParam Optional skill or attribute ID --- @return openmw.core#ActiveEffect if such an effect is active, nil otherwise +-- @return openmw.core#ActiveEffect --- -- Completely removes the active effect from the actor. From 72c382aca610a1f52d390aed260f6b93f1d0f4c9 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 6 Jan 2024 14:23:08 +0100 Subject: [PATCH 194/231] Don't crash when clicking the logo video --- apps/openmw/mwgui/windowbase.cpp | 2 +- apps/openmw/mwgui/windowbase.hpp | 2 +- apps/openmw/mwinput/mousemanager.cpp | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 4c191eaeb8..a680e38cf8 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -58,7 +58,7 @@ void WindowBase::setVisible(bool visible) onClose(); } -bool WindowBase::isVisible() +bool WindowBase::isVisible() const { return mMainWidget->getVisible(); } diff --git a/apps/openmw/mwgui/windowbase.hpp b/apps/openmw/mwgui/windowbase.hpp index 88b46b0bd2..54fb269305 100644 --- a/apps/openmw/mwgui/windowbase.hpp +++ b/apps/openmw/mwgui/windowbase.hpp @@ -37,7 +37,7 @@ namespace MWGui /// Sets the visibility of the window void setVisible(bool visible) override; /// Returns the visibility state of the window - bool isVisible(); + bool isVisible() const; void center(); diff --git a/apps/openmw/mwinput/mousemanager.cpp b/apps/openmw/mwinput/mousemanager.cpp index c91dfaedac..55b50b91ae 100644 --- a/apps/openmw/mwinput/mousemanager.cpp +++ b/apps/openmw/mwinput/mousemanager.cpp @@ -158,8 +158,9 @@ namespace MWInput // Don't trigger any mouse bindings while in settings menu, otherwise rebinding controls becomes impossible // Also do not trigger bindings when input controls are disabled, e.g. during save loading - if (!MWBase::Environment::get().getWindowManager()->getSettingsWindow()->isVisible() - && !input->controlsDisabled()) + const MWGui::SettingsWindow* settingsWindow + = MWBase::Environment::get().getWindowManager()->getSettingsWindow(); + if ((!settingsWindow || !settingsWindow->isVisible()) && !input->controlsDisabled()) mBindingsManager->mousePressed(arg, id); } From a11ff46e82f9b161b041dae504a6b0a9690363cf Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 6 Jan 2024 14:59:22 +0100 Subject: [PATCH 195/231] Drop support for save game format 4 --- CHANGELOG.md | 2 +- components/esm3/aisequence.cpp | 4 ++-- components/esm3/formatversion.hpp | 2 +- components/esm3/player.cpp | 18 +++++++----------- components/esm3/player.hpp | 3 +-- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bf0743fab..1438abb513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,7 +131,6 @@ Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds - Feature #6624: Drop support for old saves Feature #6726: Lua API for creating new objects Feature #6864: Lua file access API Feature #6922: Improve launcher appearance @@ -163,6 +162,7 @@ Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore Feature #7709: Improve resolution selection in Launcher Task #5896: Do not use deprecated MyGUI properties + Task #6624: Drop support for saves made prior to 0.45 Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector Task #7151: Do not use std::strerror to get errno error message diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index bbd42c400e..99c85db1bb 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -29,7 +29,7 @@ namespace ESM void AiTravel::load(ESMReader& esm) { esm.getHNT("DATA", mData.mX, mData.mY, mData.mZ); - esm.getHNOT(mHidden, "HIDD"); + esm.getHNT(mHidden, "HIDD"); mRepeat = false; esm.getHNOT(mRepeat, "REPT"); } @@ -258,7 +258,7 @@ namespace ESM } } - esm.getHNOT(mLastAiPackage, "LAST"); + esm.getHNT(mLastAiPackage, "LAST"); if (count > 1 && esm.getFormatVersion() <= MaxOldAiPackageFormatVersion) { diff --git a/components/esm3/formatversion.hpp b/components/esm3/formatversion.hpp index 1489926fbd..9f499a7231 100644 --- a/components/esm3/formatversion.hpp +++ b/components/esm3/formatversion.hpp @@ -27,7 +27,7 @@ namespace ESM inline constexpr FormatVersion MaxOldCountFormatVersion = 30; inline constexpr FormatVersion CurrentSaveGameFormatVersion = 31; - inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 4; + inline constexpr FormatVersion MinSupportedSaveGameFormatVersion = 5; inline constexpr FormatVersion OpenMW0_48SaveGameFormatVersion = 21; inline constexpr FormatVersion OpenMW0_49SaveGameFormatVersion = CurrentSaveGameFormatVersion; } diff --git a/components/esm3/player.cpp b/components/esm3/player.cpp index aa6b59abb9..fd280bf12e 100644 --- a/components/esm3/player.cpp +++ b/components/esm3/player.cpp @@ -30,16 +30,12 @@ namespace ESM mPaidCrimeId = -1; esm.getHNOT(mPaidCrimeId, "PAYD"); - bool checkPrevItems = true; - while (checkPrevItems) + while (esm.peekNextSub("BOUN")) { - ESM::RefId boundItemId = esm.getHNORefId("BOUN"); - ESM::RefId prevItemId = esm.getHNORefId("PREV"); + ESM::RefId boundItemId = esm.getHNRefId("BOUN"); + ESM::RefId prevItemId = esm.getHNRefId("PREV"); - if (!boundItemId.empty()) - mPreviousItems[boundItemId] = prevItemId; - else - checkPrevItems = false; + mPreviousItems[boundItemId] = prevItemId; } if (esm.getFormatVersion() <= MaxOldSkillsAndAttributesFormatVersion) @@ -103,10 +99,10 @@ namespace ESM esm.writeHNT("CURD", mCurrentCrimeId); esm.writeHNT("PAYD", mPaidCrimeId); - for (PreviousItems::const_iterator it = mPreviousItems.begin(); it != mPreviousItems.end(); ++it) + for (const auto& [bound, prev] : mPreviousItems) { - esm.writeHNRefId("BOUN", it->first); - esm.writeHNRefId("PREV", it->second); + esm.writeHNRefId("BOUN", bound); + esm.writeHNRefId("PREV", prev); } esm.writeHNT("WWAT", mSaveAttributes); diff --git a/components/esm3/player.hpp b/components/esm3/player.hpp index 7f9309765c..0f76a3b5eb 100644 --- a/components/esm3/player.hpp +++ b/components/esm3/player.hpp @@ -33,8 +33,7 @@ namespace ESM float mSaveAttributes[Attribute::Length]; float mSaveSkills[Skill::Length]; - typedef std::map PreviousItems; // previous equipped items, needed for bound spells - PreviousItems mPreviousItems; + std::map mPreviousItems; // previous equipped items, needed for bound spells void load(ESMReader& esm); void save(ESMWriter& esm) const; From 6d37618301efa572017bd2b1ebf7b019b6b483f0 Mon Sep 17 00:00:00 2001 From: AnyOldName3 Date: Sat, 6 Jan 2024 16:56:52 +0000 Subject: [PATCH 196/231] Add OpenMW-CS RC file to app rather than static lib Static libraries on Windows can't have embedded resources, so this mean the icon for the CS wasn't used. This could have also been resolved by explicitly requesting the library type as OBJECT rather than letting it default to STATIC (as object libraries aren't a thing on-disk and are just an abstraction in CMake so you can use the same object files in different targets), but this seemed less invasive. I also made it Win32-only as a Windows .rc file is meaningless on Unix, but it shouldn't be MSVC-only as MinGW can consume them. --- apps/opencs/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 70efd06090..610c5157aa 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -1,5 +1,4 @@ set (OPENCS_SRC - ${CMAKE_SOURCE_DIR}/files/windows/opencs.rc ) opencs_units (. editor) @@ -146,6 +145,9 @@ source_group (openmw-cs FILES main.cpp ${OPENCS_SRC} ${OPENCS_HDR}) if(WIN32) set(QT_USE_QTMAIN TRUE) + set(OPENCS_RC_FILE ${CMAKE_SOURCE_DIR}/files/windows/opencs.rc) +else(WIN32) + set(OPENCS_RC_FILE "") endif(WIN32) if (QT_VERSION_MAJOR VERSION_EQUAL 5) @@ -186,6 +188,7 @@ if(BUILD_OPENCS) ${OPENCS_CFG} ${OPENCS_DEFAULT_FILTERS_FILE} ${OPENCS_OPENMW_CFG} + ${OPENCS_RC_FILE} main.cpp ) From 6dc09b1cda50d0f31587d2cc3e6d0a5fe37c3ef1 Mon Sep 17 00:00:00 2001 From: psi29a Date: Sun, 7 Jan 2024 01:12:50 +0000 Subject: [PATCH 197/231] Update file before_install.osx.sh --- CI/before_install.osx.sh | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index 40210428d0..e340d501ed 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -1,22 +1,18 @@ #!/bin/sh -ex export HOMEBREW_NO_EMOJI=1 +export HOMEBREW_NO_INSTALL_CLEANUP=1 +export HOMEBREW_AUTOREMOVE=1 -brew uninstall --ignore-dependencies python@3.8 || true -brew uninstall --ignore-dependencies python@3.9 || true -brew uninstall --ignore-dependencies qt@6 || true -brew uninstall --ignore-dependencies jpeg || true +# workaround for gitlab's pre-installed brew +# purge large and unnecessary packages that get in our way and have caused issues +brew uninstall ruby php openjdk node postgresql maven curl || true brew tap --repair brew update --quiet # Some of these tools can come from places other than brew, so check before installing -brew reinstall xquartz fontconfig freetype harfbuzz brotli - -# Fix: can't open file: @loader_path/libbrotlicommon.1.dylib (No such file or directory) -BREW_LIB_PATH="$(brew --prefix)/lib" -install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlidec.1.dylib -install_name_tool -change "@loader_path/libbrotlicommon.1.dylib" "${BREW_LIB_PATH}/libbrotlicommon.1.dylib" ${BREW_LIB_PATH}/libbrotlienc.1.dylib +brew install curl xquartz gd fontconfig freetype harfbuzz brotli command -v ccache >/dev/null 2>&1 || brew install ccache command -v cmake >/dev/null 2>&1 || brew install cmake From c1c774e11df123032d94ee5306e49b5dedaf980c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 02:05:07 +0300 Subject: [PATCH 198/231] Update the spells window when constant effects are added/removed (bug #7475) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/activespells.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..83baf27872 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ Bug #7450: Evading obstacles does not work for actors missing certain animations Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously Bug #7472: Crash when enchanting last projectiles + Bug #7475: Equipping a constant effect item doesn't update the magic menu Bug #7502: Data directories dialog (0.48.0) forces adding subdirectory instead of intended directory Bug #7505: Distant terrain does not support sample size greater than cell size Bug #7553: Faction reaction loading is incorrect diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index 727bf95ed0..d8e409d9e2 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -228,6 +228,7 @@ namespace MWMechanics mSpells.emplace_back(ActiveSpellParams{ spell, ptr }); } + bool updateSpellWindow = false; if (ptr.getClass().hasInventoryStore(ptr) && !(creatureStats.isDead() && !creatureStats.isDeathAnimationFinished())) { @@ -264,6 +265,7 @@ namespace MWMechanics for (const auto& effect : params.mEffects) MWMechanics::playEffects( ptr, *world->getStore().get().find(effect.mEffectId), playNonLooping); + updateSpellWindow = true; } } } @@ -365,6 +367,7 @@ namespace MWMechanics for (const auto& effect : params.mEffects) onMagicEffectRemoved(ptr, params, effect); applyPurges(ptr, &spellIt); + updateSpellWindow = true; continue; } ++spellIt; @@ -377,6 +380,14 @@ namespace MWMechanics if (creatureStats.getMagicEffects().getOrDefault(effect).getMagnitude() > 0.f) creatureStats.getAiSequence().stopCombat(); } + + if (ptr == player && updateSpellWindow) + { + // Something happened with the spell list -- possibly while the game is paused, + // so we want to make the spell window get the memo. + // We don't normally want to do this, so this targets constant enchantments. + MWBase::Environment::get().getWindowManager()->updateSpellWindow(); + } } void ActiveSpells::addToSpells(const MWWorld::Ptr& ptr, const ActiveSpellParams& spell) From 18982ea4a0a5aaff2b52e09089d488d947748f19 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 5 Jan 2024 04:22:31 +0300 Subject: [PATCH 199/231] Read FO76 plugin header --- components/esm4/loadtes4.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/esm4/loadtes4.cpp b/components/esm4/loadtes4.cpp index a2fbe8b139..0cbf91c52e 100644 --- a/components/esm4/loadtes4.cpp +++ b/components/esm4/loadtes4.cpp @@ -100,6 +100,7 @@ void ESM4::Header::load(ESM4::Reader& reader) case ESM4::SUB_OFST: // Oblivion only? case ESM4::SUB_DELE: // Oblivion only? case ESM4::SUB_TNAM: // Fallout 4 (CK only) + case ESM::fourCC("MMSB"): // Fallout 76 reader.skipSubRecordData(); break; default: From f9825328d2e9e80978735b81209bfa5afa3b5cf2 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 5 Jan 2024 17:22:39 +0300 Subject: [PATCH 200/231] Bring ESM4 texture set reading up-to-date with FO76 --- components/esm4/loadtxst.cpp | 25 ++++++++++++++++++++++--- components/esm4/loadtxst.hpp | 7 ++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/components/esm4/loadtxst.cpp b/components/esm4/loadtxst.cpp index 3f1aebafb4..3b5f04f265 100644 --- a/components/esm4/loadtxst.cpp +++ b/components/esm4/loadtxst.cpp @@ -44,6 +44,9 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM::fourCC("FLTR"): // FO76 + reader.getZString(mFilter); + break; case ESM4::SUB_TX00: reader.getZString(mDiffuse); break; @@ -51,29 +54,45 @@ void ESM4::TextureSet::load(ESM4::Reader& reader) reader.getZString(mNormalMap); break; case ESM4::SUB_TX02: + // This is a "wrinkle map" in FO4/76 reader.getZString(mEnvMask); break; case ESM4::SUB_TX03: + // This is a glow map in FO4/76 reader.getZString(mToneMap); break; case ESM4::SUB_TX04: + // This is a height map in FO4/76 reader.getZString(mDetailMap); break; case ESM4::SUB_TX05: reader.getZString(mEnvMap); break; case ESM4::SUB_TX06: - reader.getZString(mUnknown); + reader.getZString(mMultiLayer); break; case ESM4::SUB_TX07: + // This is a "smooth specular" map in FO4/76 reader.getZString(mSpecular); break; + case ESM::fourCC("TX08"): // FO76 + reader.getZString(mSpecular); + break; + case ESM::fourCC("TX09"): // FO76 + reader.getZString(mLighting); + break; + case ESM::fourCC("TX10"): // FO76 + reader.getZString(mFlow); + break; + case ESM4::SUB_DNAM: + reader.get(mDataFlags); + break; case ESM4::SUB_MNAM: reader.getZString(mMaterial); break; - case ESM4::SUB_DNAM: - case ESM4::SUB_DODT: + case ESM4::SUB_DODT: // Decal data case ESM4::SUB_OBND: // object bounds + case ESM::fourCC("OPDS"): // Object placement defaults, FO76 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadtxst.hpp b/components/esm4/loadtxst.hpp index 0b55f37f8c..8e628df841 100644 --- a/components/esm4/loadtxst.hpp +++ b/components/esm4/loadtxst.hpp @@ -44,6 +44,7 @@ namespace ESM4 std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; + std::string mFilter; std::string mDiffuse; // includes alpha info std::string mNormalMap; // includes specular info (alpha channel) @@ -51,8 +52,12 @@ namespace ESM4 std::string mToneMap; std::string mDetailMap; std::string mEnvMap; - std::string mUnknown; + std::string mMultiLayer; std::string mSpecular; + std::string mSmoothSpecular; + std::string mLighting; + std::string mFlow; + std::uint16_t mDataFlags; std::string mMaterial; void load(ESM4::Reader& reader); From 0b63fafc6dc77b24d7eacf30813da3a94ae8c91f Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 5 Jan 2024 18:21:59 +0300 Subject: [PATCH 201/231] Bring ESM4 global variable reading up-to-date with FO76 --- components/esm4/loadglob.cpp | 11 ++++------- components/esm4/loadglob.hpp | 1 + 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/components/esm4/loadglob.cpp b/components/esm4/loadglob.cpp index 39593a4a7d..436f3e34ae 100644 --- a/components/esm4/loadglob.cpp +++ b/components/esm4/loadglob.cpp @@ -44,19 +44,16 @@ void ESM4::GlobalVariable::load(ESM4::Reader& reader) case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM::fourCC("XALG"): // FO76 + reader.get(mExtraFlags2); + break; case ESM4::SUB_FNAM: reader.get(mType); break; case ESM4::SUB_FLTV: reader.get(mValue); break; - case ESM4::SUB_FULL: - case ESM4::SUB_MODL: - case ESM4::SUB_MODB: - case ESM4::SUB_ICON: - case ESM4::SUB_DATA: - case ESM4::SUB_OBND: // TES5 - case ESM4::SUB_VMAD: // TES5 + case ESM::fourCC("NTWK"): // FO76 reader.skipSubRecordData(); break; default: diff --git a/components/esm4/loadglob.hpp b/components/esm4/loadglob.hpp index c9c83f58b4..89959bcab1 100644 --- a/components/esm4/loadglob.hpp +++ b/components/esm4/loadglob.hpp @@ -42,6 +42,7 @@ namespace ESM4 { ESM::FormId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + std::uint64_t mExtraFlags2; std::string mEditorId; From bd2ea715b422aab0904036e8b6ad094eece8a9ea Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 5 Jan 2024 18:27:33 +0300 Subject: [PATCH 202/231] Bring ESM4 head part reading up-to-date with FO76 --- components/esm4/loadhdpt.cpp | 21 +++++++++++++++++---- components/esm4/loadhdpt.hpp | 5 ++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/components/esm4/loadhdpt.cpp b/components/esm4/loadhdpt.cpp index 250a687042..c560ff5fac 100644 --- a/components/esm4/loadhdpt.cpp +++ b/components/esm4/loadhdpt.cpp @@ -48,6 +48,9 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) case ESM4::SUB_EDID: reader.getZString(mEditorId); break; + case ESM::fourCC("XALG"): // FO76 + reader.get(mExtraFlags2); + break; case ESM4::SUB_FULL: reader.getLocalizedString(mFullName); break; @@ -58,7 +61,7 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) reader.getZString(mModel); break; case ESM4::SUB_HNAM: - reader.getFormId(mAdditionalPart); + reader.getFormId(mExtraParts.emplace_back()); break; case ESM4::SUB_NAM0: // TES5 { @@ -87,15 +90,25 @@ void ESM4::HeadPart::load(ESM4::Reader& reader) case ESM4::SUB_TNAM: reader.getFormId(mBaseTexture); break; + case ESM4::SUB_CNAM: + reader.getFormId(mColor); + break; + case ESM4::SUB_RNAM: + reader.getFormId(mValidRaces.emplace_back()); + break; case ESM4::SUB_PNAM: reader.get(mType); break; case ESM4::SUB_MODT: // Model data case ESM4::SUB_MODC: case ESM4::SUB_MODS: - case ESM4::SUB_MODF: // Model data end - case ESM4::SUB_RNAM: - case ESM4::SUB_CNAM: + case ESM4::SUB_MODF: + case ESM::fourCC("ENLM"): + case ESM::fourCC("XFLG"): + case ESM::fourCC("ENLT"): + case ESM::fourCC("ENLS"): + case ESM::fourCC("AUUV"): + case ESM::fourCC("MODD"): // Model data end case ESM4::SUB_CTDA: reader.skipSubRecordData(); break; diff --git a/components/esm4/loadhdpt.hpp b/components/esm4/loadhdpt.hpp index aca3b0ca7b..5d17720100 100644 --- a/components/esm4/loadhdpt.hpp +++ b/components/esm4/loadhdpt.hpp @@ -43,6 +43,7 @@ namespace ESM4 { ESM::FormId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details + std::uint64_t mExtraFlags2; std::string mEditorId; std::string mFullName; @@ -70,10 +71,12 @@ namespace ESM4 Type_Eyelashes = 13, }; - ESM::FormId mAdditionalPart; + std::vector mExtraParts; std::array mTriFile; ESM::FormId mBaseTexture; + ESM::FormId mColor; + std::vector mValidRaces; void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; From ed31a0354ab08b9d051744413026a2012692972c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 18:35:51 +0300 Subject: [PATCH 203/231] Support playing ambient and rain weather SFX at the same time (bug #7761) --- CHANGELOG.md | 1 + apps/openmw/mwrender/skyutil.hpp | 1 + apps/openmw/mwworld/weather.cpp | 59 +++++++++++++++++++++++--------- apps/openmw/mwworld/weather.hpp | 10 ++++-- 4 files changed, 52 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..411ff28c2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -122,6 +122,7 @@ Bug #7724: Guards don't help vs werewolves Bug #7742: Governing attribute training limit should use the modified attribute Bug #7758: Water walking is not taken into account to compute path cost on the water + Bug #7761: Rain and ambient loop sounds are mutually exclusive Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwrender/skyutil.hpp b/apps/openmw/mwrender/skyutil.hpp index 1018724595..da038e6c58 100644 --- a/apps/openmw/mwrender/skyutil.hpp +++ b/apps/openmw/mwrender/skyutil.hpp @@ -65,6 +65,7 @@ namespace MWRender bool mIsStorm; ESM::RefId mAmbientLoopSoundID; + ESM::RefId mRainLoopSoundID; float mAmbientSoundVolume; std::string mParticleEffect; diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index aa75730b40..2c9b80bc5f 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -195,21 +195,19 @@ namespace MWWorld mThunderSoundID[3] = ESM::RefId::stringRefId(Fallback::Map::getString("Weather_" + name + "_Thunder_Sound_ID_3")); - // TODO: support weathers that have both "Ambient Loop Sound ID" and "Rain Loop Sound ID", need to play both - // sounds at the same time. - if (!mRainEffect.empty()) // NOTE: in vanilla, the weathers with rain seem to be hardcoded; changing // Using_Precip has no effect { - mAmbientLoopSoundID + mRainLoopSoundID = ESM::RefId::stringRefId(Fallback::Map::getString("Weather_" + name + "_Rain_Loop_Sound_ID")); - if (mAmbientLoopSoundID.empty()) // default to "rain" if not set - mAmbientLoopSoundID = ESM::RefId::stringRefId("rain"); + if (mRainLoopSoundID.empty()) // default to "rain" if not set + mRainLoopSoundID = ESM::RefId::stringRefId("rain"); + else if (mRainLoopSoundID == "None") + mRainLoopSoundID = ESM::RefId(); } - else - mAmbientLoopSoundID - = ESM::RefId::stringRefId(Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID")); + mAmbientLoopSoundID + = ESM::RefId::stringRefId(Fallback::Map::getString("Weather_" + name + "_Ambient_Loop_Sound_ID")); if (mAmbientLoopSoundID == "None") mAmbientLoopSoundID = ESM::RefId(); } @@ -552,8 +550,6 @@ namespace MWWorld , mQueuedWeather(0) , mRegions() , mResult() - , mAmbientSound(nullptr) - , mPlayingSoundID() { mTimeSettings.mNightStart = mSunsetTime + mSunsetDuration; mTimeSettings.mNightEnd = mSunriseTime; @@ -794,24 +790,52 @@ namespace MWWorld mRendering.getSkyManager()->setWeather(mResult); // Play sounds - if (mPlayingSoundID != mResult.mAmbientLoopSoundID) + if (mPlayingAmbientSoundID != mResult.mAmbientLoopSoundID) { - stopSounds(); + if (mAmbientSound) + { + MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); + mAmbientSound = nullptr; + } if (!mResult.mAmbientLoopSoundID.empty()) mAmbientSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mAmbientLoopSoundID, mResult.mAmbientSoundVolume, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::Loop); - mPlayingSoundID = mResult.mAmbientLoopSoundID; + mPlayingAmbientSoundID = mResult.mAmbientLoopSoundID; } else if (mAmbientSound) mAmbientSound->setVolume(mResult.mAmbientSoundVolume); + + if (mPlayingRainSoundID != mResult.mRainLoopSoundID) + { + if (mRainSound) + { + MWBase::Environment::get().getSoundManager()->stopSound(mRainSound); + mRainSound = nullptr; + } + if (!mResult.mRainLoopSoundID.empty()) + mRainSound = MWBase::Environment::get().getSoundManager()->playSound(mResult.mRainLoopSoundID, + mResult.mAmbientSoundVolume, 1.0, MWSound::Type::Sfx, MWSound::PlayMode::Loop); + mPlayingRainSoundID = mResult.mRainLoopSoundID; + } + else if (mRainSound) + mRainSound->setVolume(mResult.mAmbientSoundVolume); } void WeatherManager::stopSounds() { if (mAmbientSound) + { MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); - mAmbientSound = nullptr; - mPlayingSoundID = ESM::RefId(); + mAmbientSound = nullptr; + } + mPlayingAmbientSoundID = ESM::RefId(); + + if (mRainSound) + { + MWBase::Environment::get().getSoundManager()->stopSound(mRainSound); + mRainSound = nullptr; + } + mPlayingRainSoundID = ESM::RefId(); } float WeatherManager::getWindSpeed() const @@ -1118,6 +1142,7 @@ namespace MWWorld mResult.mCloudSpeed = current.mCloudSpeed; mResult.mGlareView = current.mGlareView; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mRainLoopSoundID = current.mRainLoopSoundID; mResult.mAmbientSoundVolume = 1.f; mResult.mPrecipitationAlpha = 1.f; @@ -1237,6 +1262,7 @@ namespace MWWorld mResult.mAmbientSoundVolume = 1.f - factor / threshold; mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; mResult.mAmbientLoopSoundID = current.mAmbientLoopSoundID; + mResult.mRainLoopSoundID = current.mRainLoopSoundID; mResult.mRainDiameter = current.mRainDiameter; mResult.mRainMinHeight = current.mRainMinHeight; mResult.mRainMaxHeight = current.mRainMaxHeight; @@ -1252,6 +1278,7 @@ namespace MWWorld mResult.mAmbientSoundVolume = (factor - threshold) / (1 - threshold); mResult.mPrecipitationAlpha = mResult.mAmbientSoundVolume; mResult.mAmbientLoopSoundID = other.mAmbientLoopSoundID; + mResult.mRainLoopSoundID = other.mRainLoopSoundID; mResult.mRainDiameter = other.mRainDiameter; mResult.mRainMinHeight = other.mRainMinHeight; diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 65f926c096..327136a859 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -156,9 +156,11 @@ namespace MWWorld float FogOffset; } mDL; - // Sound effect + // Sound effects // This is used for Blight, Ashstorm and Blizzard (Bloodmoon) ESM::RefId mAmbientLoopSoundID; + // This is used for Rain and Thunderstorm + ESM::RefId mRainLoopSoundID; // Is this an ash storm / blight storm? If so, the following will happen: // - The particles and clouds will be oriented so they appear to come from the Red Mountain. @@ -369,8 +371,10 @@ namespace MWWorld std::map mRegions; MWRender::WeatherResult mResult; - MWBase::Sound* mAmbientSound; - ESM::RefId mPlayingSoundID; + MWBase::Sound* mAmbientSound{ nullptr }; + ESM::RefId mPlayingAmbientSoundID; + MWBase::Sound* mRainSound{ nullptr }; + ESM::RefId mPlayingRainSoundID; void addWeather( const std::string& name, float dlFactor, float dlOffset, const std::string& particleEffect = ""); From 327fafe7397928434af4654b871d078ae2b324bc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 7 Jan 2024 06:49:56 +0300 Subject: [PATCH 204/231] Contect selector: fix ESM date and version data encoding/decoding (#7764) --- components/contentselector/model/esmfile.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 7c62299048..39a33e710e 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -51,7 +51,8 @@ QByteArray ContentSelectorModel::EsmFile::encodedData() const QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); - stream << mFileName << mAuthor << mVersion << mModified.toString() << mPath << mDescription << mGameFiles; + stream << mFileName << mAuthor << mVersion << mModified.toString(Qt::ISODate) << mPath << mDescription + << mGameFiles; return encodedData; } @@ -113,11 +114,11 @@ void ContentSelectorModel::EsmFile::setFileProperty(const FileProperty prop, con break; case FileProperty_Format: - mVersion = value.toInt(); + mVersion = value; break; case FileProperty_DateModified: - mModified = QDateTime::fromString(value); + mModified = QDateTime::fromString(value, Qt::ISODate); break; case FileProperty_FilePath: From 828c40c710f798ea60937bdd6045809b6c2362ec Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 7 Jan 2024 18:52:10 +0400 Subject: [PATCH 205/231] Do not copy due to auto misuse --- apps/launcher/graphicspage.cpp | 2 +- components/stereo/stereomanager.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index fa9d5eb479..b360c215e6 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -154,7 +154,7 @@ bool Launcher::GraphicsPage::loadSettings() if (Settings::shadows().mEnableIndoorShadows) indoorShadowsCheckBox->setCheckState(Qt::Checked); - auto boundMethod = Settings::shadows().mComputeSceneBounds.get(); + const auto& boundMethod = Settings::shadows().mComputeSceneBounds.get(); if (boundMethod == "bounds") shadowComputeSceneBoundsComboBox->setCurrentIndex(0); else if (boundMethod == "primitives") diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index a2eea0fda2..c3d7f1d320 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -315,8 +315,7 @@ namespace Stereo else { auto* ds = osg::DisplaySettings::instance().get(); - auto viewMatrix = mMainCamera->getViewMatrix(); - auto projectionMatrix = mMainCamera->getProjectionMatrix(); + const auto& projectionMatrix = mMainCamera->getProjectionMatrix(); auto s = ds->getEyeSeparation() * Constants::UnitsPerMeter; mViewOffsetMatrix[0] = osg::Matrixd(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, s, 0.0, 0.0, 1.0); From 4f65b7167a8d7e8af51449899ad4eb2e50cf97ea Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 7 Jan 2024 18:53:07 +0400 Subject: [PATCH 206/231] Do not copy vector for every door marker --- apps/openmw/mwgui/mapwindow.cpp | 9 ++++----- apps/openmw/mwgui/mapwindow.hpp | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 6d79b5f9d7..cb6ba79f9e 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -300,11 +300,9 @@ namespace MWGui return MyGUI::IntCoord(position.left - halfMarkerSize, position.top - halfMarkerSize, markerSize, markerSize); } - MyGUI::Widget* LocalMapBase::createDoorMarker( - const std::string& name, const MyGUI::VectorString& notes, float x, float y) const + MyGUI::Widget* LocalMapBase::createDoorMarker(const std::string& name, float x, float y) const { MarkerUserData data(mLocalMapRender); - data.notes = notes; data.caption = name; MarkerWidget* markerWidget = mLocalMap->createWidget( "MarkerButton", getMarkerCoordinates(x, y, data, 8), MyGUI::Align::Default); @@ -662,8 +660,9 @@ namespace MWGui MarkerUserData* data; if (mDoorMarkersToRecycle.empty()) { - markerWidget = createDoorMarker(marker.name, destNotes, marker.x, marker.y); + markerWidget = createDoorMarker(marker.name, marker.x, marker.y); data = markerWidget->getUserData(); + data->notes = std::move(destNotes); doorMarkerCreated(markerWidget); } else @@ -672,7 +671,7 @@ namespace MWGui mDoorMarkersToRecycle.pop_back(); data = markerWidget->getUserData(); - data->notes = destNotes; + data->notes = std::move(destNotes); data->caption = marker.name; markerWidget->setCoord(getMarkerCoordinates(marker.x, marker.y, *data, 8)); markerWidget->setVisible(true); diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 5afc8c7c8a..29759a4365 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -170,8 +170,7 @@ namespace MWGui MyGUI::IntPoint getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const; MyGUI::IntCoord getMarkerCoordinates( float worldX, float worldY, MarkerUserData& markerPos, size_t markerSize) const; - MyGUI::Widget* createDoorMarker( - const std::string& name, const MyGUI::VectorString& notes, float x, float y) const; + MyGUI::Widget* createDoorMarker(const std::string& name, float x, float y) const; MyGUI::IntCoord getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const; virtual void notifyPlayerUpdate() {} From 8879d89e4a227cd6fe5571dc2042b2df0fc69477 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 7 Jan 2024 19:10:09 +0400 Subject: [PATCH 207/231] Replace 'klass' by meaningful names --- apps/openmw/mwgui/charactercreation.cpp | 43 +++++++++++++------------ apps/openmw/mwgui/class.cpp | 16 ++++----- apps/openmw/mwgui/review.cpp | 8 ++--- apps/openmw/mwgui/review.hpp | 4 +-- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 19f7d97176..4141e61e34 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -333,10 +333,10 @@ namespace MWGui if (!classId.empty()) MWBase::Environment::get().getMechanicsManager()->setPlayerClass(classId); - const ESM::Class* klass = MWBase::Environment::get().getESMStore()->get().find(classId); - if (klass) + const ESM::Class* pickedClass = MWBase::Environment::get().getESMStore()->get().find(classId); + if (pickedClass) { - mPlayerClass = *klass; + mPlayerClass = *pickedClass; } MWBase::Environment::get().getWindowManager()->removeDialog(std::move(mPickClassDialog)); } @@ -454,30 +454,30 @@ namespace MWGui { if (mCreateClassDialog) { - ESM::Class klass; - klass.mName = mCreateClassDialog->getName(); - klass.mDescription = mCreateClassDialog->getDescription(); - klass.mData.mSpecialization = mCreateClassDialog->getSpecializationId(); - klass.mData.mIsPlayable = 0x1; - klass.mRecordFlags = 0; + ESM::Class createdClass; + createdClass.mName = mCreateClassDialog->getName(); + createdClass.mDescription = mCreateClassDialog->getDescription(); + createdClass.mData.mSpecialization = mCreateClassDialog->getSpecializationId(); + createdClass.mData.mIsPlayable = 0x1; + createdClass.mRecordFlags = 0; std::vector attributes = mCreateClassDialog->getFavoriteAttributes(); - assert(attributes.size() >= klass.mData.mAttribute.size()); - for (size_t i = 0; i < klass.mData.mAttribute.size(); ++i) - klass.mData.mAttribute[i] = ESM::Attribute::refIdToIndex(attributes[i]); + assert(attributes.size() >= createdClass.mData.mAttribute.size()); + for (size_t i = 0; i < createdClass.mData.mAttribute.size(); ++i) + createdClass.mData.mAttribute[i] = ESM::Attribute::refIdToIndex(attributes[i]); std::vector majorSkills = mCreateClassDialog->getMajorSkills(); std::vector minorSkills = mCreateClassDialog->getMinorSkills(); - assert(majorSkills.size() >= klass.mData.mSkills.size()); - assert(minorSkills.size() >= klass.mData.mSkills.size()); - for (size_t i = 0; i < klass.mData.mSkills.size(); ++i) + assert(majorSkills.size() >= createdClass.mData.mSkills.size()); + assert(minorSkills.size() >= createdClass.mData.mSkills.size()); + for (size_t i = 0; i < createdClass.mData.mSkills.size(); ++i) { - klass.mData.mSkills[i][1] = ESM::Skill::refIdToIndex(majorSkills[i]); - klass.mData.mSkills[i][0] = ESM::Skill::refIdToIndex(minorSkills[i]); + createdClass.mData.mSkills[i][1] = ESM::Skill::refIdToIndex(majorSkills[i]); + createdClass.mData.mSkills[i][0] = ESM::Skill::refIdToIndex(minorSkills[i]); } - MWBase::Environment::get().getMechanicsManager()->setPlayerClass(klass); - mPlayerClass = klass; + MWBase::Environment::get().getMechanicsManager()->setPlayerClass(createdClass); + mPlayerClass = std::move(createdClass); // Do not delete dialog, so that choices are remembered in case we want to go back and adjust them later mCreateClassDialog->setVisible(false); @@ -666,9 +666,10 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->setPlayerClass(mGenerateClass); - const ESM::Class* klass = MWBase::Environment::get().getESMStore()->get().find(mGenerateClass); + const ESM::Class* generatedClass + = MWBase::Environment::get().getESMStore()->get().find(mGenerateClass); - mPlayerClass = *klass; + mPlayerClass = *generatedClass; } void CharacterCreation::onGenerateClassBack() diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index d6b4e7f635..839f0f5072 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -248,27 +248,27 @@ namespace MWGui if (mCurrentClassId.empty()) return; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const ESM::Class* klass = store.get().search(mCurrentClassId); - if (!klass) + const ESM::Class* currentClass = store.get().search(mCurrentClassId); + if (!currentClass) return; ESM::Class::Specialization specialization - = static_cast(klass->mData.mSpecialization); + = static_cast(currentClass->mData.mSpecialization); std::string specName{ MWBase::Environment::get().getWindowManager()->getGameSettingString( ESM::Class::sGmstSpecializationIds[specialization], ESM::Class::sGmstSpecializationIds[specialization]) }; mSpecializationName->setCaption(specName); ToolTips::createSpecializationToolTip(mSpecializationName, specName, specialization); - mFavoriteAttribute[0]->setAttributeId(ESM::Attribute::indexToRefId(klass->mData.mAttribute[0])); - mFavoriteAttribute[1]->setAttributeId(ESM::Attribute::indexToRefId(klass->mData.mAttribute[1])); + mFavoriteAttribute[0]->setAttributeId(ESM::Attribute::indexToRefId(currentClass->mData.mAttribute[0])); + mFavoriteAttribute[1]->setAttributeId(ESM::Attribute::indexToRefId(currentClass->mData.mAttribute[1])); ToolTips::createAttributeToolTip(mFavoriteAttribute[0], mFavoriteAttribute[0]->getAttributeId()); ToolTips::createAttributeToolTip(mFavoriteAttribute[1], mFavoriteAttribute[1]->getAttributeId()); - for (size_t i = 0; i < klass->mData.mSkills.size(); ++i) + for (size_t i = 0; i < currentClass->mData.mSkills.size(); ++i) { - ESM::RefId minor = ESM::Skill::indexToRefId(klass->mData.mSkills[i][0]); - ESM::RefId major = ESM::Skill::indexToRefId(klass->mData.mSkills[i][1]); + ESM::RefId minor = ESM::Skill::indexToRefId(currentClass->mData.mSkills[i][0]); + ESM::RefId major = ESM::Skill::indexToRefId(currentClass->mData.mSkills[i][1]); mMinorSkill[i]->setSkillId(minor); mMajorSkill[i]->setSkillId(major); ToolTips::createSkillToolTip(mMinorSkill[i], minor); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index 4ea21df00c..ddce2c5f50 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -148,11 +148,11 @@ namespace MWGui mUpdateSkillArea = true; } - void ReviewDialog::setClass(const ESM::Class& class_) + void ReviewDialog::setClass(const ESM::Class& playerClass) { - mKlass = class_; - mClassWidget->setCaption(mKlass.mName); - ToolTips::createClassToolTip(mClassWidget, mKlass); + mClass = playerClass; + mClassWidget->setCaption(mClass.mName); + ToolTips::createClassToolTip(mClassWidget, mClass); } void ReviewDialog::setBirthSign(const ESM::RefId& signId) diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 6f594c60f0..7226ad628d 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -30,7 +30,7 @@ namespace MWGui void setPlayerName(const std::string& name); void setRace(const ESM::RefId& raceId); - void setClass(const ESM::Class& class_); + void setClass(const ESM::Class& playerClass); void setBirthSign(const ESM::RefId& signId); void setHealth(const MWMechanics::DynamicStat& value); @@ -96,7 +96,7 @@ namespace MWGui std::map mSkillWidgetMap; ESM::RefId mRaceId, mBirthSignId; std::string mName; - ESM::Class mKlass; + ESM::Class mClass; std::vector mSkillWidgets; //< Skills and other information bool mUpdateSkillArea; From 084fc80efdfe3342718d3bc2d25d23c32eb1d8a3 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 7 Jan 2024 18:53:36 +0400 Subject: [PATCH 208/231] Use string_view for readonly string properties --- apps/openmw/mwlua/factionbindings.cpp | 3 ++- apps/openmw/mwlua/vfsbindings.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/factionbindings.cpp b/apps/openmw/mwlua/factionbindings.cpp index a43cec874f..87ce6ced39 100644 --- a/apps/openmw/mwlua/factionbindings.cpp +++ b/apps/openmw/mwlua/factionbindings.cpp @@ -122,7 +122,8 @@ namespace MWLua return "ESM3_FactionRank[" + rec.mFactionId.toDebugString() + ", " + std::to_string(rec.mRankIndex + 1) + "]"; }; - rankT["name"] = sol::readonly_property([](const FactionRank& rec) { return rec.mRankName; }); + rankT["name"] + = sol::readonly_property([](const FactionRank& rec) -> std::string_view { return rec.mRankName; }); rankT["primarySkillValue"] = sol::readonly_property([](const FactionRank& rec) { return rec.mPrimarySkill; }); rankT["favouredSkillValue"] = sol::readonly_property([](const FactionRank& rec) { return rec.mFavouredSkill; }); rankT["factionReaction"] = sol::readonly_property([](const FactionRank& rec) { return rec.mFactReaction; }); diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp index ad32520649..0eccb336c2 100644 --- a/apps/openmw/mwlua/vfsbindings.cpp +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -161,7 +161,8 @@ namespace MWLua auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); sol::usertype handle = context.mLua->sol().new_usertype("FileHandle"); - handle["fileName"] = sol::readonly_property([](const FileHandle& self) { return self.mFileName; }); + handle["fileName"] + = sol::readonly_property([](const FileHandle& self) -> std::string_view { return self.mFileName; }); handle[sol::meta_function::to_string] = [](const FileHandle& self) { return "FileHandle{'" + self.mFileName + "'" + (!self.mFilePtr ? ", closed" : "") + "}"; }; From cc0b00a0d254e910f5ce8586d5c19ff68ac8424e Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Jan 2024 12:51:48 +0100 Subject: [PATCH 209/231] Use settings values to declare colour settings --- apps/opencs/model/prefs/coloursetting.cpp | 2 +- apps/opencs/model/prefs/coloursetting.hpp | 3 ++- apps/opencs/model/prefs/state.cpp | 33 ++++++++++++----------- apps/opencs/model/prefs/state.hpp | 2 +- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/apps/opencs/model/prefs/coloursetting.cpp b/apps/opencs/model/prefs/coloursetting.cpp index 9e237dd7a8..10ca9d7f68 100644 --- a/apps/opencs/model/prefs/coloursetting.cpp +++ b/apps/opencs/model/prefs/coloursetting.cpp @@ -14,7 +14,7 @@ #include "state.hpp" CSMPrefs::ColourSetting::ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mWidget(nullptr) { diff --git a/apps/opencs/model/prefs/coloursetting.hpp b/apps/opencs/model/prefs/coloursetting.hpp index f5af627491..85e43f28bd 100644 --- a/apps/opencs/model/prefs/coloursetting.hpp +++ b/apps/opencs/model/prefs/coloursetting.hpp @@ -6,6 +6,7 @@ #include #include +#include #include class QMutex; @@ -30,7 +31,7 @@ namespace CSMPrefs public: explicit ColourSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); ColourSetting& setTooltip(const std::string& tooltip); diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index b05a4b7473..13a2a35cc5 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -126,14 +126,14 @@ void CSMPrefs::State::declare() .setRange(0, 10000); declareInt(mValues->mScripts.mErrorHeight, "Initial height of the error panel").setRange(100, 10000); declareBool(mValues->mScripts.mHighlightOccurrences, "Highlight other occurrences of selected names"); - declareColour("colour-highlight", "Colour of highlighted occurrences", QColor("lightcyan")); - declareColour("colour-int", "Highlight Colour: Integer Literals", QColor("darkmagenta")); - declareColour("colour-float", "Highlight Colour: Float Literals", QColor("magenta")); - declareColour("colour-name", "Highlight Colour: Names", QColor("grey")); - declareColour("colour-keyword", "Highlight Colour: Keywords", QColor("red")); - declareColour("colour-special", "Highlight Colour: Special Characters", QColor("darkorange")); - declareColour("colour-comment", "Highlight Colour: Comments", QColor("green")); - declareColour("colour-id", "Highlight Colour: IDs", QColor("blue")); + declareColour(mValues->mScripts.mColourHighlight, "Colour of highlighted occurrences"); + declareColour(mValues->mScripts.mColourInt, "Highlight Colour: Integer Literals"); + declareColour(mValues->mScripts.mColourFloat, "Highlight Colour: Float Literals"); + declareColour(mValues->mScripts.mColourName, "Highlight Colour: Names"); + declareColour(mValues->mScripts.mColourKeyword, "Highlight Colour: Keywords"); + declareColour(mValues->mScripts.mColourSpecial, "Highlight Colour: Special Characters"); + declareColour(mValues->mScripts.mColourComment, "Highlight Colour: Comments"); + declareColour(mValues->mScripts.mColourId, "Highlight Colour: IDs"); declareCategory("General Input"); declareBool(mValues->mGeneralInput.mCycle, "Cyclic next/previous") @@ -185,18 +185,18 @@ void CSMPrefs::State::declare() .setRange(10, 10000); declareDouble(mValues->mRendering.mObjectMarkerAlpha, "Object Marker Transparency").setPrecision(2).setRange(0, 1); declareBool(mValues->mRendering.mSceneUseGradient, "Use Gradient Background"); - declareColour("scene-day-background-colour", "Day Background Colour", QColor(110, 120, 128, 255)); - declareColour("scene-day-gradient-colour", "Day Gradient Colour", QColor(47, 51, 51, 255)) + declareColour(mValues->mRendering.mSceneDayBackgroundColour, "Day Background Colour"); + declareColour(mValues->mRendering.mSceneDayGradientColour, "Day Gradient Colour") .setTooltip( "Sets the gradient color to use in conjunction with the day background color. Ignored if " "the gradient option is disabled."); - declareColour("scene-bright-background-colour", "Scene Bright Background Colour", QColor(79, 87, 92, 255)); - declareColour("scene-bright-gradient-colour", "Scene Bright Gradient Colour", QColor(47, 51, 51, 255)) + declareColour(mValues->mRendering.mSceneBrightBackgroundColour, "Scene Bright Background Colour"); + declareColour(mValues->mRendering.mSceneBrightGradientColour, "Scene Bright Gradient Colour") .setTooltip( "Sets the gradient color to use in conjunction with the bright background color. Ignored if " "the gradient option is disabled."); - declareColour("scene-night-background-colour", "Scene Night Background Colour", QColor(64, 77, 79, 255)); - declareColour("scene-night-gradient-colour", "Scene Night Gradient Colour", QColor(47, 51, 51, 255)) + declareColour(mValues->mRendering.mSceneNightBackgroundColour, "Scene Night Background Colour"); + declareColour(mValues->mRendering.mSceneNightGradientColour, "Scene Night Gradient Colour") .setTooltip( "Sets the gradient color to use in conjunction with the night background color. Ignored if " "the gradient option is disabled."); @@ -477,13 +477,14 @@ CSMPrefs::EnumSetting& CSMPrefs::State::declareEnum(EnumSettingValue& value, con return *setting; } -CSMPrefs::ColourSetting& CSMPrefs::State::declareColour(const std::string& key, const QString& label, QColor default_) +CSMPrefs::ColourSetting& CSMPrefs::State::declareColour( + Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); CSMPrefs::ColourSetting* setting - = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::ColourSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index f3e580ea5a..ce6e89ef08 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -72,7 +72,7 @@ namespace CSMPrefs EnumSetting& declareEnum(EnumSettingValue& value, const QString& label); - ColourSetting& declareColour(const std::string& key, const QString& label, QColor default_); + ColourSetting& declareColour(Settings::SettingValue& value, const QString& label); ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); From 067957f57b7a6af55f17056181776631f3cdffcd Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 26 Dec 2023 14:38:39 +0100 Subject: [PATCH 210/231] Use "" to quote apps/openmw includes and remove unused Using "" makes clangd to find unused includes which makes it quite easy to remove them. --- apps/launcher/utils/openalutil.cpp | 2 +- apps/openmw/mwgui/console.cpp | 3 ++- apps/openmw/mwlua/cellbindings.cpp | 2 ++ apps/openmw/mwlua/luabindings.cpp | 1 + apps/openmw/mwlua/luamanagerimp.cpp | 2 ++ apps/openmw/mwlua/objectbindings.cpp | 3 +++ apps/openmw/mwlua/types/activator.cpp | 4 ---- apps/openmw/mwlua/types/actor.cpp | 2 ++ apps/openmw/mwlua/types/actor.hpp | 11 +++++------ apps/openmw/mwlua/types/apparatus.cpp | 4 +--- apps/openmw/mwlua/types/armor.cpp | 4 +--- apps/openmw/mwlua/types/book.cpp | 6 ++---- apps/openmw/mwlua/types/clothing.cpp | 4 +--- apps/openmw/mwlua/types/container.cpp | 2 +- apps/openmw/mwlua/types/creature.cpp | 4 ---- apps/openmw/mwlua/types/door.cpp | 1 - apps/openmw/mwlua/types/ingredient.cpp | 5 +---- apps/openmw/mwlua/types/light.cpp | 4 +--- apps/openmw/mwlua/types/lockable.cpp | 4 ++-- apps/openmw/mwlua/types/lockpick.cpp | 4 +--- apps/openmw/mwlua/types/misc.cpp | 5 ++--- apps/openmw/mwlua/types/npc.cpp | 12 ++++++------ apps/openmw/mwlua/types/player.cpp | 13 +++++++------ apps/openmw/mwlua/types/potion.cpp | 4 +--- apps/openmw/mwlua/types/probe.cpp | 4 +--- apps/openmw/mwlua/types/repair.cpp | 2 +- apps/openmw/mwlua/types/static.cpp | 4 ---- apps/openmw/mwlua/types/types.hpp | 1 - apps/openmw/mwlua/types/weapon.cpp | 5 +---- apps/openmw/mwlua/worker.cpp | 2 +- apps/openmw/mwrender/actorspaths.hpp | 2 +- apps/openmw/mwrender/fogmanager.cpp | 2 +- apps/openmw/mwworld/cellref.cpp | 8 ++++---- apps/openmw/mwworld/groundcoverstore.cpp | 2 -- .../mwdialogue/test_keywordsearch.cpp | 1 + apps/openmw_test_suite/openmw/options.cpp | 3 ++- 36 files changed, 58 insertions(+), 84 deletions(-) diff --git a/apps/launcher/utils/openalutil.cpp b/apps/launcher/utils/openalutil.cpp index 1a332e9788..9a9ae9981b 100644 --- a/apps/launcher/utils/openalutil.cpp +++ b/apps/launcher/utils/openalutil.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include "apps/openmw/mwsound/alext.h" #include "openalutil.hpp" diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index d4553b9664..504ea560b2 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -20,6 +19,8 @@ #include #include +#include "apps/openmw/mwgui/textcolours.hpp" + #include "../mwscript/extensions.hpp" #include "../mwscript/interpretercontext.hpp" diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 62dcc69330..081df13a0e 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -43,6 +43,8 @@ #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/cellstore.hpp" #include "../mwworld/worldmodel.hpp" diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 6331bc5466..48f2f3e35d 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -21,6 +21,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/statemanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/action.hpp" #include "../mwworld/class.hpp" #include "../mwworld/datetimemanager.hpp" diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 43092af3dc..2417e9e340 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -21,11 +21,13 @@ #include #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwrender/postprocessor.hpp" #include "../mwworld/datetimemanager.hpp" #include "../mwworld/esmstore.hpp" +#include "../mwworld/player.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/scene.hpp" #include "../mwworld/worldmodel.hpp" diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index f45decea3c..748d963bdc 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -20,6 +20,9 @@ #include "../mwmechanics/creaturestats.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + #include "luaevents.hpp" #include "luamanagerimp.hpp" #include "types/types.hpp" diff --git a/apps/openmw/mwlua/types/activator.cpp b/apps/openmw/mwlua/types/activator.cpp index 5ffae0ccd7..43e03952f7 100644 --- a/apps/openmw/mwlua/types/activator.cpp +++ b/apps/openmw/mwlua/types/activator.cpp @@ -5,10 +5,6 @@ #include #include -#include -#include -#include - namespace sol { template <> diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index a4449f6fb0..473b0d3301 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -6,8 +6,10 @@ #include #include +#include "apps/openmw/mwbase/environment.hpp" #include "apps/openmw/mwbase/mechanicsmanager.hpp" #include "apps/openmw/mwbase/windowmanager.hpp" +#include "apps/openmw/mwbase/world.hpp" #include "apps/openmw/mwmechanics/actorutil.hpp" #include "apps/openmw/mwmechanics/creaturestats.hpp" #include "apps/openmw/mwmechanics/drawstate.hpp" diff --git a/apps/openmw/mwlua/types/actor.hpp b/apps/openmw/mwlua/types/actor.hpp index 4a16b65cbf..6700b4a403 100644 --- a/apps/openmw/mwlua/types/actor.hpp +++ b/apps/openmw/mwlua/types/actor.hpp @@ -4,16 +4,15 @@ #include #include -#include - -#include "apps/openmw/mwworld/esmstore.hpp" -#include -#include #include #include +#include + +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "../context.hpp" -#include "../object.hpp" + namespace MWLua { diff --git a/apps/openmw/mwlua/types/apparatus.cpp b/apps/openmw/mwlua/types/apparatus.cpp index 05eae8b2aa..282dd0669d 100644 --- a/apps/openmw/mwlua/types/apparatus.cpp +++ b/apps/openmw/mwlua/types/apparatus.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/armor.cpp b/apps/openmw/mwlua/types/armor.cpp index b0df533327..91a4c05d8b 100644 --- a/apps/openmw/mwlua/types/armor.cpp +++ b/apps/openmw/mwlua/types/armor.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/book.cpp b/apps/openmw/mwlua/types/book.cpp index dfdbcff1ca..ce2138127d 100644 --- a/apps/openmw/mwlua/types/book.cpp +++ b/apps/openmw/mwlua/types/book.cpp @@ -4,14 +4,12 @@ #include #include +#include #include #include #include -#include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/clothing.cpp b/apps/openmw/mwlua/types/clothing.cpp index 74b03148cb..894748946d 100644 --- a/apps/openmw/mwlua/types/clothing.cpp +++ b/apps/openmw/mwlua/types/clothing.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/container.cpp b/apps/openmw/mwlua/types/container.cpp index ac2e75dea4..332cf6ac2e 100644 --- a/apps/openmw/mwlua/types/container.cpp +++ b/apps/openmw/mwlua/types/container.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include "apps/openmw/mwworld/class.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/creature.cpp b/apps/openmw/mwlua/types/creature.cpp index 54a3c37750..ddf90bf8c5 100644 --- a/apps/openmw/mwlua/types/creature.cpp +++ b/apps/openmw/mwlua/types/creature.cpp @@ -6,10 +6,6 @@ #include #include -#include -#include -#include - namespace sol { template <> diff --git a/apps/openmw/mwlua/types/door.cpp b/apps/openmw/mwlua/types/door.cpp index a4185434c3..df1d10015a 100644 --- a/apps/openmw/mwlua/types/door.cpp +++ b/apps/openmw/mwlua/types/door.cpp @@ -7,7 +7,6 @@ #include #include -#include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/worldmodel.hpp" namespace sol diff --git a/apps/openmw/mwlua/types/ingredient.cpp b/apps/openmw/mwlua/types/ingredient.cpp index 72b9f27263..abfd2329ce 100644 --- a/apps/openmw/mwlua/types/ingredient.cpp +++ b/apps/openmw/mwlua/types/ingredient.cpp @@ -6,10 +6,7 @@ #include #include -#include - -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/light.cpp b/apps/openmw/mwlua/types/light.cpp index d2cf195219..5a357994a3 100644 --- a/apps/openmw/mwlua/types/light.cpp +++ b/apps/openmw/mwlua/types/light.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/lockable.cpp b/apps/openmw/mwlua/types/lockable.cpp index e7413edef5..2569f42ee4 100644 --- a/apps/openmw/mwlua/types/lockable.cpp +++ b/apps/openmw/mwlua/types/lockable.cpp @@ -1,11 +1,11 @@ - #include "types.hpp" + #include #include #include #include -#include +#include "apps/openmw/mwworld/esmstore.hpp" namespace MWLua { diff --git a/apps/openmw/mwlua/types/lockpick.cpp b/apps/openmw/mwlua/types/lockpick.cpp index 6de1f7f670..373de4b24d 100644 --- a/apps/openmw/mwlua/types/lockpick.cpp +++ b/apps/openmw/mwlua/types/lockpick.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/misc.cpp b/apps/openmw/mwlua/types/misc.cpp index 9e6b2d6ae5..f83864477f 100644 --- a/apps/openmw/mwlua/types/misc.cpp +++ b/apps/openmw/mwlua/types/misc.cpp @@ -6,9 +6,8 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/npc.cpp b/apps/openmw/mwlua/types/npc.cpp index 177c0327ab..30c8fd60d9 100644 --- a/apps/openmw/mwlua/types/npc.cpp +++ b/apps/openmw/mwlua/types/npc.cpp @@ -5,12 +5,12 @@ #include #include -#include -#include -#include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/mechanicsmanager.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwmechanics/npcstats.hpp" +#include "apps/openmw/mwworld/class.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" #include "../classbindings.hpp" #include "../localscripts.hpp" diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index cef0753817..07b238fa36 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -1,12 +1,13 @@ #include "types.hpp" #include "../luamanagerimp.hpp" -#include -#include -#include -#include -#include -#include + +#include "apps/openmw/mwbase/inputmanager.hpp" +#include "apps/openmw/mwbase/journal.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwmechanics/npcstats.hpp" +#include "apps/openmw/mwworld/class.hpp" +#include "apps/openmw/mwworld/globals.hpp" namespace MWLua { diff --git a/apps/openmw/mwlua/types/potion.cpp b/apps/openmw/mwlua/types/potion.cpp index 33302a3d34..50aca6d9e7 100644 --- a/apps/openmw/mwlua/types/potion.cpp +++ b/apps/openmw/mwlua/types/potion.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/probe.cpp b/apps/openmw/mwlua/types/probe.cpp index 6a3784b41a..30c84326a5 100644 --- a/apps/openmw/mwlua/types/probe.cpp +++ b/apps/openmw/mwlua/types/probe.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/repair.cpp b/apps/openmw/mwlua/types/repair.cpp index 5e97e8c787..880a74d131 100644 --- a/apps/openmw/mwlua/types/repair.cpp +++ b/apps/openmw/mwlua/types/repair.cpp @@ -5,7 +5,7 @@ #include #include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { diff --git a/apps/openmw/mwlua/types/static.cpp b/apps/openmw/mwlua/types/static.cpp index 960218ca68..78f8cffd67 100644 --- a/apps/openmw/mwlua/types/static.cpp +++ b/apps/openmw/mwlua/types/static.cpp @@ -5,10 +5,6 @@ #include #include -#include -#include -#include - namespace sol { template <> diff --git a/apps/openmw/mwlua/types/types.hpp b/apps/openmw/mwlua/types/types.hpp index adac372277..b52846508a 100644 --- a/apps/openmw/mwlua/types/types.hpp +++ b/apps/openmw/mwlua/types/types.hpp @@ -7,7 +7,6 @@ #include #include "apps/openmw/mwbase/environment.hpp" -#include "apps/openmw/mwbase/world.hpp" #include "apps/openmw/mwworld/esmstore.hpp" #include "apps/openmw/mwworld/store.hpp" diff --git a/apps/openmw/mwlua/types/weapon.cpp b/apps/openmw/mwlua/types/weapon.cpp index de9b2efb95..f09cd96dad 100644 --- a/apps/openmw/mwlua/types/weapon.cpp +++ b/apps/openmw/mwlua/types/weapon.cpp @@ -5,9 +5,7 @@ #include #include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" namespace sol { @@ -16,7 +14,6 @@ namespace sol { }; } -#include namespace { diff --git a/apps/openmw/mwlua/worker.cpp b/apps/openmw/mwlua/worker.cpp index e8b06cf210..193d340208 100644 --- a/apps/openmw/mwlua/worker.cpp +++ b/apps/openmw/mwlua/worker.cpp @@ -2,7 +2,7 @@ #include "luamanagerimp.hpp" -#include +#include "apps/openmw/profile.hpp" #include #include diff --git a/apps/openmw/mwrender/actorspaths.hpp b/apps/openmw/mwrender/actorspaths.hpp index d18197b974..239806576f 100644 --- a/apps/openmw/mwrender/actorspaths.hpp +++ b/apps/openmw/mwrender/actorspaths.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_MWRENDER_AGENTSPATHS_H #define OPENMW_MWRENDER_AGENTSPATHS_H -#include +#include "apps/openmw/mwworld/ptr.hpp" #include diff --git a/apps/openmw/mwrender/fogmanager.cpp b/apps/openmw/mwrender/fogmanager.cpp index ef8de1cb2e..b75fb507ed 100644 --- a/apps/openmw/mwrender/fogmanager.cpp +++ b/apps/openmw/mwrender/fogmanager.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include "apps/openmw/mwworld/cell.hpp" namespace MWRender { diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index fda7157010..854348c2a8 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -8,10 +8,10 @@ #include #include -#include -#include -#include -#include +#include "apps/openmw/mwbase/environment.hpp" +#include "apps/openmw/mwbase/world.hpp" +#include "apps/openmw/mwmechanics/spellutil.hpp" +#include "apps/openmw/mwworld/esmstore.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/groundcoverstore.cpp b/apps/openmw/mwworld/groundcoverstore.cpp index 17bf72b5d3..f45b49babe 100644 --- a/apps/openmw/mwworld/groundcoverstore.cpp +++ b/apps/openmw/mwworld/groundcoverstore.cpp @@ -9,8 +9,6 @@ #include #include -#include - #include "store.hpp" namespace MWWorld diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp index b97f30b319..a3f0d8d3c0 100644 --- a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -1,4 +1,5 @@ #include "apps/openmw/mwdialogue/keywordsearch.hpp" + #include struct KeywordSearchTest : public ::testing::Test diff --git a/apps/openmw_test_suite/openmw/options.cpp b/apps/openmw_test_suite/openmw/options.cpp index d79a387645..fc89264f8c 100644 --- a/apps/openmw_test_suite/openmw/options.cpp +++ b/apps/openmw_test_suite/openmw/options.cpp @@ -1,4 +1,5 @@ -#include +#include "apps/openmw/options.hpp" + #include #include From afd6f0739ca4b30547daee82eca149ae73547357 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 26 Nov 2023 09:28:52 +0100 Subject: [PATCH 211/231] Format box shape indices grouping by triangle --- .../detournavigator/recastmeshbuilder.cpp | 48 +++++-------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/components/detournavigator/recastmeshbuilder.cpp b/components/detournavigator/recastmeshbuilder.cpp index 1684e3e542..6c15b441e7 100644 --- a/components/detournavigator/recastmeshbuilder.cpp +++ b/components/detournavigator/recastmeshbuilder.cpp @@ -189,42 +189,18 @@ namespace DetourNavigator void RecastMeshBuilder::addObject(const btBoxShape& shape, const btTransform& transform, const AreaType areaType) { constexpr std::array indices{ { - 0, - 2, - 3, - 3, - 1, - 0, - 0, - 4, - 6, - 6, - 2, - 0, - 0, - 1, - 5, - 5, - 4, - 0, - 7, - 5, - 1, - 1, - 3, - 7, - 7, - 3, - 2, - 2, - 6, - 7, - 7, - 6, - 4, - 4, - 5, - 7, + 0, 2, 3, // triangle 0 + 3, 1, 0, // triangle 1 + 0, 4, 6, // triangle 2 + 6, 2, 0, // triangle 3 + 0, 1, 5, // triangle 4 + 5, 4, 0, // triangle 5 + 7, 5, 1, // triangle 6 + 1, 3, 7, // triangle 7 + 7, 3, 2, // triangle 8 + 2, 6, 7, // triangle 9 + 7, 6, 4, // triangle 10 + 4, 5, 7, // triangle 11 } }; for (std::size_t i = 0; i < indices.size(); i += 3) From b0b6c48a8804c9d373d043140402745deb925b8c Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 26 Nov 2023 10:28:39 +0100 Subject: [PATCH 212/231] Add clarifying comments to detournavigator coordinates conversion functions --- components/detournavigator/settingsutils.hpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/detournavigator/settingsutils.hpp b/components/detournavigator/settingsutils.hpp index 10e99d002f..c9d8665d51 100644 --- a/components/detournavigator/settingsutils.hpp +++ b/components/detournavigator/settingsutils.hpp @@ -47,6 +47,7 @@ namespace DetourNavigator return position; } + // Returns value in NavMesh coordinates inline float getTileSize(const RecastSettings& settings) { return static_cast(settings.mTileSize) * settings.mCellSize; @@ -62,16 +63,19 @@ namespace DetourNavigator return static_cast(v); } + // Returns integer tile position for position in navmesh coordinates inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec2f& position) { return TilePosition(getTilePosition(settings, position.x()), getTilePosition(settings, position.y())); } + // Returns integer tile position for position in navmesh coordinates inline TilePosition getTilePosition(const RecastSettings& settings, const osg::Vec3f& position) { return getTilePosition(settings, osg::Vec2f(position.x(), position.z())); } + // Returns tile bounds in navmesh coordinates inline TileBounds makeTileBounds(const RecastSettings& settings, const TilePosition& tilePosition) { return TileBounds{ @@ -80,6 +84,7 @@ namespace DetourNavigator }; } + // Returns border size relative to cell size inline float getBorderSize(const RecastSettings& settings) { return static_cast(settings.mBorderSize) * settings.mCellSize; @@ -95,6 +100,7 @@ namespace DetourNavigator return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1; } + // Returns tile bounds in real coordinates inline TileBounds makeRealTileBoundsWithBorder(const RecastSettings& settings, const TilePosition& tilePosition) { TileBounds result = makeTileBounds(settings, tilePosition); From 3af28439d6dd4453825f69ea7c95c127d4249461 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 6 Jan 2024 19:41:18 +0300 Subject: [PATCH 213/231] Interrupt thunder SFX indoors (bug #6402) --- CHANGELOG.md | 1 + apps/openmw/mwworld/weather.cpp | 15 ++++++++++++--- apps/openmw/mwworld/weather.hpp | 3 ++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 181116ca9d..aea5b6b0a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile + Bug #6402: The sound of a thunderstorm does not stop playing after entering the premises Bug #6427: Enemy health bar disappears before damaging effect ends Bug #6550: Cloned body parts don't inherit texture effects Bug #6645: Enemy block sounds align with animation instead of blocked hits diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 2c9b80bc5f..655cd5aa7a 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -180,7 +180,6 @@ namespace MWWorld , mTransitionDelta(Fallback::Map::getFloat("Weather_" + name + "_Transition_Delta")) , mThunderFrequency(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Frequency")) , mThunderThreshold(Fallback::Map::getFloat("Weather_" + name + "_Thunder_Threshold")) - , mThunderSoundID() , mFlashDecrement(Fallback::Map::getFloat("Weather_" + name + "_Flash_Decrement")) , mFlashBrightness(0.0f) { @@ -823,19 +822,29 @@ namespace MWWorld void WeatherManager::stopSounds() { + MWBase::SoundManager* sndMgr = MWBase::Environment::get().getSoundManager(); if (mAmbientSound) { - MWBase::Environment::get().getSoundManager()->stopSound(mAmbientSound); + sndMgr->stopSound(mAmbientSound); mAmbientSound = nullptr; } mPlayingAmbientSoundID = ESM::RefId(); if (mRainSound) { - MWBase::Environment::get().getSoundManager()->stopSound(mRainSound); + sndMgr->stopSound(mRainSound); mRainSound = nullptr; } mPlayingRainSoundID = ESM::RefId(); + + for (ESM::RefId soundId : mWeatherSettings[mCurrentWeather].mThunderSoundID) + if (!soundId.empty() && sndMgr->getSoundPlaying(MWWorld::ConstPtr(), soundId)) + sndMgr->stopSound3D(MWWorld::ConstPtr(), soundId); + + if (inTransition()) + for (ESM::RefId soundId : mWeatherSettings[mNextWeather].mThunderSoundID) + if (!soundId.empty() && sndMgr->getSoundPlaying(MWWorld::ConstPtr(), soundId)) + sndMgr->stopSound3D(MWWorld::ConstPtr(), soundId); } float WeatherManager::getWindSpeed() const diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index 327136a859..0643240dcd 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -162,6 +162,8 @@ namespace MWWorld // This is used for Rain and Thunderstorm ESM::RefId mRainLoopSoundID; + std::array mThunderSoundID; + // Is this an ash storm / blight storm? If so, the following will happen: // - The particles and clouds will be oriented so they appear to come from the Red Mountain. // - Characters will animate their hand to protect eyes from the storm when looking in its direction (idlestorm @@ -213,7 +215,6 @@ namespace MWWorld // non-zero values. float mThunderFrequency; float mThunderThreshold; - ESM::RefId mThunderSoundID[4]; float mFlashDecrement; float mFlashBrightness; From 9102fd4d578331ca7dd82274056a7f03be843090 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 8 Jan 2024 15:57:18 +0400 Subject: [PATCH 214/231] Remove unused code --- apps/openmw/mwsound/openal_output.cpp | 59 --------------------------- apps/openmw/mwsound/openal_output.hpp | 1 - apps/openmw/mwsound/sound_output.hpp | 1 - 3 files changed, 61 deletions(-) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 363a0d06b5..99003d5ce3 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -1034,65 +1034,6 @@ namespace MWSound return ret; } - void OpenAL_Output::setHrtf(const std::string& hrtfname, HrtfMode hrtfmode) - { - if (!mDevice || !ALC.SOFT_HRTF) - { - Log(Debug::Info) << "HRTF extension not present"; - return; - } - - LPALCGETSTRINGISOFT alcGetStringiSOFT = nullptr; - getALCFunc(alcGetStringiSOFT, mDevice, "alcGetStringiSOFT"); - - LPALCRESETDEVICESOFT alcResetDeviceSOFT = nullptr; - getALCFunc(alcResetDeviceSOFT, mDevice, "alcResetDeviceSOFT"); - - std::vector attrs; - attrs.reserve(15); - - attrs.push_back(ALC_HRTF_SOFT); - attrs.push_back(hrtfmode == HrtfMode::Disable ? ALC_FALSE - : hrtfmode == HrtfMode::Enable ? ALC_TRUE - : - /*hrtfmode == HrtfMode::Auto ?*/ ALC_DONT_CARE_SOFT); - if (!hrtfname.empty()) - { - ALCint index = -1; - ALCint num_hrtf; - alcGetIntegerv(mDevice, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtf); - for (ALCint i = 0; i < num_hrtf; ++i) - { - const ALCchar* entry = alcGetStringiSOFT(mDevice, ALC_HRTF_SPECIFIER_SOFT, i); - if (hrtfname == entry) - { - index = i; - break; - } - } - - if (index < 0) - Log(Debug::Warning) << "Failed to find HRTF name \"" << hrtfname << "\", using default"; - else - { - attrs.push_back(ALC_HRTF_ID_SOFT); - attrs.push_back(index); - } - } - attrs.push_back(0); - alcResetDeviceSOFT(mDevice, attrs.data()); - - ALCint hrtf_state; - alcGetIntegerv(mDevice, ALC_HRTF_SOFT, 1, &hrtf_state); - if (!hrtf_state) - Log(Debug::Info) << "HRTF disabled"; - else - { - const ALCchar* hrtf = alcGetString(mDevice, ALC_HRTF_SPECIFIER_SOFT); - Log(Debug::Info) << "Enabled HRTF " << hrtf; - } - } - std::pair OpenAL_Output::loadSound(const std::string& fname) { getALError(); diff --git a/apps/openmw/mwsound/openal_output.hpp b/apps/openmw/mwsound/openal_output.hpp index eed23ac659..7636f7bda9 100644 --- a/apps/openmw/mwsound/openal_output.hpp +++ b/apps/openmw/mwsound/openal_output.hpp @@ -84,7 +84,6 @@ namespace MWSound void deinit() override; std::vector enumerateHrtf() override; - void setHrtf(const std::string& hrtfname, HrtfMode hrtfmode) override; std::pair loadSound(const std::string& fname) override; size_t unloadSound(Sound_Handle data) override; diff --git a/apps/openmw/mwsound/sound_output.hpp b/apps/openmw/mwsound/sound_output.hpp index 8eec20bcba..df95f0909e 100644 --- a/apps/openmw/mwsound/sound_output.hpp +++ b/apps/openmw/mwsound/sound_output.hpp @@ -38,7 +38,6 @@ namespace MWSound virtual void deinit() = 0; virtual std::vector enumerateHrtf() = 0; - virtual void setHrtf(const std::string& hrtfname, HrtfMode hrtfmode) = 0; virtual std::pair loadSound(const std::string& fname) = 0; virtual size_t unloadSound(Sound_Handle data) = 0; From d1a7dfee877c4c2749f3c43e7f86087ccab68011 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 8 Jan 2024 16:19:40 +0400 Subject: [PATCH 215/231] Add missing assertion --- components/lua_ui/element.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index baa3438982..84383f89e1 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -280,6 +280,7 @@ namespace LuaUi auto children = parent->children(); auto it = std::find(children.begin(), children.end(), mRoot); mRoot = createWidget(layout(), 0); + assert(it != children.end()); *it = mRoot; parent->setChildren(children); mRoot->updateCoord(); From 6756b8613dce96fe72a1f98e0ced7b9fa4808e16 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Jan 2024 17:18:08 +0100 Subject: [PATCH 216/231] Restore beast race animations --- apps/openmw/mwrender/npcanimation.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 1559ebdd5d..0a837cbe11 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -510,7 +510,7 @@ namespace MWRender if (!isWerewolf) addAnimSource(base, smodel); - if (smodel != defaultSkeleton && base != defaultSkeleton) + if (!isBase || (isBase && base != defaultSkeleton)) addAnimSource(defaultSkeleton, smodel); if (!isBase) From 5043e67e06942bcf40d12bc9a36b7508497d8d39 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 1 Jan 2024 13:22:11 +0300 Subject: [PATCH 217/231] Replicate recent save loading prompt behavior (bug #7617) --- CHANGELOG.md | 1 + apps/openmw/mwstate/statemanagerimp.cpp | 38 ++++++++++++++++++++----- apps/openmw/mwstate/statemanagerimp.hpp | 1 + 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbd4e463f..00795b34cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -98,6 +98,7 @@ Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves Bug #7611: Beast races' idle animations slide after turning or jumping in place + Bug #7617: The death prompt asks the player if they wanted to load the character's last created save Bug #7619: Long map notes may get cut off Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 89b63c35e4..4358c4094e 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -64,6 +64,7 @@ void MWState::StateManager::cleanup(bool force) mState = State_NoGame; mCharacterManager.setCurrentCharacter(nullptr); mTimePlayed = 0; + mLastSavegame.clear(); MWMechanics::CreatureStats::cleanup(); } @@ -119,14 +120,27 @@ void MWState::StateManager::askLoadRecent() if (!mAskLoadRecent) { - const MWState::Character* character = getCurrentCharacter(); - if (!character || character->begin() == character->end()) // no saves + if (mLastSavegame.empty()) // no saves { MWBase::Environment::get().getWindowManager()->pushGuiMode(MWGui::GM_MainMenu); } else { - MWState::Slot lastSave = *character->begin(); + std::string saveName = Files::pathToUnicodeString(mLastSavegame.filename()); + // Assume the last saved game belongs to the current character's slot list. + const Character* character = getCurrentCharacter(); + if (character) + { + for (const auto& slot : *character) + { + if (slot.mPath == mLastSavegame) + { + saveName = slot.mProfile.mDescription; + break; + } + } + } + std::vector buttons; buttons.emplace_back("#{Interface:Yes}"); buttons.emplace_back("#{Interface:No}"); @@ -134,7 +148,7 @@ void MWState::StateManager::askLoadRecent() = MWBase::Environment::get().getL10nManager()->getMessage("OMWEngine", "AskLoadLastSave"); std::string_view tag = "%s"; size_t pos = message.find(tag); - message.replace(pos, tag.length(), lastSave.mProfile.mDescription); + message.replace(pos, tag.length(), saveName); MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); mAskLoadRecent = true; } @@ -322,6 +336,7 @@ void MWState::StateManager::saveGame(std::string_view description, const Slot* s throw std::runtime_error("Write operation failed (file stream)"); Settings::saves().mCharacter.set(Files::pathToUnicodeString(slot->mPath.parent_path().filename())); + mLastSavegame = slot->mPath; const auto finish = std::chrono::steady_clock::now(); @@ -561,6 +576,7 @@ void MWState::StateManager::loadGame(const Character* character, const std::file if (character) Settings::saves().mCharacter.set(Files::pathToUnicodeString(character->getPath().filename())); + mLastSavegame = filepath; MWBase::Environment::get().getWindowManager()->setNewGame(false); MWBase::Environment::get().getWorld()->saveLoaded(); @@ -639,7 +655,15 @@ void MWState::StateManager::quickLoad() void MWState::StateManager::deleteGame(const MWState::Character* character, const MWState::Slot* slot) { + const std::filesystem::path savePath = slot->mPath; mCharacterManager.deleteSlot(character, slot); + if (mLastSavegame == savePath) + { + if (character->begin() != character->end()) + mLastSavegame = character->begin()->mPath; + else + mLastSavegame.clear(); + } } MWState::Character* MWState::StateManager::getCurrentCharacter() @@ -670,9 +694,9 @@ void MWState::StateManager::update(float duration) { mAskLoadRecent = false; // Load last saved game for current character - - MWState::Slot lastSave = *curCharacter->begin(); - loadGame(curCharacter, lastSave.mPath); + // loadGame resets the game state along with mLastSavegame so we want to preserve it + const std::filesystem::path filePath = std::move(mLastSavegame); + loadGame(curCharacter, filePath); } else if (iButton == 1) { diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index c293209f34..a76b829e38 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -17,6 +17,7 @@ namespace MWState State mState; CharacterManager mCharacterManager; double mTimePlayed; + std::filesystem::path mLastSavegame; private: void cleanup(bool force = false); From 164b6309a76f5983a7267a641dfc45847e15fe8d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Jan 2024 18:08:17 +0100 Subject: [PATCH 218/231] Improve legibility --- apps/openmw/mwrender/npcanimation.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index 0a837cbe11..84522ee86e 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -510,11 +510,15 @@ namespace MWRender if (!isWerewolf) addAnimSource(base, smodel); - if (!isBase || (isBase && base != defaultSkeleton)) - addAnimSource(defaultSkeleton, smodel); - if (!isBase) + { + addAnimSource(defaultSkeleton, smodel); addAnimSource(smodel, smodel); + } + else if (base != defaultSkeleton) + { + addAnimSource(defaultSkeleton, smodel); + } if (!isWerewolf && isBeast && mNpc->mRace.contains("argonian")) addAnimSource("meshes\\xargonian_swimkna.nif", smodel); From 66f5d70550600027425c23c79864c21f5b959cf5 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 5 Jan 2024 12:57:04 +0100 Subject: [PATCH 219/231] Use settings values to declare shortcut settings --- apps/opencs/model/prefs/shortcutmanager.cpp | 4 +- apps/opencs/model/prefs/shortcutmanager.hpp | 6 +- apps/opencs/model/prefs/shortcutsetting.cpp | 2 +- apps/opencs/model/prefs/shortcutsetting.hpp | 3 +- apps/opencs/model/prefs/state.cpp | 281 ++++++++++---------- apps/opencs/model/prefs/state.hpp | 2 +- 6 files changed, 148 insertions(+), 150 deletions(-) diff --git a/apps/opencs/model/prefs/shortcutmanager.cpp b/apps/opencs/model/prefs/shortcutmanager.cpp index 3089281c91..d6686d31d9 100644 --- a/apps/opencs/model/prefs/shortcutmanager.cpp +++ b/apps/opencs/model/prefs/shortcutmanager.cpp @@ -43,7 +43,7 @@ namespace CSMPrefs mEventHandler->removeShortcut(shortcut); } - bool ShortcutManager::getSequence(const std::string& name, QKeySequence& sequence) const + bool ShortcutManager::getSequence(std::string_view name, QKeySequence& sequence) const { SequenceMap::const_iterator item = mSequences.find(name); if (item != mSequences.end()) @@ -56,7 +56,7 @@ namespace CSMPrefs return false; } - void ShortcutManager::setSequence(const std::string& name, const QKeySequence& sequence) + void ShortcutManager::setSequence(std::string_view name, const QKeySequence& sequence) { // Add to map/modify SequenceMap::iterator item = mSequences.find(name); diff --git a/apps/opencs/model/prefs/shortcutmanager.hpp b/apps/opencs/model/prefs/shortcutmanager.hpp index 87d2f2256c..0cfe3ad86a 100644 --- a/apps/opencs/model/prefs/shortcutmanager.hpp +++ b/apps/opencs/model/prefs/shortcutmanager.hpp @@ -28,8 +28,8 @@ namespace CSMPrefs /// The shortcut class will do this automatically void removeShortcut(Shortcut* shortcut); - bool getSequence(const std::string& name, QKeySequence& sequence) const; - void setSequence(const std::string& name, const QKeySequence& sequence); + bool getSequence(std::string_view name, QKeySequence& sequence) const; + void setSequence(std::string_view name, const QKeySequence& sequence); bool getModifier(const std::string& name, int& modifier) const; void setModifier(std::string_view name, int modifier); @@ -50,7 +50,7 @@ namespace CSMPrefs private: // Need a multimap in case multiple shortcuts share the same name typedef std::multimap> ShortcutMap; - typedef std::map SequenceMap; + typedef std::map> SequenceMap; typedef std::map> ModifierMap; typedef std::map NameMap; typedef std::map KeyMap; diff --git a/apps/opencs/model/prefs/shortcutsetting.cpp b/apps/opencs/model/prefs/shortcutsetting.cpp index 33bb521290..bdaf3a0fda 100644 --- a/apps/opencs/model/prefs/shortcutsetting.cpp +++ b/apps/opencs/model/prefs/shortcutsetting.cpp @@ -19,7 +19,7 @@ namespace CSMPrefs { ShortcutSetting::ShortcutSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index) + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index) : TypedSetting(parent, mutex, key, label, index) , mButton(nullptr) , mEditorActive(false) diff --git a/apps/opencs/model/prefs/shortcutsetting.hpp b/apps/opencs/model/prefs/shortcutsetting.hpp index 84857a9bc7..bcb7b89488 100644 --- a/apps/opencs/model/prefs/shortcutsetting.hpp +++ b/apps/opencs/model/prefs/shortcutsetting.hpp @@ -2,6 +2,7 @@ #define CSM_PREFS_SHORTCUTSETTING_H #include +#include #include #include @@ -24,7 +25,7 @@ namespace CSMPrefs public: explicit ShortcutSetting( - Category* parent, QMutex* mutex, const std::string& key, const QString& label, Settings::Index& index); + Category* parent, QMutex* mutex, std::string_view key, const QString& label, Settings::Index& index); SettingWidgets makeWidgets(QWidget* parent) override; diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 13a2a35cc5..c11996a6ea 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -250,157 +250,154 @@ void CSMPrefs::State::declare() declareCategory("Key Bindings"); declareSubcategory("Document"); - declareShortcut("document-file-newgame", "New Game", QKeySequence(Qt::ControlModifier | Qt::Key_N)); - declareShortcut("document-file-newaddon", "New Addon", QKeySequence()); - declareShortcut("document-file-open", "Open", QKeySequence(Qt::ControlModifier | Qt::Key_O)); - declareShortcut("document-file-save", "Save", QKeySequence(Qt::ControlModifier | Qt::Key_S)); - declareShortcut("document-help-help", "Help", QKeySequence(Qt::Key_F1)); - declareShortcut("document-help-tutorial", "Tutorial", QKeySequence()); - declareShortcut("document-file-verify", "Verify", QKeySequence()); - declareShortcut("document-file-merge", "Merge", QKeySequence()); - declareShortcut("document-file-errorlog", "Open Load Error Log", QKeySequence()); - declareShortcut("document-file-metadata", "Meta Data", QKeySequence()); - declareShortcut("document-file-close", "Close Document", QKeySequence(Qt::ControlModifier | Qt::Key_W)); - declareShortcut("document-file-exit", "Exit Application", QKeySequence(Qt::ControlModifier | Qt::Key_Q)); - declareShortcut("document-edit-undo", "Undo", QKeySequence(Qt::ControlModifier | Qt::Key_Z)); - declareShortcut("document-edit-redo", "Redo", QKeySequence(Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_Z)); - declareShortcut("document-edit-preferences", "Open Preferences", QKeySequence()); - declareShortcut("document-edit-search", "Search", QKeySequence(Qt::ControlModifier | Qt::Key_F)); - declareShortcut("document-view-newview", "New View", QKeySequence()); - declareShortcut("document-view-statusbar", "Toggle Status Bar", QKeySequence()); - declareShortcut("document-view-filters", "Open Filter List", QKeySequence()); - declareShortcut("document-world-regions", "Open Region List", QKeySequence()); - declareShortcut("document-world-cells", "Open Cell List", QKeySequence()); - declareShortcut("document-world-referencables", "Open Object List", QKeySequence()); - declareShortcut("document-world-references", "Open Instance List", QKeySequence()); - declareShortcut("document-world-lands", "Open Lands List", QKeySequence()); - declareShortcut("document-world-landtextures", "Open Land Textures List", QKeySequence()); - declareShortcut("document-world-pathgrid", "Open Pathgrid List", QKeySequence()); - declareShortcut("document-world-regionmap", "Open Region Map", QKeySequence()); - declareShortcut("document-mechanics-globals", "Open Global List", QKeySequence()); - declareShortcut("document-mechanics-gamesettings", "Open Game Settings", QKeySequence()); - declareShortcut("document-mechanics-scripts", "Open Script List", QKeySequence()); - declareShortcut("document-mechanics-spells", "Open Spell List", QKeySequence()); - declareShortcut("document-mechanics-enchantments", "Open Enchantment List", QKeySequence()); - declareShortcut("document-mechanics-magiceffects", "Open Magic Effect List", QKeySequence()); - declareShortcut("document-mechanics-startscripts", "Open Start Script List", QKeySequence()); - declareShortcut("document-character-skills", "Open Skill List", QKeySequence()); - declareShortcut("document-character-classes", "Open Class List", QKeySequence()); - declareShortcut("document-character-factions", "Open Faction List", QKeySequence()); - declareShortcut("document-character-races", "Open Race List", QKeySequence()); - declareShortcut("document-character-birthsigns", "Open Birthsign List", QKeySequence()); - declareShortcut("document-character-topics", "Open Topic List", QKeySequence()); - declareShortcut("document-character-journals", "Open Journal List", QKeySequence()); - declareShortcut("document-character-topicinfos", "Open Topic Info List", QKeySequence()); - declareShortcut("document-character-journalinfos", "Open Journal Info List", QKeySequence()); - declareShortcut("document-character-bodyparts", "Open Body Part List", QKeySequence()); - declareShortcut("document-assets-reload", "Reload Assets", QKeySequence(Qt::Key_F5)); - declareShortcut("document-assets-sounds", "Open Sound Asset List", QKeySequence()); - declareShortcut("document-assets-soundgens", "Open Sound Generator List", QKeySequence()); - declareShortcut("document-assets-meshes", "Open Mesh Asset List", QKeySequence()); - declareShortcut("document-assets-icons", "Open Icon Asset List", QKeySequence()); - declareShortcut("document-assets-music", "Open Music Asset List", QKeySequence()); - declareShortcut("document-assets-soundres", "Open Sound File List", QKeySequence()); - declareShortcut("document-assets-textures", "Open Texture Asset List", QKeySequence()); - declareShortcut("document-assets-videos", "Open Video Asset List", QKeySequence()); - declareShortcut("document-debug-run", "Run Debug", QKeySequence()); - declareShortcut("document-debug-shutdown", "Stop Debug", QKeySequence()); - declareShortcut("document-debug-profiles", "Debug Profiles", QKeySequence()); - declareShortcut("document-debug-runlog", "Open Run Log", QKeySequence()); + declareShortcut(mValues->mKeyBindings.mDocumentFileNewgame, "New Game"); + declareShortcut(mValues->mKeyBindings.mDocumentFileNewaddon, "New Addon"); + declareShortcut(mValues->mKeyBindings.mDocumentFileOpen, "Open"); + declareShortcut(mValues->mKeyBindings.mDocumentFileSave, "Save"); + declareShortcut(mValues->mKeyBindings.mDocumentHelpHelp, "Help"); + declareShortcut(mValues->mKeyBindings.mDocumentHelpTutorial, "Tutorial"); + declareShortcut(mValues->mKeyBindings.mDocumentFileVerify, "Verify"); + declareShortcut(mValues->mKeyBindings.mDocumentFileMerge, "Merge"); + declareShortcut(mValues->mKeyBindings.mDocumentFileErrorlog, "Open Load Error Log"); + declareShortcut(mValues->mKeyBindings.mDocumentFileMetadata, "Meta Data"); + declareShortcut(mValues->mKeyBindings.mDocumentFileClose, "Close Document"); + declareShortcut(mValues->mKeyBindings.mDocumentFileExit, "Exit Application"); + declareShortcut(mValues->mKeyBindings.mDocumentEditUndo, "Undo"); + declareShortcut(mValues->mKeyBindings.mDocumentEditRedo, "Redo"); + declareShortcut(mValues->mKeyBindings.mDocumentEditPreferences, "Open Preferences"); + declareShortcut(mValues->mKeyBindings.mDocumentEditSearch, "Search"); + declareShortcut(mValues->mKeyBindings.mDocumentViewNewview, "New View"); + declareShortcut(mValues->mKeyBindings.mDocumentViewStatusbar, "Toggle Status Bar"); + declareShortcut(mValues->mKeyBindings.mDocumentViewFilters, "Open Filter List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldRegions, "Open Region List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldCells, "Open Cell List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldReferencables, "Open Object List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldReferences, "Open Instance List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldLands, "Open Lands List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldLandtextures, "Open Land Textures List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldPathgrid, "Open Pathgrid List"); + declareShortcut(mValues->mKeyBindings.mDocumentWorldRegionmap, "Open Region Map"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsGlobals, "Open Global List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsGamesettings, "Open Game Settings"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsScripts, "Open Script List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsSpells, "Open Spell List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsEnchantments, "Open Enchantment List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsMagiceffects, "Open Magic Effect List"); + declareShortcut(mValues->mKeyBindings.mDocumentMechanicsStartscripts, "Open Start Script List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterSkills, "Open Skill List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterClasses, "Open Class List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterFactions, "Open Faction List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterRaces, "Open Race List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterBirthsigns, "Open Birthsign List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterTopics, "Open Topic List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterJournals, "Open Journal List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterTopicinfos, "Open Topic Info List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterJournalinfos, "Open Journal Info List"); + declareShortcut(mValues->mKeyBindings.mDocumentCharacterBodyparts, "Open Body Part List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsReload, "Reload Assets"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsSounds, "Open Sound Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsSoundgens, "Open Sound Generator List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsMeshes, "Open Mesh Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsIcons, "Open Icon Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsMusic, "Open Music Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsSoundres, "Open Sound File List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsTextures, "Open Texture Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentAssetsVideos, "Open Video Asset List"); + declareShortcut(mValues->mKeyBindings.mDocumentDebugRun, "Run Debug"); + declareShortcut(mValues->mKeyBindings.mDocumentDebugShutdown, "Stop Debug"); + declareShortcut(mValues->mKeyBindings.mDocumentDebugProfiles, "Debug Profiles"); + declareShortcut(mValues->mKeyBindings.mDocumentDebugRunlog, "Open Run Log"); declareSubcategory("Table"); - declareShortcut("table-edit", "Edit Record", QKeySequence()); - declareShortcut("table-add", "Add Row/Record", QKeySequence(Qt::ShiftModifier | Qt::Key_A)); - declareShortcut("table-clone", "Clone Record", QKeySequence(Qt::ShiftModifier | Qt::Key_D)); - declareShortcut("touch-record", "Touch Record", QKeySequence()); - declareShortcut("table-revert", "Revert Record", QKeySequence()); - declareShortcut("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete)); - declareShortcut("table-moveup", "Move Record Up", QKeySequence()); - declareShortcut("table-movedown", "Move Record Down", QKeySequence()); - declareShortcut("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); - declareShortcut("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V)); - declareShortcut("table-extendeddelete", "Extended Record Deletion", QKeySequence()); - declareShortcut("table-extendedrevert", "Extended Record Revertion", QKeySequence()); + declareShortcut(mValues->mKeyBindings.mTableEdit, "Edit Record"); + declareShortcut(mValues->mKeyBindings.mTableAdd, "Add Row/Record"); + declareShortcut(mValues->mKeyBindings.mTableClone, "Clone Record"); + declareShortcut(mValues->mKeyBindings.mTouchRecord, "Touch Record"); + declareShortcut(mValues->mKeyBindings.mTableRevert, "Revert Record"); + declareShortcut(mValues->mKeyBindings.mTableRemove, "Remove Row/Record"); + declareShortcut(mValues->mKeyBindings.mTableMoveup, "Move Record Up"); + declareShortcut(mValues->mKeyBindings.mTableMovedown, "Move Record Down"); + declareShortcut(mValues->mKeyBindings.mTableView, "View Record"); + declareShortcut(mValues->mKeyBindings.mTablePreview, "Preview Record"); + declareShortcut(mValues->mKeyBindings.mTableExtendeddelete, "Extended Record Deletion"); + declareShortcut(mValues->mKeyBindings.mTableExtendedrevert, "Extended Record Revertion"); declareSubcategory("Report Table"); - declareShortcut("reporttable-show", "Show Report", QKeySequence()); - declareShortcut("reporttable-remove", "Remove Report", QKeySequence(Qt::Key_Delete)); - declareShortcut("reporttable-replace", "Replace Report", QKeySequence()); - declareShortcut("reporttable-refresh", "Refresh Report", QKeySequence()); + declareShortcut(mValues->mKeyBindings.mReporttableShow, "Show Report"); + declareShortcut(mValues->mKeyBindings.mReporttableRemove, "Remove Report"); + declareShortcut(mValues->mKeyBindings.mReporttableReplace, "Replace Report"); + declareShortcut(mValues->mKeyBindings.mReporttableRefresh, "Refresh Report"); declareSubcategory("Scene"); - declareShortcut("scene-navi-primary", "Camera Rotation From Mouse Movement", QKeySequence(Qt::LeftButton)); - declareShortcut("scene-navi-secondary", "Camera Translation From Mouse Movement", - QKeySequence(Qt::ControlModifier | (int)Qt::LeftButton)); - declareShortcut("scene-open-primary", "Primary Open", QKeySequence(Qt::ShiftModifier | (int)Qt::LeftButton)); - declareShortcut("scene-edit-primary", "Primary Edit", QKeySequence(Qt::RightButton)); - declareShortcut("scene-edit-secondary", "Secondary Edit", QKeySequence(Qt::ControlModifier | (int)Qt::RightButton)); - declareShortcut("scene-select-primary", "Primary Select", QKeySequence(Qt::MiddleButton)); - declareShortcut( - "scene-select-secondary", "Secondary Select", QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton)); - declareShortcut( - "scene-select-tertiary", "Tertiary Select", QKeySequence(Qt::ShiftModifier | (int)Qt::MiddleButton)); + declareShortcut(mValues->mKeyBindings.mSceneNaviPrimary, "Camera Rotation From Mouse Movement"); + declareShortcut(mValues->mKeyBindings.mSceneNaviSecondary, "Camera Translation From Mouse Movement"); + declareShortcut(mValues->mKeyBindings.mSceneOpenPrimary, "Primary Open"); + declareShortcut(mValues->mKeyBindings.mSceneEditPrimary, "Primary Edit"); + declareShortcut(mValues->mKeyBindings.mSceneEditSecondary, "Secondary Edit"); + declareShortcut(mValues->mKeyBindings.mSceneSelectPrimary, "Primary Select"); + declareShortcut(mValues->mKeyBindings.mSceneSelectSecondary, "Secondary Select"); + declareShortcut(mValues->mKeyBindings.mSceneSelectTertiary, "Tertiary Select"); declareModifier(mValues->mKeyBindings.mSceneSpeedModifier, "Speed Modifier"); - declareShortcut("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete)); - declareShortcut("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G)); - declareShortcut("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H)); - declareShortcut("scene-instance-drop-terrain-separately", "Drop to terrain level separately", QKeySequence()); - declareShortcut("scene-instance-drop-collision-separately", "Drop to collision separately", QKeySequence()); - declareShortcut("scene-load-cam-cell", "Load Camera Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_5)); - declareShortcut("scene-load-cam-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6)); - declareShortcut("scene-load-cam-northcell", "Load North Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_8)); - declareShortcut("scene-load-cam-westcell", "Load West Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_4)); - declareShortcut("scene-load-cam-southcell", "Load South Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_2)); - declareShortcut("scene-edit-abort", "Abort", QKeySequence(Qt::Key_Escape)); - declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); - declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); - declareShortcut("scene-duplicate", "Duplicate Instance", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); - declareShortcut("scene-clear-selection", "Clear Selection", QKeySequence(Qt::Key_Space)); - declareShortcut("scene-unhide-all", "Unhide All Objects", QKeySequence(Qt::AltModifier | Qt::Key_H)); - declareShortcut("scene-toggle-visibility", "Toggle Selection Visibility", QKeySequence(Qt::Key_H)); - declareShortcut("scene-group-1", "Select Group 1", QKeySequence(Qt::Key_1)); - declareShortcut("scene-save-1", "Save Group 1", QKeySequence(Qt::ControlModifier | Qt::Key_1)); - declareShortcut("scene-group-2", "Select Group 2", QKeySequence(Qt::Key_2)); - declareShortcut("scene-save-2", "Save Group 2", QKeySequence(Qt::ControlModifier | Qt::Key_2)); - declareShortcut("scene-group-3", "Select Group 3", QKeySequence(Qt::Key_3)); - declareShortcut("scene-save-3", "Save Group 3", QKeySequence(Qt::ControlModifier | Qt::Key_3)); - declareShortcut("scene-group-4", "Select Group 4", QKeySequence(Qt::Key_4)); - declareShortcut("scene-save-4", "Save Group 4", QKeySequence(Qt::ControlModifier | Qt::Key_4)); - declareShortcut("scene-group-5", "Selection Group 5", QKeySequence(Qt::Key_5)); - declareShortcut("scene-save-5", "Save Group 5", QKeySequence(Qt::ControlModifier | Qt::Key_5)); - declareShortcut("scene-group-6", "Selection Group 6", QKeySequence(Qt::Key_6)); - declareShortcut("scene-save-6", "Save Group 6", QKeySequence(Qt::ControlModifier | Qt::Key_6)); - declareShortcut("scene-group-7", "Selection Group 7", QKeySequence(Qt::Key_7)); - declareShortcut("scene-save-7", "Save Group 7", QKeySequence(Qt::ControlModifier | Qt::Key_7)); - declareShortcut("scene-group-8", "Selection Group 8", QKeySequence(Qt::Key_8)); - declareShortcut("scene-save-8", "Save Group 8", QKeySequence(Qt::ControlModifier | Qt::Key_8)); - declareShortcut("scene-group-9", "Selection Group 9", QKeySequence(Qt::Key_9)); - declareShortcut("scene-save-9", "Save Group 9", QKeySequence(Qt::ControlModifier | Qt::Key_9)); - declareShortcut("scene-group-0", "Selection Group 10", QKeySequence(Qt::Key_0)); - declareShortcut("scene-save-0", "Save Group 10", QKeySequence(Qt::ControlModifier | Qt::Key_0)); + declareShortcut(mValues->mKeyBindings.mSceneDelete, "Delete Instance"); + declareShortcut(mValues->mKeyBindings.mSceneInstanceDropTerrain, "Drop to terrain level"); + declareShortcut(mValues->mKeyBindings.mSceneInstanceDropCollision, "Drop to collision"); + declareShortcut(mValues->mKeyBindings.mSceneInstanceDropTerrainSeparately, "Drop to terrain level separately"); + declareShortcut(mValues->mKeyBindings.mSceneInstanceDropCollisionSeparately, "Drop to collision separately"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamCell, "Load Camera Cell"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamEastcell, "Load East Cell"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamNorthcell, "Load North Cell"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamWestcell, "Load West Cell"); + declareShortcut(mValues->mKeyBindings.mSceneLoadCamSouthcell, "Load South Cell"); + declareShortcut(mValues->mKeyBindings.mSceneEditAbort, "Abort"); + declareShortcut(mValues->mKeyBindings.mSceneFocusToolbar, "Toggle Toolbar Focus"); + declareShortcut(mValues->mKeyBindings.mSceneRenderStats, "Debug Rendering Stats"); + declareShortcut(mValues->mKeyBindings.mSceneDuplicate, "Duplicate Instance"); + declareShortcut(mValues->mKeyBindings.mSceneClearSelection, "Clear Selection"); + declareShortcut(mValues->mKeyBindings.mSceneUnhideAll, "Unhide All Objects"); + declareShortcut(mValues->mKeyBindings.mSceneToggleVisibility, "Toggle Selection Visibility"); + declareShortcut(mValues->mKeyBindings.mSceneGroup0, "Selection Group 0"); + declareShortcut(mValues->mKeyBindings.mSceneSave0, "Save Group 0"); + declareShortcut(mValues->mKeyBindings.mSceneGroup1, "Select Group 1"); + declareShortcut(mValues->mKeyBindings.mSceneSave1, "Save Group 1"); + declareShortcut(mValues->mKeyBindings.mSceneGroup2, "Select Group 2"); + declareShortcut(mValues->mKeyBindings.mSceneSave2, "Save Group 2"); + declareShortcut(mValues->mKeyBindings.mSceneGroup3, "Select Group 3"); + declareShortcut(mValues->mKeyBindings.mSceneSave3, "Save Group 3"); + declareShortcut(mValues->mKeyBindings.mSceneGroup4, "Select Group 4"); + declareShortcut(mValues->mKeyBindings.mSceneSave4, "Save Group 4"); + declareShortcut(mValues->mKeyBindings.mSceneGroup5, "Selection Group 5"); + declareShortcut(mValues->mKeyBindings.mSceneSave5, "Save Group 5"); + declareShortcut(mValues->mKeyBindings.mSceneGroup6, "Selection Group 6"); + declareShortcut(mValues->mKeyBindings.mSceneSave6, "Save Group 6"); + declareShortcut(mValues->mKeyBindings.mSceneGroup7, "Selection Group 7"); + declareShortcut(mValues->mKeyBindings.mSceneSave7, "Save Group 7"); + declareShortcut(mValues->mKeyBindings.mSceneGroup8, "Selection Group 8"); + declareShortcut(mValues->mKeyBindings.mSceneSave8, "Save Group 8"); + declareShortcut(mValues->mKeyBindings.mSceneGroup9, "Selection Group 9"); + declareShortcut(mValues->mKeyBindings.mSceneSave9, "Save Group 9"); declareSubcategory("1st/Free Camera"); - declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W)); - declareShortcut("free-backward", "Backward", QKeySequence(Qt::Key_S)); - declareShortcut("free-left", "Left", QKeySequence(Qt::Key_A)); - declareShortcut("free-right", "Right", QKeySequence(Qt::Key_D)); - declareShortcut("free-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); - declareShortcut("free-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); - declareShortcut("free-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); + declareShortcut(mValues->mKeyBindings.mFreeForward, "Forward"); + declareShortcut(mValues->mKeyBindings.mFreeBackward, "Backward"); + declareShortcut(mValues->mKeyBindings.mFreeLeft, "Left"); + declareShortcut(mValues->mKeyBindings.mFreeRight, "Right"); + declareShortcut(mValues->mKeyBindings.mFreeRollLeft, "Roll Left"); + declareShortcut(mValues->mKeyBindings.mFreeRollRight, "Roll Right"); + declareShortcut(mValues->mKeyBindings.mFreeSpeedMode, "Toggle Speed Mode"); declareSubcategory("Orbit Camera"); - declareShortcut("orbit-up", "Up", QKeySequence(Qt::Key_W)); - declareShortcut("orbit-down", "Down", QKeySequence(Qt::Key_S)); - declareShortcut("orbit-left", "Left", QKeySequence(Qt::Key_A)); - declareShortcut("orbit-right", "Right", QKeySequence(Qt::Key_D)); - declareShortcut("orbit-roll-left", "Roll Left", QKeySequence(Qt::Key_Q)); - declareShortcut("orbit-roll-right", "Roll Right", QKeySequence(Qt::Key_E)); - declareShortcut("orbit-speed-mode", "Toggle Speed Mode", QKeySequence(Qt::Key_F)); - declareShortcut("orbit-center-selection", "Center On Selected", QKeySequence(Qt::Key_C)); + declareShortcut(mValues->mKeyBindings.mOrbitUp, "Up"); + declareShortcut(mValues->mKeyBindings.mOrbitDown, "Down"); + declareShortcut(mValues->mKeyBindings.mOrbitLeft, "Left"); + declareShortcut(mValues->mKeyBindings.mOrbitRight, "Right"); + declareShortcut(mValues->mKeyBindings.mOrbitRollLeft, "Roll Left"); + declareShortcut(mValues->mKeyBindings.mOrbitRollRight, "Roll Right"); + declareShortcut(mValues->mKeyBindings.mOrbitSpeedMode, "Toggle Speed Mode"); + declareShortcut(mValues->mKeyBindings.mOrbitCenterSelection, "Center On Selected"); declareSubcategory("Script Editor"); - declareShortcut("script-editor-comment", "Comment Selection", QKeySequence()); - declareShortcut("script-editor-uncomment", "Uncomment Selection", QKeySequence()); + declareShortcut(mValues->mKeyBindings.mScriptEditorComment, "Comment Selection"); + declareShortcut(mValues->mKeyBindings.mScriptEditorUncomment, "Uncomment Selection"); declareCategory("Models"); declareString(mValues->mModels.mBaseanim, "base animations").setTooltip("3rd person base model with textkeys-data"); @@ -492,7 +489,7 @@ CSMPrefs::ColourSetting& CSMPrefs::State::declareColour( } CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( - const std::string& key, const QString& label, const QKeySequence& default_) + Settings::SettingValue& value, const QString& label) { if (mCurrentCategory == mCategories.end()) throw std::logic_error("no category for setting"); @@ -500,11 +497,11 @@ CSMPrefs::ShortcutSetting& CSMPrefs::State::declareShortcut( // Setup with actual data QKeySequence sequence; - getShortcutManager().convertFromString(mIndex->get(mCurrentCategory->second.getKey(), key), sequence); - getShortcutManager().setSequence(key, sequence); + getShortcutManager().convertFromString(value, sequence); + getShortcutManager().setSequence(value.mName, sequence); CSMPrefs::ShortcutSetting* setting - = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, key, label, *mIndex); + = new CSMPrefs::ShortcutSetting(&mCurrentCategory->second, &mMutex, value.mName, label, *mIndex); mCurrentCategory->second.addSetting(setting); return *setting; diff --git a/apps/opencs/model/prefs/state.hpp b/apps/opencs/model/prefs/state.hpp index ce6e89ef08..821322d586 100644 --- a/apps/opencs/model/prefs/state.hpp +++ b/apps/opencs/model/prefs/state.hpp @@ -74,7 +74,7 @@ namespace CSMPrefs ColourSetting& declareColour(Settings::SettingValue& value, const QString& label); - ShortcutSetting& declareShortcut(const std::string& key, const QString& label, const QKeySequence& default_); + ShortcutSetting& declareShortcut(Settings::SettingValue& value, const QString& label); StringSetting& declareString(Settings::SettingValue& value, const QString& label); From 9e3b427a985aff66f18a5fa05be610cd154b4e5c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 8 Jan 2024 22:56:58 +0300 Subject: [PATCH 220/231] Unbreak un-paging (#7768) --- apps/openmw/mwworld/scene.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 72b2dc3022..80f52f2375 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -111,11 +111,16 @@ namespace std::string model = getModel(ptr); const auto rotation = makeDirectNodeRotation(ptr); + // Null node meant to distinguish objects that aren't in the scene from paged objects + // TODO: find a more clever way to make paging exclusion more reliable? + static const osg::ref_ptr pagedNode( + new SceneUtil::PositionAttitudeTransform); + ESM::RefNum refnum = ptr.getCellRef().getRefNum(); if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum)) ptr.getClass().insertObjectRendering(ptr, model, rendering); else - ptr.getRefData().setBaseNode(nullptr); + ptr.getRefData().setBaseNode(pagedNode); setNodeRotation(ptr, rendering, rotation); if (ptr.getClass().useAnim()) From d16b1ca54ef81fe4adaa5b9d759cf76eaca73b61 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 8 Jan 2024 22:29:59 +0100 Subject: [PATCH 221/231] make macos use openal-soft --- CI/before_install.osx.sh | 3 +-- CI/before_script.osx.sh | 3 ++- CMakeLists.txt | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index e340d501ed..dd54030dfb 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -19,9 +19,8 @@ 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" - # Install deps -brew install icu4c yaml-cpp sqlite +brew install openal-soft icu4c yaml-cpp sqlite ccache --version cmake --version diff --git a/CI/before_script.osx.sh b/CI/before_script.osx.sh index c956f27514..cab67b6e4d 100755 --- a/CI/before_script.osx.sh +++ b/CI/before_script.osx.sh @@ -10,12 +10,13 @@ DEPENDENCIES_ROOT="/tmp/openmw-deps" QT_PATH=$(brew --prefix qt@5) ICU_PATH=$(brew --prefix icu4c) +OPENAL_PATH=$(brew --prefix openal-soft) CCACHE_EXECUTABLE=$(brew --prefix ccache)/bin/ccache mkdir build cd build cmake \ --D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH" \ +-D CMAKE_PREFIX_PATH="$DEPENDENCIES_ROOT;$QT_PATH;$OPENAL_PATH" \ -D CMAKE_C_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \ -D CMAKE_CXX_COMPILER_LAUNCHER="$CCACHE_EXECUTABLE" \ -D CMAKE_CXX_FLAGS="-stdlib=libc++" \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 34df0216da..28109bd01b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,7 @@ IF(NOT CMAKE_BUILD_TYPE) ENDIF() if (APPLE) + set(CMAKE_FIND_FRAMEWORK LAST) # prefer dylibs over frameworks set(APP_BUNDLE_NAME "${CMAKE_PROJECT_NAME}.app") set(APP_BUNDLE_DIR "${OpenMW_BINARY_DIR}/${APP_BUNDLE_NAME}") From ad0ad625e5936090352f9ecc87093bfb566c95c7 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 20 Feb 2023 23:55:49 +0100 Subject: [PATCH 222/231] Use single global static variable in Npc::getSoundIdFromSndGen for all parts To eliminate checks for local static variable initialization. --- apps/openmw/mwclass/npc.cpp | 46 ++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 330a48daeb..4f295f7b35 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -61,6 +61,23 @@ namespace { + struct NpcParts + { + const ESM::RefId mSwimLeft = ESM::RefId::stringRefId("Swim Left"); + const ESM::RefId mSwimRight = ESM::RefId::stringRefId("Swim Right"); + const ESM::RefId mFootWaterLeft = ESM::RefId::stringRefId("FootWaterLeft"); + const ESM::RefId mFootWaterRight = ESM::RefId::stringRefId("FootWaterRight"); + const ESM::RefId mFootBareLeft = ESM::RefId::stringRefId("FootBareLeft"); + const ESM::RefId mFootBareRight = ESM::RefId::stringRefId("FootBareRight"); + const ESM::RefId mFootLightLeft = ESM::RefId::stringRefId("footLightLeft"); + const ESM::RefId mFootLightRight = ESM::RefId::stringRefId("footLightRight"); + const ESM::RefId mFootMediumRight = ESM::RefId::stringRefId("FootMedRight"); + const ESM::RefId mFootMediumLeft = ESM::RefId::stringRefId("FootMedLeft"); + const ESM::RefId mFootHeavyLeft = ESM::RefId::stringRefId("footHeavyLeft"); + const ESM::RefId mFootHeavyRight = ESM::RefId::stringRefId("footHeavyRight"); + }; + + const NpcParts npcParts; int is_even(double d) { @@ -1225,19 +1242,6 @@ namespace MWClass ESM::RefId Npc::getSoundIdFromSndGen(const MWWorld::Ptr& ptr, std::string_view name) const { - static const ESM::RefId swimLeft = ESM::RefId::stringRefId("Swim Left"); - static const ESM::RefId swimRight = ESM::RefId::stringRefId("Swim Right"); - static const ESM::RefId footWaterLeft = ESM::RefId::stringRefId("FootWaterLeft"); - static const ESM::RefId footWaterRight = ESM::RefId::stringRefId("FootWaterRight"); - static const ESM::RefId footBareLeft = ESM::RefId::stringRefId("FootBareLeft"); - static const ESM::RefId footBareRight = ESM::RefId::stringRefId("FootBareRight"); - static const ESM::RefId footLightLeft = ESM::RefId::stringRefId("footLightLeft"); - static const ESM::RefId footLightRight = ESM::RefId::stringRefId("footLightRight"); - static const ESM::RefId footMediumRight = ESM::RefId::stringRefId("FootMedRight"); - static const ESM::RefId footMediumLeft = ESM::RefId::stringRefId("FootMedLeft"); - static const ESM::RefId footHeavyLeft = ESM::RefId::stringRefId("footHeavyLeft"); - static const ESM::RefId footHeavyRight = ESM::RefId::stringRefId("footHeavyRight"); - if (name == "left" || name == "right") { MWBase::World* world = MWBase::Environment::get().getWorld(); @@ -1245,9 +1249,9 @@ namespace MWClass return ESM::RefId(); osg::Vec3f pos(ptr.getRefData().getPosition().asVec3()); if (world->isSwimming(ptr)) - return (name == "left") ? swimLeft : swimRight; + return (name == "left") ? npcParts.mSwimLeft : npcParts.mSwimRight; if (world->isUnderwater(ptr.getCell(), pos) || world->isWalkingOnWater(ptr)) - return (name == "left") ? footWaterLeft : footWaterRight; + return (name == "left") ? npcParts.mFootWaterLeft : npcParts.mFootWaterRight; if (world->isOnGround(ptr)) { if (getNpcStats(ptr).isWerewolf() @@ -1262,15 +1266,15 @@ namespace MWClass const MWWorld::InventoryStore& inv = Npc::getInventoryStore(ptr); MWWorld::ConstContainerStoreIterator boots = inv.getSlot(MWWorld::InventoryStore::Slot_Boots); if (boots == inv.end() || boots->getType() != ESM::Armor::sRecordId) - return (name == "left") ? footBareLeft : footBareRight; + return (name == "left") ? npcParts.mFootBareLeft : npcParts.mFootBareRight; ESM::RefId skill = boots->getClass().getEquipmentSkill(*boots); if (skill == ESM::Skill::LightArmor) - return (name == "left") ? footLightLeft : footLightRight; + return (name == "left") ? npcParts.mFootLightLeft : npcParts.mFootLightRight; else if (skill == ESM::Skill::MediumArmor) - return (name == "left") ? footMediumLeft : footMediumRight; + return (name == "left") ? npcParts.mFootMediumLeft : npcParts.mFootMediumRight; else if (skill == ESM::Skill::HeavyArmor) - return (name == "left") ? footHeavyLeft : footHeavyRight; + return (name == "left") ? npcParts.mFootHeavyLeft : npcParts.mFootHeavyRight; } return ESM::RefId(); } @@ -1279,9 +1283,9 @@ namespace MWClass if (name == "land") return ESM::RefId(); if (name == "swimleft") - return swimLeft; + return npcParts.mSwimLeft; if (name == "swimright") - return swimRight; + return npcParts.mSwimRight; // TODO: I have no idea what these are supposed to do for NPCs since they use // voiced dialog for various conditions like health loss and combat taunts. Maybe // only for biped creatures? From fbd99583ca6cb120ebfd67cce7d38848529dc4ae Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 9 Jan 2024 16:11:44 +0400 Subject: [PATCH 223/231] Move local variables in GUI code --- apps/openmw/mwgui/alchemywindow.cpp | 4 +++- apps/openmw/mwgui/bookpage.cpp | 2 +- apps/openmw/mwgui/console.cpp | 2 +- apps/openmw/mwgui/formatting.cpp | 2 +- apps/openmw/mwgui/spellcreationdialog.cpp | 2 +- apps/openmw/mwgui/spellicons.cpp | 2 +- apps/openmw/mwgui/tooltips.cpp | 2 +- 7 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index 4215782e2f..5a6245fca0 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -395,8 +395,10 @@ namespace MWGui { std::string suggestedName = mAlchemy->suggestPotionName(); if (suggestedName != mSuggestedPotionName) + { mNameEdit->setCaptionWithReplacing(suggestedName); - mSuggestedPotionName = suggestedName; + mSuggestedPotionName = std::move(suggestedName); + } mSortModel->clearDragItems(); diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 5d9256b20d..1966442513 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -1065,7 +1065,7 @@ namespace MWGui { createActiveFormats(newBook); - mBook = newBook; + mBook = std::move(newBook); setPage(newPage); if (newPage < mBook->mPages.size()) diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 504ea560b2..b430e08142 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -440,7 +440,7 @@ namespace MWGui // If new search term reset position, otherwise continue from current position if (newSearchTerm != mCurrentSearchTerm) { - mCurrentSearchTerm = newSearchTerm; + mCurrentSearchTerm = std::move(newSearchTerm); mCurrentOccurrenceIndex = std::string::npos; } diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index 7f62bbf49c..b2d9415897 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -221,7 +221,7 @@ namespace MWGui::Formatting } } - mAttributes[key] = value; + mAttributes[key] = std::move(value); } } diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index bb97ccb82f..d668db1dec 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -472,7 +472,7 @@ namespace MWGui ESM::EffectList effectList; effectList.mList = mEffects; - mSpell.mEffects = effectList; + mSpell.mEffects = std::move(effectList); mSpell.mData.mCost = int(y); mSpell.mData.mType = ESM::Spell::ST_Spell; mSpell.mData.mFlags = 0; diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index 5337e2f798..aa29dfc156 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -172,7 +172,7 @@ namespace MWGui w += 16; ToolTipInfo* tooltipInfo = image->getUserData(); - tooltipInfo->text = sourcesDescription; + tooltipInfo->text = std::move(sourcesDescription); // Fade out if (totalDuration >= fadeTime && fadeTime > 0.f) diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 9ee7d08f31..0a0343831d 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -251,7 +251,7 @@ namespace MWGui if (!cost.empty() && cost != "0") info.text += MWGui::ToolTips::getValueString(MWMechanics::calcSpellCost(*spell), "#{sCastCost}"); - info.effects = effects; + info.effects = std::move(effects); tooltipSize = createToolTip(info); } else if (type == "Layout") From 521394d67b8df7883306a0ed800b8b11a6375aea Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Jan 2024 12:06:01 +0100 Subject: [PATCH 224/231] Override launcher file info with higher priority info --- CHANGELOG.md | 1 + .../contentselector/model/contentmodel.cpp | 28 +++++++++++++------ components/contentselector/model/esmfile.hpp | 4 --- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 181116ca9d..580e9a2143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,6 +121,7 @@ Bug #7712: Casting doesn't support spells and enchantments with no effects Bug #7723: Assaulting vampires and werewolves shouldn't be a crime Bug #7724: Guards don't help vs werewolves + Bug #7733: Launcher shows incorrect data paths when there's two plugins with the same name Bug #7742: Governing attribute training limit should use the modified attribute Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 9b7bb11f09..c3ea050064 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -2,6 +2,7 @@ #include "esmfile.hpp" #include +#include #include #include @@ -447,26 +448,37 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf { QFileInfo info(dir.absoluteFilePath(path2)); - if (item(info.fileName())) - continue; - // Enabled by default in system openmw.cfg; shouldn't be shown in content list. if (info.fileName().compare("builtin.omwscripts", Qt::CaseInsensitive) == 0) continue; + EsmFile* file = const_cast(item(info.fileName())); + bool add = file == nullptr; + std::unique_ptr newFile; + if (add) + { + newFile = std::make_unique(path2); + file = newFile.get(); + } + else + { + // We've found the same file in a higher priority dir, update our existing entry + file->setFileName(path2); + file->setGameFiles({}); + } + if (info.fileName().endsWith(".omwscripts", Qt::CaseInsensitive)) { - EsmFile* file = new EsmFile(path2); file->setDate(info.lastModified()); file->setFilePath(info.absoluteFilePath()); - addFile(file); + if (add) + addFile(newFile.release()); setNew(file->fileName(), newfiles); continue; } try { - EsmFile* file = new EsmFile(path2); file->setDate(info.lastModified()); file->setFilePath(info.absoluteFilePath()); std::filesystem::path filepath = Files::pathFromQString(info.absoluteFilePath()); @@ -522,14 +534,14 @@ void ContentSelectorModel::ContentModel::addFiles(const QString& path, bool newf } // Put the file in the table - addFile(file); + if (add) + addFile(newFile.release()); setNew(file->fileName(), newfiles); } catch (std::runtime_error& e) { // An error occurred while reading the .esp qWarning() << "Error reading addon file: " << e.what(); - continue; } } } diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index a65c778294..cf0e086362 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -30,15 +30,11 @@ namespace ContentSelectorModel }; EsmFile(const QString& fileName = QString(), ModelItem* parent = nullptr); - // EsmFile(const EsmFile &); - - ~EsmFile() {} void setFileProperty(const FileProperty prop, const QString& value); void setFileName(const QString& fileName); void setAuthor(const QString& author); - void setSize(const int size); void setDate(const QDateTime& modified); void setFormat(const QString& format); void setFilePath(const QString& path); From 672cefd59422f97bd6257f727e1b201e2139838d Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sun, 7 Jan 2024 16:37:25 +0100 Subject: [PATCH 225/231] Track checked EsmFile pointers instead of full paths --- CHANGELOG.md | 1 + apps/launcher/datafilespage.cpp | 24 +------------ .../contentselector/model/contentmodel.cpp | 36 ++++++++----------- .../contentselector/model/contentmodel.hpp | 5 +-- 4 files changed, 20 insertions(+), 46 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 580e9a2143..3f07def9ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,7 @@ Bug #7084: Resurrecting an actor doesn't take into account base record changes Bug #7088: Deleting last save game of last character doesn't clear character name/details Bug #7092: BSA archives from higher priority directories don't take priority + Bug #7103: Multiple paths pointing to the same plugin but with different cases lead to automatically removed config entries Bug #7122: Teleportation to underwater should cancel active water walking effect Bug #7131: MyGUI log spam when post processing HUD is open Bug #7134: Saves with an invalid last generated RefNum can be loaded diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index dc2c07d9bd..114221ce92 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -125,27 +125,6 @@ namespace Launcher { return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024); } - - std::optional findFirstPath(const QStringList& directories, const QString& fileName) - { - for (const QString& directoryPath : directories) - { - const QString filePath = QDir(directoryPath).absoluteFilePath(fileName); - if (QFile::exists(filePath)) - return filePath; - } - return std::nullopt; - } - - QStringList findAllFilePaths(const QStringList& directories, const QStringList& fileNames) - { - QStringList result; - result.reserve(fileNames.size()); - for (const QString& fileName : fileNames) - if (const auto filepath = findFirstPath(directories, fileName)) - result.append(*filepath); - return result; - } } } @@ -366,8 +345,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) row++; } - mSelector->setProfileContent( - findAllFilePaths(directories, mLauncherSettings.getContentListFiles(contentModelName))); + mSelector->setProfileContent(mLauncherSettings.getContentListFiles(contentModelName)); } void Launcher::DataFilesPage::saveSettings(const QString& profile) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index c3ea050064..5e6db2fd7e 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -128,7 +128,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex& index continue; noGameFiles = false; - if (isChecked(depFile->filePath())) + if (mCheckedFiles.contains(depFile)) { gamefileChecked = true; break; @@ -215,7 +215,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int if (file == mGameFile) return QVariant(); - return mCheckStates[file->filePath()]; + return mCheckedFiles.contains(file) ? Qt::Checked : Qt::Unchecked; } case Qt::UserRole: @@ -229,7 +229,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex& index, int } case Qt::UserRole + 1: - return isChecked(file->filePath()); + return mCheckedFiles.contains(file); } return QVariant(); } @@ -277,12 +277,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const { int checkValue = value.toInt(); bool setState = false; - if ((checkValue == Qt::Checked) && !isChecked(file->filePath())) + if (checkValue == Qt::Checked && !mCheckedFiles.contains(file)) { setState = true; success = true; } - else if ((checkValue == Qt::Checked) && isChecked(file->filePath())) + else if (checkValue == Qt::Checked && mCheckedFiles.contains(file)) setState = true; else if (checkValue == Qt::Unchecked) setState = true; @@ -628,14 +628,6 @@ void ContentSelectorModel::ContentModel::sortFiles() emit layoutChanged(); } -bool ContentSelectorModel::ContentModel::isChecked(const QString& filepath) const -{ - const auto it = mCheckStates.find(filepath); - if (it == mCheckStates.end()) - return false; - return it.value() == Qt::Checked; -} - bool ContentSelectorModel::ContentModel::isEnabled(const QModelIndex& index) const { return (flags(index) & Qt::ItemIsEnabled); @@ -723,7 +715,7 @@ QList ContentSelectorModel::ContentModel:: } else { - if (!isChecked(dependentFile->filePath())) + if (!mCheckedFiles.contains(dependentFile)) { errors.append(LoadOrderError(LoadOrderError::ErrorCode_InactiveDependency, dependentfileName)); } @@ -776,9 +768,13 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, Qt::CheckState state = Qt::Unchecked; if (checkState) + { state = Qt::Checked; + mCheckedFiles.insert(file); + } + else + mCheckedFiles.erase(file); - mCheckStates[filepath] = state; emit dataChanged(indexFromItem(item(filepath)), indexFromItem(item(filepath))); if (file->isGameFile()) @@ -794,8 +790,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, if (!upstreamFile) continue; - if (!isChecked(upstreamFile->filePath())) - mCheckStates[upstreamFile->filePath()] = Qt::Checked; + mCheckedFiles.insert(upstreamFile); emit dataChanged(indexFromItem(upstreamFile), indexFromItem(upstreamFile)); } @@ -810,8 +805,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, if (downstreamFile->gameFiles().contains(filename, Qt::CaseInsensitive)) { - if (mCheckStates.contains(downstreamFile->filePath())) - mCheckStates[downstreamFile->filePath()] = Qt::Unchecked; + mCheckedFiles.erase(downstreamFile); emit dataChanged(indexFromItem(downstreamFile), indexFromItem(downstreamFile)); } @@ -829,7 +823,7 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke // First search for game files and next addons, // so we get more or less correct game files vs addons order. for (EsmFile* file : mFiles) - if (isChecked(file->filePath())) + if (mCheckedFiles.contains(file)) list << file; return list; @@ -838,6 +832,6 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke void ContentSelectorModel::ContentModel::uncheckAll() { emit layoutAboutToBeChanged(); - mCheckStates.clear(); + mCheckedFiles.clear(); emit layoutChanged(); } diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index d56f8f9a3b..1ba3090a32 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -7,6 +7,8 @@ #include #include +#include + namespace ContentSelectorModel { class EsmFile; @@ -57,7 +59,6 @@ namespace ContentSelectorModel void setCurrentGameFile(const EsmFile* file); bool isEnabled(const QModelIndex& index) const; - bool isChecked(const QString& filepath) const; bool setCheckState(const QString& filepath, bool isChecked); bool isNew(const QString& filepath) const; void setNew(const QString& filepath, bool isChecked); @@ -85,7 +86,7 @@ namespace ContentSelectorModel const EsmFile* mGameFile; ContentFileList mFiles; QStringList mArchives; - QHash mCheckStates; + std::set mCheckedFiles; QHash mNewFiles; QSet mPluginsWithLoadOrderError; QString mEncoding; From 0db8026356c4f652b02141afc136fb488c194271 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Jan 2024 17:08:05 +0100 Subject: [PATCH 226/231] Stop adding and deleting rows --- .../contentselector/model/contentmodel.cpp | 82 ++++++++----------- components/contentselector/model/esmfile.cpp | 16 ---- components/contentselector/model/esmfile.hpp | 4 - .../contentselector/view/contentselector.cpp | 6 ++ .../contentselector/view/contentselector.hpp | 1 + 5 files changed, 39 insertions(+), 70 deletions(-) diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 5e6db2fd7e..0aab06ac90 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -6,9 +6,11 @@ #include #include +#include #include #include #include +#include #include #include @@ -315,34 +317,12 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex& index, const bool ContentSelectorModel::ContentModel::insertRows(int position, int rows, const QModelIndex& parent) { - if (parent.isValid()) - return false; - - beginInsertRows(parent, position, position + rows - 1); - { - for (int row = 0; row < rows; ++row) - mFiles.insert(position, new EsmFile); - } - endInsertRows(); - - return true; + return false; } bool ContentSelectorModel::ContentModel::removeRows(int position, int rows, const QModelIndex& parent) { - if (parent.isValid()) - return false; - - beginRemoveRows(parent, position, position + rows - 1); - { - for (int row = 0; row < rows; ++row) - delete mFiles.takeAt(position); - } - endRemoveRows(); - - // at this point we know that drag and drop has finished. - checkForLoadOrderErrors(); - return true; + return false; } Qt::DropActions ContentSelectorModel::ContentModel::supportedDropActions() const @@ -358,13 +338,14 @@ QStringList ContentSelectorModel::ContentModel::mimeTypes() const QMimeData* ContentSelectorModel::ContentModel::mimeData(const QModelIndexList& indexes) const { QByteArray encodedData; + QDataStream stream(&encodedData, QIODevice::WriteOnly); for (const QModelIndex& index : indexes) { if (!index.isValid()) continue; - encodedData.append(item(index.row())->encodedData()); + stream << index.row(); } QMimeData* mimeData = new QMimeData(); @@ -396,26 +377,31 @@ bool ContentSelectorModel::ContentModel::dropMimeData( QByteArray encodedData = data->data(mMimeType); QDataStream stream(&encodedData, QIODevice::ReadOnly); + std::vector toMove; while (!stream.atEnd()) { - - QString value; - QStringList values; - QStringList gamefiles; - - for (int i = 0; i < EsmFile::FileProperty_GameFile; ++i) - { - stream >> value; - values << value; - } - - stream >> gamefiles; - - insertRows(beginRow, 1); - - QModelIndex idx = index(beginRow++, 0, QModelIndex()); - setData(idx, QStringList() << values << gamefiles, Qt::EditRole); + int sourceRow; + stream >> sourceRow; + toMove.emplace_back(mFiles.at(sourceRow)); } + int minRow = mFiles.size(); + int maxRow = 0; + for (EsmFile* file : toMove) + { + int from = mFiles.indexOf(file); + int to = beginRow; + if (from < beginRow) + to--; + else if (from > beginRow) + beginRow++; + minRow = std::min(minRow, std::min(to, from)); + maxRow = std::max(maxRow, std::max(to, from)); + mFiles.move(from, to); + } + + dataChanged(index(minRow, 0), index(maxRow, 0)); + // at this point we know that drag and drop has finished. + checkForLoadOrderErrors(); return true; } @@ -566,6 +552,7 @@ void ContentSelectorModel::ContentModel::clearFiles() if (filesCount > 0) { beginRemoveRows(QModelIndex(), 0, filesCount - 1); + qDeleteAll(mFiles); mFiles.clear(); endRemoveRows(); } @@ -688,7 +675,7 @@ void ContentSelectorModel::ContentModel::checkForLoadOrderErrors() { for (int row = 0; row < mFiles.count(); ++row) { - EsmFile* file = item(row); + EsmFile* file = mFiles.at(row); bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0; if (isRowInError) { @@ -765,13 +752,8 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, if (!file) return false; - Qt::CheckState state = Qt::Unchecked; - if (checkState) - { - state = Qt::Checked; mCheckedFiles.insert(file); - } else mCheckedFiles.erase(file); @@ -781,7 +763,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, refreshModel(); // if we're checking an item, ensure all "upstream" files (dependencies) are checked as well. - if (state == Qt::Checked) + if (checkState) { for (const QString& upstreamName : file->gameFiles()) { @@ -796,7 +778,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString& filepath, } } // otherwise, if we're unchecking an item (or the file is a game file) ensure all downstream files are unchecked. - if (state == Qt::Unchecked) + else { for (const EsmFile* downstreamFile : mFiles) { diff --git a/components/contentselector/model/esmfile.cpp b/components/contentselector/model/esmfile.cpp index 39a33e710e..e4280baef7 100644 --- a/components/contentselector/model/esmfile.cpp +++ b/components/contentselector/model/esmfile.cpp @@ -1,10 +1,5 @@ #include "esmfile.hpp" -#include -#include - -int ContentSelectorModel::EsmFile::sPropertyCount = 7; - ContentSelectorModel::EsmFile::EsmFile(const QString& fileName, ModelItem* parent) : ModelItem(parent) , mFileName(fileName) @@ -46,17 +41,6 @@ void ContentSelectorModel::EsmFile::setDescription(const QString& description) mDescription = description; } -QByteArray ContentSelectorModel::EsmFile::encodedData() const -{ - QByteArray encodedData; - QDataStream stream(&encodedData, QIODevice::WriteOnly); - - stream << mFileName << mAuthor << mVersion << mModified.toString(Qt::ISODate) << mPath << mDescription - << mGameFiles; - - return encodedData; -} - bool ContentSelectorModel::EsmFile::isGameFile() const { return (mGameFiles.size() == 0) diff --git a/components/contentselector/model/esmfile.hpp b/components/contentselector/model/esmfile.hpp index cf0e086362..606cc3d319 100644 --- a/components/contentselector/model/esmfile.hpp +++ b/components/contentselector/model/esmfile.hpp @@ -64,10 +64,6 @@ namespace ContentSelectorModel } bool isGameFile() const; - QByteArray encodedData() const; - - public: - static int sPropertyCount; private: QString mTooltipTemlate = tr( diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 3f75b82487..00c32e272d 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -108,6 +108,7 @@ void ContentSelectorView::ContentSelector::buildAddonView() connect(ui->addonView, &QTableView::activated, this, &ContentSelector::slotAddonTableItemActivated); connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this, &ContentSelector::signalAddonDataChanged); + connect(mContentModel, &ContentSelectorModel::ContentModel::dataChanged, this, &ContentSelector::slotRowsMoved); buildContextMenu(); } @@ -331,3 +332,8 @@ void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QSt { ui->addonView->setDragEnabled(newText.isEmpty()); } + +void ContentSelectorView::ContentSelector::slotRowsMoved() +{ + ui->addonView->selectionModel()->clearSelection(); +} \ No newline at end of file diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index 48377acb86..2b739645ba 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -85,6 +85,7 @@ namespace ContentSelectorView void slotUncheckMultiSelectedItems(); void slotCopySelectedItemsPaths(); void slotSearchFilterTextChanged(const QString& newText); + void slotRowsMoved(); }; } From e7d1611be34dcdbc1950afbfe33297d0de302523 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 9 Jan 2024 18:19:04 +0100 Subject: [PATCH 227/231] Update ActiveEffect documentation --- files/lua_api/openmw/core.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 890532fc13..7ea3c75f1c 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -682,7 +682,6 @@ --- -- @type ActiveEffect -- Magic effect that is currently active on an actor. --- Note that when this effect expires or is removed, it will remain temporarily. Magnitude will be set to 0 for effects that expire. -- @field #string affectedSkill Optional skill ID -- @field #string affectedAttribute Optional attribute ID -- @field #string id Effect id string From e67d6c6ebfcf4688f054604ec0d8add4b9ae2490 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 10 Jan 2024 12:30:19 +0400 Subject: [PATCH 228/231] Refactor Lua properties --- apps/openmw/mwlua/inputbindings.cpp | 33 ++++++++++++++++++----------- apps/openmw/mwlua/stats.cpp | 5 +++-- apps/openmw/mwlua/uibindings.cpp | 4 ++-- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwlua/inputbindings.cpp b/apps/openmw/mwlua/inputbindings.cpp index 33b19f3b4d..b44a528f2c 100644 --- a/apps/openmw/mwlua/inputbindings.cpp +++ b/apps/openmw/mwlua/inputbindings.cpp @@ -76,13 +76,18 @@ namespace MWLua inputActions[sol::meta_function::pairs] = pairs; } - context.mLua->sol().new_usertype("ActionInfo", "key", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mKey; }), "name", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mName; }), "description", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDescription; }), "type", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mType; }), "l10n", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mL10n; }), "defaultValue", - sol::property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; })); + auto actionInfo = context.mLua->sol().new_usertype("ActionInfo"); + actionInfo["key"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mKey; }); + actionInfo["name"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mName; }); + actionInfo["description"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mDescription; }); + actionInfo["l10n"] = sol::readonly_property( + [](const LuaUtil::InputAction::Info& info) -> std::string_view { return info.mL10n; }); + actionInfo["type"] = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mType; }); + actionInfo["defaultValue"] + = sol::readonly_property([](const LuaUtil::InputAction::Info& info) { return info.mDefaultValue; }); auto inputTriggers = context.mLua->sol().new_usertype("InputTriggers"); inputTriggers[sol::meta_function::index] @@ -102,11 +107,15 @@ namespace MWLua inputTriggers[sol::meta_function::pairs] = pairs; } - context.mLua->sol().new_usertype("TriggerInfo", "key", - sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mKey; }), "name", - sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mName; }), "description", - sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mDescription; }), "l10n", - sol::property([](const LuaUtil::InputTrigger::Info& info) { return info.mL10n; })); + auto triggerInfo = context.mLua->sol().new_usertype("TriggerInfo"); + triggerInfo["key"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mKey; }); + triggerInfo["name"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mName; }); + triggerInfo["description"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mDescription; }); + triggerInfo["l10n"] = sol::readonly_property( + [](const LuaUtil::InputTrigger::Info& info) -> std::string_view { return info.mL10n; }); MWBase::InputManager* input = MWBase::Environment::get().getInputManager(); sol::table api(context.mLua->sol(), sol::create); diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index ff74704ed8..02bed00bf5 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -380,7 +380,7 @@ namespace MWLua addProp(context, attributeStatT, "base", &MWMechanics::AttributeValue::getBase); addProp(context, attributeStatT, "damage", &MWMechanics::AttributeValue::getDamage); attributeStatT["modified"] - = sol::property([=](const AttributeStat& stat) { return stat.getModified(context); }); + = sol::readonly_property([=](const AttributeStat& stat) { return stat.getModified(context); }); addProp(context, attributeStatT, "modifier", &MWMechanics::AttributeValue::getModifier); sol::table attributes(context.mLua->sol(), sol::create); stats["attributes"] = LuaUtil::makeReadOnly(attributes); @@ -399,7 +399,8 @@ namespace MWLua auto skillStatT = context.mLua->sol().new_usertype("SkillStat"); addProp(context, skillStatT, "base", &MWMechanics::SkillValue::getBase); addProp(context, skillStatT, "damage", &MWMechanics::SkillValue::getDamage); - skillStatT["modified"] = sol::property([=](const SkillStat& stat) { return stat.getModified(context); }); + skillStatT["modified"] + = sol::readonly_property([=](const SkillStat& stat) { return stat.getModified(context); }); addProp(context, skillStatT, "modifier", &MWMechanics::SkillValue::getModifier); skillStatT["progress"] = sol::property([context](const SkillStat& stat) { return stat.getProgress(context); }, [context](const SkillStat& stat, const sol::object& value) { stat.cache(context, "progress", value); }); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 843917275c..30f190ad38 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -161,8 +161,8 @@ namespace MWLua api["_getMenuTransparency"] = []() -> float { return Settings::gui().mMenuTransparency; }; auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); - uiLayer["name"] = sol::property([](LuaUi::Layer& self) { return self.name(); }); - uiLayer["size"] = sol::property([](LuaUi::Layer& self) { return self.size(); }); + uiLayer["name"] = sol::readonly_property([](LuaUi::Layer& self) -> std::string_view { return self.name(); }); + uiLayer["size"] = sol::readonly_property([](LuaUi::Layer& self) { return self.size(); }); uiLayer[sol::meta_function::to_string] = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; From 0e1bb4534557425cab49cc1721293a07d25a3d8f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Wed, 10 Jan 2024 14:24:18 +0400 Subject: [PATCH 229/231] Cleanup navmesh updater --- components/detournavigator/asyncnavmeshupdater.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/components/detournavigator/asyncnavmeshupdater.cpp b/components/detournavigator/asyncnavmeshupdater.cpp index 3a1b6fd77a..980281240d 100644 --- a/components/detournavigator/asyncnavmeshupdater.cpp +++ b/components/detournavigator/asyncnavmeshupdater.cpp @@ -180,8 +180,8 @@ namespace DetourNavigator if (!playerTileChanged && changedTiles.empty()) return; - const dtNavMeshParams params = *navMeshCacheItem->lockConst()->getImpl().getParams(); - const int maxTiles = std::min(mSettings.get().mMaxTilesNumber, params.maxTiles); + const int maxTiles + = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); std::unique_lock lock(mMutex); @@ -376,9 +376,10 @@ namespace DetourNavigator return JobStatus::Done; const auto playerTile = *mPlayerTile.lockConst(); - const auto params = *navMeshCacheItem->lockConst()->getImpl().getParams(); + const int maxTiles + = std::min(mSettings.get().mMaxTilesNumber, navMeshCacheItem->lockConst()->getImpl().getParams()->maxTiles); - if (!shouldAddTile(job.mChangedTile, playerTile, std::min(mSettings.get().mMaxTilesNumber, params.maxTiles))) + if (!shouldAddTile(job.mChangedTile, playerTile, maxTiles)) { Log(Debug::Debug) << "Ignore add tile by job " << job.mId << ": too far from player"; navMeshCacheItem->lock()->removeTile(job.mChangedTile); From ccbc02abc3da4f1a124df9de5b4b1022cda48f92 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 10 Jan 2024 08:07:48 +0300 Subject: [PATCH 230/231] Handle running stats extensions on non-actors gracefully (#7770) --- CHANGELOG.md | 1 + apps/openmw/mwscript/statsextensions.cpp | 143 +++++++++++++++++++---- 2 files changed, 120 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f07def9ac..32d0a6a866 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,7 @@ Bug #7742: Governing attribute training limit should use the modified attribute Bug #7758: Water walking is not taken into account to compute path cost on the water Bug #7761: Rain and ambient loop sounds are mutually exclusive + Bug #7770: Sword of the Perithia: Script execution failure Feature #2566: Handle NAM9 records for manual cell references Feature #3537: Shader-based water ripples Feature #5173: Support for NiFogProperty diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index d617a02b9a..4bc59e1524 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -85,7 +85,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).getLevel(); + Interpreter::Type_Integer value = -1; + if (ptr.getClass().isActor()) + value = ptr.getClass().getCreatureStats(ptr).getLevel(); runtime.push(value); } @@ -102,7 +104,8 @@ namespace MWScript Interpreter::Type_Integer value = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getCreatureStats(ptr).setLevel(value); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).setLevel(value); } }; @@ -121,7 +124,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Float value = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).getModified(); + Interpreter::Type_Float value = 0.f; + if (ptr.getClass().isActor()) + value = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex).getModified(); runtime.push(value); } @@ -145,6 +150,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); attribute.setBase(value, true); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); @@ -169,6 +177,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::AttributeValue attribute = ptr.getClass().getCreatureStats(ptr).getAttribute(mIndex); modStat(attribute, value); ptr.getClass().getCreatureStats(ptr).setAttribute(mIndex, attribute); @@ -189,14 +200,14 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Float value; + Interpreter::Type_Float value = 0.f; if (mIndex == 0 && ptr.getClass().hasItemHealth(ptr)) { // health is a special case value = static_cast(ptr.getClass().getItemMaxHealth(ptr)); } - else + else if (ptr.getClass().isActor()) { value = ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex).getCurrent(); // GetMagicka shouldn't return negative values @@ -225,6 +236,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::DynamicStat stat(ptr.getClass().getCreatureStats(ptr).getDynamic(mIndex)); stat.setBase(value); @@ -254,6 +268,9 @@ namespace MWScript Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + // workaround broken endgame scripts that kill dagoth ur if (!R::implicit && ptr.getCellRef().getRefId() == "dagoth_ur_1") { @@ -301,6 +318,9 @@ namespace MWScript Interpreter::Type_Float diff = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); Interpreter::Type_Float current = stats.getDynamic(mIndex).getCurrent(); @@ -336,6 +356,13 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); + + if (!ptr.getClass().isActor()) + { + runtime.push(0.f); + return; + } + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); runtime.push(stats.getDynamic(mIndex).getRatio()); @@ -357,6 +384,12 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.getClass().isActor()) + { + runtime.push(0.f); + return; + } + Interpreter::Type_Float value = ptr.getClass().getSkill(ptr, mId); runtime.push(value); @@ -381,6 +414,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isNpc()) + return; + MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats(ptr); stats.getSkill(mId).setBase(value, true); @@ -405,6 +441,9 @@ namespace MWScript Interpreter::Type_Float value = runtime[0].mFloat; runtime.pop(); + if (!ptr.getClass().isNpc()) + return; + MWMechanics::SkillValue& skill = ptr.getClass().getNpcStats(ptr).getSkill(mId); modStat(skill, value); } @@ -465,6 +504,9 @@ namespace MWScript ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + if (!ptr.getClass().isActor()) + return; + const ESM::Spell* spell = MWBase::Environment::get().getESMStore()->get().find(id); MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); @@ -491,6 +533,9 @@ namespace MWScript ESM::RefId id = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + if (!ptr.getClass().isActor()) + return; + MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr); creatureStats.getSpells().remove(id); @@ -514,7 +559,8 @@ namespace MWScript ESM::RefId spellid = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).getActiveSpells().removeEffects(ptr, spellid); } }; @@ -529,7 +575,8 @@ namespace MWScript Interpreter::Type_Integer effectId = runtime[0].mInteger; runtime.pop(); - ptr.getClass().getCreatureStats(ptr).getActiveSpells().purgeEffect(ptr, effectId); + if (ptr.getClass().isActor()) + ptr.getClass().getCreatureStats(ptr).getActiveSpells().purgeEffect(ptr, effectId); } }; @@ -845,7 +892,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getCreatureStats(ptr).hasCommonDisease()); + if (ptr.getClass().isActor()) + runtime.push(ptr.getClass().getCreatureStats(ptr).hasCommonDisease()); + else + runtime.push(0); } }; @@ -857,7 +907,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getCreatureStats(ptr).hasBlightDisease()); + if (ptr.getClass().isActor()) + runtime.push(ptr.getClass().getCreatureStats(ptr).hasBlightDisease()); + else + runtime.push(0); } }; @@ -872,9 +925,16 @@ namespace MWScript ESM::RefId race = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); - const ESM::RefId& npcRace = ptr.get()->mBase->mRace; + if (ptr.getClass().isNpc()) + { + const ESM::RefId& npcRace = ptr.get()->mBase->mRace; - runtime.push(race == npcRace); + runtime.push(race == npcRace); + } + else + { + runtime.push(0); + } } }; @@ -1043,10 +1103,15 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).hasDied(); + Interpreter::Type_Integer value = 0; + if (ptr.getClass().isActor()) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + value = stats.hasDied(); - if (value) - ptr.getClass().getCreatureStats(ptr).clearHasDied(); + if (value) + stats.clearHasDied(); + } runtime.push(value); } @@ -1060,10 +1125,15 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).hasBeenMurdered(); + Interpreter::Type_Integer value = 0; + if (ptr.getClass().isActor()) + { + auto& stats = ptr.getClass().getCreatureStats(ptr); + value = stats.hasBeenMurdered(); - if (value) - ptr.getClass().getCreatureStats(ptr).clearHasBeenMurdered(); + if (value) + stats.clearHasBeenMurdered(); + } runtime.push(value); } @@ -1077,7 +1147,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - Interpreter::Type_Integer value = ptr.getClass().getCreatureStats(ptr).getKnockedDownOneFrame(); + Interpreter::Type_Integer value = 0; + if (ptr.getClass().isActor()) + value = ptr.getClass().getCreatureStats(ptr).getKnockedDownOneFrame(); runtime.push(value); } @@ -1090,7 +1162,10 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - runtime.push(ptr.getClass().getNpcStats(ptr).isWerewolf()); + if (ptr.getClass().isNpc()) + runtime.push(ptr.getClass().getNpcStats(ptr).isWerewolf()); + else + runtime.push(0); } }; @@ -1101,7 +1176,8 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - MWBase::Environment::get().getMechanicsManager()->setWerewolf(ptr, set); + if (ptr.getClass().isNpc()) + MWBase::Environment::get().getMechanicsManager()->setWerewolf(ptr, set); } }; @@ -1112,7 +1188,8 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - MWBase::Environment::get().getMechanicsManager()->applyWerewolfAcrobatics(ptr); + if (ptr.getClass().isNpc()) + MWBase::Environment::get().getMechanicsManager()->applyWerewolfAcrobatics(ptr); } }; @@ -1124,6 +1201,9 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.getClass().isActor()) + return; + if (ptr == MWMechanics::getPlayer()) { MWBase::Environment::get().getMechanicsManager()->resurrect(ptr); @@ -1192,6 +1272,12 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); + if (!ptr.getClass().isActor()) + { + runtime.push(0); + return; + } + const MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) @@ -1226,6 +1312,13 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); + + int arg = runtime[0].mInteger; + runtime.pop(); + + if (!ptr.getClass().isActor()) + return; + MWMechanics::MagicEffects& effects = ptr.getClass().getCreatureStats(ptr).getMagicEffects(); float currentValue = effects.getOrDefault(mPositiveEffect).getMagnitude(); if (mNegativeEffect != -1) @@ -1239,8 +1332,6 @@ namespace MWScript if (mPositiveEffect == ESM::MagicEffect::ResistFrost) currentValue += effects.getOrDefault(ESM::MagicEffect::FrostShield).getMagnitude(); - int arg = runtime[0].mInteger; - runtime.pop(); effects.modifyBase(mPositiveEffect, (arg - static_cast(currentValue))); } }; @@ -1261,10 +1352,14 @@ namespace MWScript void execute(Interpreter::Runtime& runtime) override { MWWorld::Ptr ptr = R()(runtime); - MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); int arg = runtime[0].mInteger; runtime.pop(); + + if (!ptr.getClass().isActor()) + return; + + MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); stats.getMagicEffects().modifyBase(mPositiveEffect, arg); } }; From 52623ddd7d153520f956b6cc48c4d66e97e016f0 Mon Sep 17 00:00:00 2001 From: Yury Stepovikov Date: Thu, 11 Jan 2024 00:59:27 +0000 Subject: [PATCH 231/231] Set MacOS current_path before reading configuration files [#7706] --- AUTHORS.md | 1 + apps/launcher/main.cpp | 5 ----- apps/opencs/main.cpp | 5 ----- apps/openmw/main.cpp | 2 -- apps/wizard/main.cpp | 2 -- components/files/macospath.cpp | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 34 insertions(+), 14 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 9791171b9c..e2903febe4 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -245,6 +245,7 @@ Programmers xyzz Yohaulticetl Yuri Krupenin + Yury Stepovikov zelurker Documentation diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 4aac90fb6e..78323458ce 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -41,11 +41,6 @@ int runLauncher(int argc, char* argv[]) appTranslator.load(":/translations/" + locale + ".qm"); app.installTranslator(&appTranslator); - // Now we make sure the current dir is set to application path - QDir dir(QCoreApplication::applicationDirPath()); - - QDir::setCurrent(dir.absolutePath()); - Launcher::MainDialog mainWin(configurationManager); Launcher::FirstRunDialogResult result = mainWin.showFirstRunDialog(); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index ecab9614a1..e7f980dc0d 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -81,11 +81,6 @@ int runApplication(int argc, char* argv[]) Application application(argc, argv); -#ifdef Q_OS_MAC - QDir dir(QCoreApplication::applicationDirPath()); - QDir::setCurrent(dir.absolutePath()); -#endif - application.setWindowIcon(QIcon(":./openmw-cs.png")); CS::Editor editor(argc, argv); diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index b0b49f3acd..5bbc0211c1 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -219,8 +219,6 @@ int runApplication(int argc, char* argv[]) Platform::init(); #ifdef __APPLE__ - std::filesystem::path binary_path = std::filesystem::absolute(std::filesystem::path(argv[0])); - std::filesystem::current_path(binary_path.parent_path()); setenv("OSG_GL_TEXTURE_STORAGE", "OFF", 0); #endif diff --git a/apps/wizard/main.cpp b/apps/wizard/main.cpp index e2b0d3874b..03ac24c8c0 100644 --- a/apps/wizard/main.cpp +++ b/apps/wizard/main.cpp @@ -28,8 +28,6 @@ int main(int argc, char* argv[]) app.setLibraryPaths(libraryPaths); #endif - QDir::setCurrent(dir.absolutePath()); - Wizard::MainWizard wizard; wizard.show(); diff --git a/components/files/macospath.cpp b/components/files/macospath.cpp index 2d0a409782..4b37c2fb26 100644 --- a/components/files/macospath.cpp +++ b/components/files/macospath.cpp @@ -5,13 +5,41 @@ #include #include #include +#include #include #include +#include +#include #include namespace { + std::filesystem::path getBinaryPath() + { + uint32_t bufsize = 0; + _NSGetExecutablePath(nullptr, &bufsize); + + std::vector buf(bufsize); + + if (_NSGetExecutablePath(buf.data(), &bufsize) == 0) + { + std::filesystem::path path = std::filesystem::path(buf.begin(), buf.end()); + + if (std::filesystem::is_symlink(path)) + { + return std::filesystem::read_symlink(path); + } + + return path; + } + else + { + Log(Debug::Warning) << "Not enough buffer size to get executable path: " << bufsize; + throw std::runtime_error("Failed to get executable path"); + } + } + std::filesystem::path getUserHome() { const char* dir = getenv("HOME"); @@ -36,6 +64,11 @@ namespace Files MacOsPath::MacOsPath(const std::string& application_name) : mName(application_name) { + std::filesystem::path binary_path = getBinaryPath(); + std::error_code ec; + std::filesystem::current_path(binary_path.parent_path(), ec); + if (ec.value() != 0) + Log(Debug::Warning) << "Error " << ec.message() << " when changing current directory"; } std::filesystem::path MacOsPath::getUserConfigPath() const