From 4d6667920493fb3711ac275dbd281efd75166215 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 21 Oct 2023 13:04:02 +0200 Subject: [PATCH 01/36] Queue Lua handler `uiModeChanged` --- apps/openmw/mwlua/luamanagerimp.cpp | 16 +++++++++++++++- apps/openmw/mwlua/luamanagerimp.hpp | 1 + apps/openmw/mwlua/playerscripts.hpp | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index fc686dcbb3..63a2838250 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -246,6 +246,13 @@ namespace MWLua reloadAllScriptsImpl(); mReloadAllScriptsRequested = false; } + + if (mDelayedUiModeChangedArg) + { + if (playerScripts) + playerScripts->uiModeChanged(*mDelayedUiModeChangedArg, true); + mDelayedUiModeChangedArg = std::nullopt; + } } void LuaManager::applyDelayedActions() @@ -275,6 +282,7 @@ namespace MWLua mGlobalScripts.removeAllScripts(); mGlobalScriptsStarted = false; mNewGameStarted = false; + mDelayedUiModeChangedArg = std::nullopt; if (!mPlayer.isEmpty()) { mPlayer.getCellRef().unsetRefNum(); @@ -325,9 +333,15 @@ namespace MWLua { if (mPlayer.isEmpty()) return; + ObjectId argId = arg.isEmpty() ? ObjectId() : getId(arg); + if (mApplyingDelayedActions) + { + mDelayedUiModeChangedArg = argId; + return; + } PlayerScripts* playerScripts = dynamic_cast(mPlayer.getRefData().getLuaScripts()); if (playerScripts) - playerScripts->uiModeChanged(arg, mApplyingDelayedActions); + playerScripts->uiModeChanged(argId, false); } void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 9f4c0096b5..a725761dbd 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -204,6 +204,7 @@ namespace MWLua std::optional mTeleportPlayerAction; std::vector mUIMessages; std::vector> mInGameConsoleMessages; + std::optional mDelayedUiModeChangedArg; LuaUtil::LuaStorage mGlobalStorage{ mLua.sol() }; LuaUtil::LuaStorage mPlayerStorage{ mLua.sol() }; diff --git a/apps/openmw/mwlua/playerscripts.hpp b/apps/openmw/mwlua/playerscripts.hpp index e8cad4968c..2d3aa9bc78 100644 --- a/apps/openmw/mwlua/playerscripts.hpp +++ b/apps/openmw/mwlua/playerscripts.hpp @@ -66,9 +66,9 @@ namespace MWLua } // `arg` is either forwarded from MWGui::pushGuiMode or empty - void uiModeChanged(const MWWorld::Ptr& arg, bool byLuaAction) + void uiModeChanged(ObjectId arg, bool byLuaAction) { - if (arg.isEmpty()) + if (arg.isZeroOrUnset()) callEngineHandlers(mUiModeChanged, byLuaAction); else callEngineHandlers(mUiModeChanged, byLuaAction, LObject(arg)); From ee5ca066fd5fb2346ab63d66bb366282830b80b7 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 21 Oct 2023 13:56:13 +0300 Subject: [PATCH 02/36] Allow talking with fleeing creatures (bug #7631) --- CHANGELOG.md | 1 + apps/openmw/mwclass/creature.cpp | 9 ++++++--- apps/openmw/mwmechanics/aicombat.cpp | 2 +- apps/openmw/mwmechanics/aicombat.hpp | 2 +- apps/openmw/mwmechanics/aisequence.cpp | 9 +++++++++ apps/openmw/mwmechanics/aisequence.hpp | 3 +++ apps/openmw/mwmechanics/aistate.hpp | 7 +++++++ 7 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c01901ae1..0874cf1a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ Bug #7604: Goblins Grunt becomes idle once injured Bug #7609: ForceGreeting should not open dialogue for werewolves Bug #7630: Charm can be cast on creatures + Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing 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/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 4950a8c40b..eebcb99512 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -464,18 +464,20 @@ namespace MWClass } const MWMechanics::CreatureStats& stats = getCreatureStats(ptr); + const MWMechanics::AiSequence& aiSequence = stats.getAiSequence(); + const bool isInCombat = aiSequence.isInCombat(); if (stats.isDead()) { // by default user can loot friendly actors during death animation - if (Settings::game().mCanLootDuringDeathAnimation && !stats.getAiSequence().isInCombat()) + if (Settings::game().mCanLootDuringDeathAnimation && !isInCombat) return std::make_unique(ptr); // otherwise wait until death animation if (stats.isDeathAnimationFinished()) return std::make_unique(ptr); } - else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) + else if ((!isInCombat || aiSequence.isFleeing()) && !stats.getKnockedDown()) return std::make_unique(ptr); // Tribunal and some mod companions oddly enough must use open action as fallback @@ -570,7 +572,8 @@ namespace MWClass if (customData.mCreatureStats.isDead() && customData.mCreatureStats.isDeathAnimationFinished()) return true; - return !customData.mCreatureStats.getAiSequence().isInCombat(); + const MWMechanics::AiSequence& aiSeq = customData.mCreatureStats.getAiSequence(); + return !aiSeq.isInCombat() || aiSeq.isFleeing(); } MWGui::ToolTipInfo Creature::getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 4c6ea42d36..5285fb31dd 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -708,7 +708,7 @@ namespace MWMechanics mFleeDest = ESM::Pathgrid::Point(0, 0, 0); } - bool AiCombatStorage::isFleeing() + bool AiCombatStorage::isFleeing() const { return mFleeState != FleeState_None; } diff --git a/apps/openmw/mwmechanics/aicombat.hpp b/apps/openmw/mwmechanics/aicombat.hpp index 92d380dbd8..494f038d6e 100644 --- a/apps/openmw/mwmechanics/aicombat.hpp +++ b/apps/openmw/mwmechanics/aicombat.hpp @@ -70,7 +70,7 @@ namespace MWMechanics void startFleeing(); void stopFleeing(); - bool isFleeing(); + bool isFleeing() const; }; /// \brief Causes the actor to fight another actor diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b58927c993..13602877ed 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -135,6 +135,15 @@ namespace MWMechanics return mNumPursuitPackages > 0; } + bool AiSequence::isFleeing() const + { + if (!isInCombat()) + return false; + + const AiCombatStorage* storage = mAiState.getPtr(); + return storage && storage->isFleeing(); + } + bool AiSequence::isEngagedWithActor() const { if (!isInCombat()) diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index ab3cc11e2c..92c1724ea6 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -117,6 +117,9 @@ namespace MWMechanics /// Is there any pursuit package. bool isInPursuit() const; + /// Is the actor fleeing? + bool isFleeing() const; + /// Removes all packages using the specified id. void removePackagesById(AiPackageTypeId id); diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index d79469a9a0..f2ce17fd9c 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -38,6 +38,13 @@ namespace MWMechanics return *result; } + /// \brief returns pointer to stored object in the desired type + template + Derived* getPtr() const + { + return dynamic_cast(mStorage.get()); + } + template void copy(DerivedClassStorage& destination) const { From 3330907d7bf7c68a1145b535341f00770850f262 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 21 Oct 2023 16:22:15 +0300 Subject: [PATCH 03/36] Combat AI: Prefer picking reachable/visible targets (bug #6932) --- 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 2c01901ae1..e5a6dcb4b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ 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 + Bug #6932: Creatures flee from my followers and we have to chase after them Bug #6939: OpenMW-CS: ID columns are too short Bug #6949: Sun Damage effect doesn't work in quasi exteriors Bug #6964: Nerasa Dralor Won't Follow diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index b58927c993..b058e61c92 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -272,7 +272,9 @@ namespace MWMechanics } else { - float rating = MWMechanics::getBestActionRating(actor, target); + float rating = 0.f; + if (MWMechanics::canFight(actor, target)) + rating = MWMechanics::getBestActionRating(actor, target); const ESM::Position& targetPos = target.getRefData().getPosition(); From f68bd3ba971dee1ed3b1dbdefa555a0ecb510508 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 12 Oct 2023 01:12:50 +0300 Subject: [PATCH 04/36] Launcher: Improve directory appending UX and heuristics (bug #7502) - Recognize more asset directories and omwgame files - Always let the user append the selected directory - Automatically pick the user-selected directory if it's the only valid directory - Add a caption to directory selection dialog, properly handle cancellation --- CHANGELOG.md | 1 + apps/launcher/datafilespage.cpp | 75 +++++++++++++++++++++------------ apps/launcher/datafilespage.hpp | 1 - 3 files changed, 50 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c01901ae1..be460568ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,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 #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 Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 756aaba131..b6192d3c02 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -40,8 +40,35 @@ namespace { void contentSubdirs(const QString& path, QStringList& dirs) { - QStringList fileFilter{ "*.esm", "*.esp", "*.omwaddon", "*.bsa", "*.ba2", "*.omwscripts" }; - QStringList dirFilter{ "bookart", "icons", "meshes", "music", "sound", "textures" }; + static const QStringList fileFilter{ + "*.esm", + "*.esp", + "*.bsa", + "*.ba2", + "*.omwgame", + "*.omwaddon", + "*.omwscripts", + }; + + static const QStringList dirFilter{ + "animations", + "bookart", + "fonts", + "icons", + "interface", + "l10n", + "meshes", + "music", + "mygui", + "scripts", + "shaders", + "sound", + "splash", + "strings", + "textures", + "trees", + "video", + }; QDir currentDir(path); if (!currentDir.entryInfoList(fileFilter, QDir::Files).empty() @@ -587,18 +614,6 @@ void Launcher::DataFilesPage::updateCloneProfileOkButton(const QString& text) mCloneProfileDialog->setOkButtonEnabled(!text.isEmpty() && ui.profilesComboBox->findText(text) == -1); } -QString Launcher::DataFilesPage::selectDirectory() -{ - QFileDialog fileDialog(this); - fileDialog.setFileMode(QFileDialog::Directory); - fileDialog.setOptions(QFileDialog::Option::ShowDirsOnly | QFileDialog::Option::ReadOnly); - - if (fileDialog.exec() == QDialog::Rejected) - return {}; - - return QDir(fileDialog.selectedFiles()[0]).canonicalPath(); -} - void Launcher::DataFilesPage::addSubdirectories(bool append) { int selectedRow = append ? ui.directoryListWidget->count() : ui.directoryListWidget->currentRow(); @@ -606,22 +621,30 @@ void Launcher::DataFilesPage::addSubdirectories(bool append) if (selectedRow == -1) return; - const auto rootDir = selectDirectory(); - if (rootDir.isEmpty()) + QString rootPath = QFileDialog::getExistingDirectory( + this, tr("Select Directory"), QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::Option::ReadOnly); + + if (rootPath.isEmpty()) return; - QStringList subdirs; - contentSubdirs(rootDir, subdirs); + const QDir rootDir(rootPath); + rootPath = rootDir.canonicalPath(); - if (subdirs.empty()) + QStringList subdirs; + contentSubdirs(rootPath, subdirs); + + // Always offer to append the root directory just in case + if (subdirs.isEmpty() || subdirs[0] != rootPath) + subdirs.prepend(rootPath); + else if (subdirs.size() == 1) { - // we didn't find anything that looks like a content directory, add directory selected by user - if (ui.directoryListWidget->findItems(rootDir, Qt::MatchFixedString).isEmpty()) - { - ui.directoryListWidget->addItem(rootDir); - mNewDataDirs.push_back(rootDir); - refreshDataFilesView(); - } + // We didn't find anything else that looks like a content directory + // Automatically add the directory selected by user + if (!ui.directoryListWidget->findItems(rootPath, Qt::MatchFixedString).isEmpty()) + return; + ui.directoryListWidget->addItem(rootPath); + mNewDataDirs.push_back(rootPath); + refreshDataFilesView(); return; } diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 033c91f9c7..dc3aeaef6f 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -131,7 +131,6 @@ namespace Launcher void reloadCells(QStringList selectedFiles); void refreshDataFilesView(); void updateNavMeshProgress(int minDataSize); - QString selectDirectory(); /** * Returns the file paths of all selected content files From f4d349070c99443cdf5f4607047a48093804c32b Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Tue, 24 Oct 2023 13:51:12 -0500 Subject: [PATCH 05/36] Add isTeleportingEnabled and setTeleportingEnabled --- apps/openmw/mwlua/types/player.cpp | 13 +++++++++++++ files/lua_api/openmw/types.lua | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index ab15385f08..52170f7075 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -138,6 +138,19 @@ namespace MWLua throw std::runtime_error("The argument must be a player."); return input->getControlSwitch(key); }; + player["isTeleportingEnabled"] = [](const Object& player) -> bool { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player."); + return MWBase::Environment::get().getWorld()->isTeleportingEnabled(); + }; + player["setTeleportingEnabled"] = [context](const Object& player, bool state) { + if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) + throw std::runtime_error("The argument must be a player."); + if (dynamic_cast(&player) && !dynamic_cast(&player)) + throw std::runtime_error("Only player and global scripts can toggle teleportation."); + context.mLuaManager->addAction([state] { MWBase::Environment::get().getWorld()->enableTeleporting(state); }, + "toggleTeleportingAction"); + }; player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) { if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) throw std::runtime_error("The argument must be a player."); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index c65a7323cb..3df689ce0e 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -919,13 +919,26 @@ -- @function [parent=#Player] getCrimeLevel -- @param openmw.core#GameObject player -- @return #number - + --- -- Whether the character generation for this player is finished. -- @function [parent=#Player] isCharGenFinished -- @param openmw.core#GameObject player -- @return #boolean +--- +-- Whether teleportation for this player is enabled. +-- @function [parent=#Player] isTeleportingEnabled +-- @param openmw.core#GameObject player +-- @param #boolean player +-- @return #boolean + +--- +-- Enables or disables teleportation for this player. +-- @function [parent=#Player] setTeleportingEnabled +-- @param openmw.core#GameObject player +-- @param #boolean state True to enable teleporting, false to disable. + --- -- Returns a list containing quests @{#PlayerQuest} for the specified player, indexed by quest ID. -- @function [parent=#Player] quests From 23bf6ed878fb7ce2b11e3023ddd9851a46728fce Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 16 Oct 2023 18:16:12 +0200 Subject: [PATCH 06/36] Remove missing scripts from actors much like is done for items --- CHANGELOG.md | 1 + apps/openmw/mwworld/esmstore.cpp | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ac1067da..9ea5c748e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Bug #7134: Saves with an invalid last generated RefNum can be loaded Bug #7163: Myar Aranath: Wheat breaks the GUI Bug #7172: Current music playlist continues playing indefinitely if next playlist is empty + Bug #7204: Missing actor scripts freeze the game 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 diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index f9b53cf21f..ad3d1f8d43 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -84,7 +84,8 @@ namespace } std::vector getNPCsToReplace(const MWWorld::Store& factions, - const MWWorld::Store& classes, const std::unordered_map& npcs) + const MWWorld::Store& classes, 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); @@ -122,6 +123,14 @@ namespace changed = true; } + if (!npc.mScript.empty() && !scripts.search(npc.mScript)) + { + Log(Debug::Verbose) << "NPC " << npc.mId << " (" << npc.mName << ") has nonexistent script " + << npc.mScript << ", ignoring it."; + npc.mScript = ESM::RefId(); + changed = true; + } + if (changed) npcsToReplace.push_back(npc); } @@ -138,9 +147,9 @@ namespace { if (!item.mScript.empty() && !scripts.search(item.mScript)) { + Log(Debug::Verbose) << MapT::mapped_type::getRecordType() << ' ' << id << " (" << item.mName + << ") has nonexistent script " << item.mScript << ", ignoring it."; item.mScript = ESM::RefId(); - Log(Debug::Verbose) << "Item " << id << " (" << item.mName << ") has nonexistent script " - << item.mScript << ", ignoring it."; } } } @@ -517,8 +526,8 @@ namespace MWWorld void ESMStore::validate() { auto& npcs = getWritable(); - std::vector npcsToReplace - = getNPCsToReplace(getWritable(), getWritable(), npcs.mStatic); + std::vector npcsToReplace = getNPCsToReplace( + getWritable(), getWritable(), getWritable(), npcs.mStatic); for (const ESM::NPC& npc : npcsToReplace) { @@ -526,6 +535,8 @@ namespace MWWorld npcs.insertStatic(npc); } + removeMissingScripts(getWritable(), getWritable().mStatic); + // Validate spell effects for invalid arguments std::vector spellsToReplace; auto& spells = getWritable(); @@ -605,8 +616,8 @@ namespace MWWorld auto& npcs = getWritable(); auto& scripts = getWritable(); - std::vector npcsToReplace - = getNPCsToReplace(getWritable(), getWritable(), npcs.mDynamic); + std::vector npcsToReplace = getNPCsToReplace( + getWritable(), getWritable(), getWritable(), npcs.mDynamic); for (const ESM::NPC& npc : npcsToReplace) npcs.insert(npc); @@ -614,6 +625,7 @@ namespace MWWorld removeMissingScripts(scripts, getWritable().mDynamic); removeMissingScripts(scripts, getWritable().mDynamic); removeMissingScripts(scripts, getWritable().mDynamic); + removeMissingScripts(scripts, getWritable().mDynamic); removeMissingScripts(scripts, getWritable().mDynamic); removeMissingObjects(getWritable()); From e6c02efbb7eb0fa70b3c445ed84f98d54c3a8d0e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 16 Oct 2023 02:34:07 +0300 Subject: [PATCH 07/36] Add record presence early-outs for various script instructions (feature #7625) AddItem, RemoveItem, StartScript, StopScript, AddTopic --- CHANGELOG.md | 1 + apps/openmw/mwscript/containerextensions.cpp | 13 +++++++++++++ apps/openmw/mwscript/dialogueextensions.cpp | 8 ++++++++ apps/openmw/mwscript/miscextensions.cpp | 17 +++++++++++++++++ 4 files changed, 39 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ac1067da..2ea1c33513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ Feature #7546: Start the game on Fredas Feature #7568: Uninterruptable scripted music Feature #7618: Show the player character's health in the save details + Feature #7625: Add some missing console error outputs Feature #7634: Support NiParticleBomb Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index edee3963e7..4a0d302cd8 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -31,6 +31,7 @@ #include "../mwmechanics/actorutil.hpp" #include "../mwmechanics/levelledlist.hpp" +#include "interpretercontext.hpp" #include "ref.hpp" namespace @@ -94,6 +95,12 @@ namespace MWScript Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->find(item)) + { + runtime.getContext().report("Failed to add item '" + item.getRefIdString() + "': unknown ID"); + return; + } + if (count < 0) count = static_cast(count); @@ -210,6 +217,12 @@ namespace MWScript Interpreter::Type_Integer count = runtime[0].mInteger; runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->find(item)) + { + runtime.getContext().report("Failed to remove item '" + item.getRefIdString() + "': unknown ID"); + return; + } + if (count < 0) count = static_cast(count); diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 1f0a9a37cf..6511fbdb01 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -16,6 +16,7 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "ref.hpp" @@ -89,6 +90,13 @@ namespace MWScript ESM::RefId topic = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + if (!MWBase::Environment::get().getESMStore()->get().search(topic)) + { + runtime.getContext().report( + "Failed to add topic '" + topic.getRefIdString() + "': topic record not found"); + return; + } + MWBase::Environment::get().getDialogueManager()->addTopic(topic); } }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 43da00afe3..687b512106 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -184,6 +185,14 @@ namespace MWScript MWWorld::Ptr target = R()(runtime, false); ESM::RefId name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + + if (!MWBase::Environment::get().getESMStore()->get().search(name)) + { + runtime.getContext().report( + "Failed to start global script '" + name.getRefIdString() + "': script record not found"); + return; + } + MWBase::Environment::get().getScriptManager()->getGlobalScripts().addScript(name, target); } }; @@ -206,6 +215,14 @@ namespace MWScript { const ESM::RefId& name = ESM::RefId::stringRefId(runtime.getStringLiteral(runtime[0].mInteger)); runtime.pop(); + + if (!MWBase::Environment::get().getESMStore()->get().search(name)) + { + runtime.getContext().report( + "Failed to stop global script '" + name.getRefIdString() + "': script record not found"); + return; + } + MWBase::Environment::get().getScriptManager()->getGlobalScripts().removeScript(name); } }; From 3e31142c0bba38f0f116d43e9c28f371ffa753f9 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Wed, 25 Oct 2023 17:24:18 -0500 Subject: [PATCH 08/36] Remove lua action --- apps/openmw/mwlua/types/player.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index 52170f7075..fc4c9858cc 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -148,8 +148,7 @@ namespace MWLua throw std::runtime_error("The argument must be a player."); if (dynamic_cast(&player) && !dynamic_cast(&player)) throw std::runtime_error("Only player and global scripts can toggle teleportation."); - context.mLuaManager->addAction([state] { MWBase::Environment::get().getWorld()->enableTeleporting(state); }, - "toggleTeleportingAction"); + MWBase::Environment::get().getWorld()->enableTeleporting(state); }; player["setControlSwitch"] = [input](const Object& player, std::string_view key, bool v) { if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) From 6a671186ee1089cde407c422d7c27471a2f88a81 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Thu, 26 Oct 2023 11:50:50 -0500 Subject: [PATCH 09/36] Removed unused var --- apps/openmw/mwlua/types/player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/types/player.cpp b/apps/openmw/mwlua/types/player.cpp index fc4c9858cc..cef0753817 100644 --- a/apps/openmw/mwlua/types/player.cpp +++ b/apps/openmw/mwlua/types/player.cpp @@ -143,7 +143,7 @@ namespace MWLua throw std::runtime_error("The argument must be a player."); return MWBase::Environment::get().getWorld()->isTeleportingEnabled(); }; - player["setTeleportingEnabled"] = [context](const Object& player, bool state) { + player["setTeleportingEnabled"] = [](const Object& player, bool state) { if (player.ptr() != MWBase::Environment::get().getWorld()->getPlayerPtr()) throw std::runtime_error("The argument must be a player."); if (dynamic_cast(&player) && !dynamic_cast(&player)) From dcd81d026f87c7242ea33f185bdeb33ae671561b Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 27 Oct 2023 12:24:42 +0200 Subject: [PATCH 10/36] Use settings values for Video settings * Convert window mode, vsync mode into enums, screenshot type into a struct. * Add missing doc for screenshot type. --- apps/launcher/graphicspage.cpp | 76 +++++++----------- apps/launcher/graphicspage.hpp | 3 +- apps/launcher/maindialog.cpp | 13 ++- apps/openmw/engine.cpp | 24 +++--- apps/openmw/mwgui/settingswindow.cpp | 58 ++++++-------- apps/openmw/mwgui/windowmanagerimp.cpp | 18 ++--- apps/openmw/mwinput/actionmanager.cpp | 7 +- apps/openmw/mwinput/sensormanager.cpp | 3 +- apps/openmw/mwlua/camerabindings.cpp | 8 +- apps/openmw/mwlua/uibindings.cpp | 5 +- apps/openmw/mwrender/characterpreview.cpp | 2 +- apps/openmw/mwrender/postprocessor.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 4 +- apps/openmw/mwrender/screenshotmanager.cpp | 79 ++++++++----------- apps/openmw/mwrender/util.cpp | 2 +- components/CMakeLists.txt | 12 ++- components/sdlutil/sdlgraphicswindow.cpp | 10 +-- components/sdlutil/sdlgraphicswindow.hpp | 10 +-- components/sdlutil/sdlinputwrapper.cpp | 6 +- components/sdlutil/sdlvideowrapper.cpp | 10 +-- components/sdlutil/sdlvideowrapper.hpp | 4 +- components/sdlutil/vsyncmode.hpp | 14 ++++ components/settings/categories/video.hpp | 14 ++-- components/settings/screenshotsettings.hpp | 29 +++++++ components/settings/settings.cpp | 51 ++++++++++++ components/settings/settings.hpp | 38 +++++++-- components/settings/settingvalue.hpp | 38 +++++++++ components/settings/windowmode.hpp | 14 ++++ components/stereo/stereomanager.cpp | 2 +- .../reference/modding/settings/video.rst | 9 +++ files/settings-default.cfg | 2 +- 31 files changed, 346 insertions(+), 221 deletions(-) create mode 100644 components/sdlutil/vsyncmode.hpp create mode 100644 components/settings/screenshotsettings.hpp create mode 100644 components/settings/windowmode.hpp diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 9a10bf6d9c..18fb57805f 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -96,32 +96,29 @@ bool Launcher::GraphicsPage::loadSettings() // Visuals - int vsync = Settings::Manager::getInt("vsync mode", "Video"); - if (vsync < 0 || vsync > 2) - vsync = 0; + const int vsync = Settings::video().mVsyncMode; vSyncComboBox->setCurrentIndex(vsync); - size_t windowMode = static_cast(Settings::Manager::getInt("window mode", "Video")); - if (windowMode > static_cast(Settings::WindowMode::Windowed)) - windowMode = 0; - windowModeComboBox->setCurrentIndex(windowMode); - slotFullScreenChanged(windowMode); + const Settings::WindowMode windowMode = Settings::video().mWindowMode; - if (Settings::Manager::getBool("window border", "Video")) + windowModeComboBox->setCurrentIndex(static_cast(windowMode)); + handleWindowModeChange(windowMode); + + if (Settings::video().mWindowBorder) windowBorderCheckBox->setCheckState(Qt::Checked); // aaValue is the actual value (0, 1, 2, 4, 8, 16) - int aaValue = Settings::Manager::getInt("antialiasing", "Video"); + const int aaValue = Settings::video().mAntialiasing; // aaIndex is the index into the allowed values in the pull down. - int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); + const int aaIndex = antiAliasingComboBox->findText(QString::number(aaValue)); if (aaIndex != -1) antiAliasingComboBox->setCurrentIndex(aaIndex); - int width = Settings::Manager::getInt("resolution x", "Video"); - int height = Settings::Manager::getInt("resolution y", "Video"); + const int width = Settings::video().mResolutionX; + const int height = Settings::video().mResolutionY; QString resolution = QString::number(width) + QString(" x ") + QString::number(height); - screenComboBox->setCurrentIndex(Settings::Manager::getInt("screen", "Video")); + screenComboBox->setCurrentIndex(Settings::video().mScreen); int resIndex = resolutionComboBox->findText(resolution, Qt::MatchStartsWith); @@ -137,7 +134,7 @@ bool Launcher::GraphicsPage::loadSettings() customHeightSpinBox->setValue(height); } - float fpsLimit = Settings::Manager::getFloat("framerate limit", "Video"); + const float fpsLimit = Settings::video().mFramerateLimit; if (fpsLimit != 0) { framerateLimitCheckBox->setCheckState(Qt::Checked); @@ -198,23 +195,10 @@ void Launcher::GraphicsPage::saveSettings() { // Visuals - // Ensure we only set the new settings if they changed. This is to avoid cluttering the - // user settings file (which by definition should only contain settings the user has touched) - int cVSync = vSyncComboBox->currentIndex(); - if (cVSync != Settings::Manager::getInt("vsync mode", "Video")) - Settings::Manager::setInt("vsync mode", "Video", cVSync); - - int cWindowMode = windowModeComboBox->currentIndex(); - if (cWindowMode != Settings::Manager::getInt("window mode", "Video")) - Settings::Manager::setInt("window mode", "Video", cWindowMode); - - bool cWindowBorder = windowBorderCheckBox->checkState(); - if (cWindowBorder != Settings::Manager::getBool("window border", "Video")) - Settings::Manager::setBool("window border", "Video", cWindowBorder); - - int cAAValue = antiAliasingComboBox->currentText().toInt(); - if (cAAValue != Settings::Manager::getInt("antialiasing", "Video")) - Settings::Manager::setInt("antialiasing", "Video", cAAValue); + Settings::video().mVsyncMode.set(static_cast(vSyncComboBox->currentIndex())); + Settings::video().mWindowMode.set(static_cast(windowModeComboBox->currentIndex())); + Settings::video().mWindowBorder.set(windowBorderCheckBox->checkState() == Qt::Checked); + Settings::video().mAntialiasing.set(antiAliasingComboBox->currentText().toInt()); int cWidth = 0; int cHeight = 0; @@ -234,25 +218,17 @@ void Launcher::GraphicsPage::saveSettings() cHeight = customHeightSpinBox->value(); } - if (cWidth != Settings::Manager::getInt("resolution x", "Video")) - Settings::Manager::setInt("resolution x", "Video", cWidth); - - if (cHeight != Settings::Manager::getInt("resolution y", "Video")) - Settings::Manager::setInt("resolution y", "Video", cHeight); - - int cScreen = screenComboBox->currentIndex(); - if (cScreen != Settings::Manager::getInt("screen", "Video")) - Settings::Manager::setInt("screen", "Video", cScreen); + Settings::video().mResolutionX.set(cWidth); + Settings::video().mResolutionY.set(cHeight); + Settings::video().mScreen.set(screenComboBox->currentIndex()); if (framerateLimitCheckBox->checkState() != Qt::Unchecked) { - float cFpsLimit = framerateLimitSpinBox->value(); - if (cFpsLimit != Settings::Manager::getFloat("framerate limit", "Video")) - Settings::Manager::setFloat("framerate limit", "Video", cFpsLimit); + Settings::video().mFramerateLimit.set(framerateLimitSpinBox->value()); } - else if (Settings::Manager::getFloat("framerate limit", "Video") != 0) + else if (Settings::video().mFramerateLimit != 0) { - Settings::Manager::setFloat("framerate limit", "Video", 0); + Settings::video().mFramerateLimit.set(0); } // Lighting @@ -392,8 +368,12 @@ void Launcher::GraphicsPage::screenChanged(int screen) void Launcher::GraphicsPage::slotFullScreenChanged(int mode) { - if (mode == static_cast(Settings::WindowMode::Fullscreen) - || mode == static_cast(Settings::WindowMode::WindowedFullscreen)) + handleWindowModeChange(static_cast(mode)); +} + +void Launcher::GraphicsPage::handleWindowModeChange(Settings::WindowMode mode) +{ + if (mode == Settings::WindowMode::Fullscreen || mode == Settings::WindowMode::WindowedFullscreen) { standardRadioButton->toggle(); customRadioButton->setEnabled(false); diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index 92bdf35ac4..85f91d1ff1 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -3,7 +3,7 @@ #include "ui_graphicspage.h" -#include +#include namespace Files { @@ -40,6 +40,7 @@ namespace Launcher static QRect getMaximumResolution(); bool setupSDL(); + void handleWindowModeChange(Settings::WindowMode state); }; } #endif diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 023d6b729a..bba3bbe5e1 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -1,13 +1,5 @@ #include "maindialog.hpp" -#include -#include -#include -#include -#include -#include -#include - #include #include #include @@ -15,10 +7,15 @@ #include #include +#include +#include #include #include #include +#include #include +#include +#include #include "datafilespage.hpp" #include "graphicspage.hpp" diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7393562cfb..3cac85984b 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -452,14 +452,13 @@ void OMW::Engine::setSkipMenu(bool skipMenu, bool newGame) void OMW::Engine::createWindow() { - int screen = Settings::Manager::getInt("screen", "Video"); - int width = Settings::Manager::getInt("resolution x", "Video"); - int height = Settings::Manager::getInt("resolution y", "Video"); - Settings::WindowMode windowMode - = static_cast(Settings::Manager::getInt("window mode", "Video")); - bool windowBorder = Settings::Manager::getBool("window border", "Video"); - int vsync = Settings::Manager::getInt("vsync mode", "Video"); - unsigned int antialiasing = std::max(0, Settings::Manager::getInt("antialiasing", "Video")); + const int screen = Settings::video().mScreen; + const int width = Settings::video().mResolutionX; + const int height = Settings::video().mResolutionY; + const Settings::WindowMode windowMode = Settings::video().mWindowMode; + const bool windowBorder = Settings::video().mWindowBorder; + const SDLUtil::VSyncMode vsync = Settings::video().mVsyncMode; + unsigned antialiasing = static_cast(Settings::video().mAntialiasing); int pos_x = SDL_WINDOWPOS_CENTERED_DISPLAY(screen), pos_y = SDL_WINDOWPOS_CENTERED_DISPLAY(screen); @@ -482,8 +481,7 @@ void OMW::Engine::createWindow() if (!windowBorder) flags |= SDL_WINDOW_BORDERLESS; - SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, - Settings::Manager::getBool("minimize on focus loss", "Video") ? "1" : "0"); + SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, Settings::video().mMinimizeOnFocusLoss ? "1" : "0"); checkSDLError(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8)); checkSDLError(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8)); @@ -513,7 +511,7 @@ void OMW::Engine::createWindow() Log(Debug::Warning) << "Warning: " << antialiasing << "x antialiasing not supported, trying " << antialiasing / 2; antialiasing /= 2; - Settings::Manager::setInt("antialiasing", "Video", antialiasing); + Settings::video().mAntialiasing.set(antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } @@ -560,7 +558,7 @@ void OMW::Engine::createWindow() SDL_DestroyWindow(mWindow); mWindow = nullptr; antialiasing /= 2; - Settings::Manager::setInt("antialiasing", "Video", antialiasing); + Settings::video().mAntialiasing.set(antialiasing); checkSDLError(SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, antialiasing)); continue; } @@ -866,7 +864,7 @@ void OMW::Engine::go() // Do not try to outsmart the OS thread scheduler (see bug #4785). mViewer->setUseConfigureAffinity(false); - mEnvironment.setFrameRateLimit(Settings::Manager::getFloat("framerate limit", "Video")); + mEnvironment.setFrameRateLimit(Settings::video().mFramerateLimit); prepareEngine(); diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 1a41f9bb55..fbcdb963fc 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -354,7 +354,7 @@ namespace MWGui += MyGUI::newDelegate(this, &SettingsWindow::onResetDefaultBindings); // fill resolution list - int screen = Settings::Manager::getInt("screen", "Video"); + const int screen = Settings::video().mScreen; int numDisplayModes = SDL_GetNumDisplayModes(screen); std::vector> resolutions; for (int i = 0; i < numDisplayModes; i++) @@ -396,8 +396,7 @@ namespace MWGui updateMaxLightsComboBox(mMaxLights); - Settings::WindowMode windowMode - = static_cast(Settings::Manager::getInt("window mode", "Video")); + const Settings::WindowMode windowMode = Settings::video().mWindowMode; mWindowBorderButton->setEnabled( windowMode != Settings::WindowMode::Fullscreen && windowMode != Settings::WindowMode::WindowedFullscreen); @@ -491,8 +490,8 @@ namespace MWGui int resX, resY; parseResolution(resX, resY, resStr); - Settings::Manager::setInt("resolution x", "Video", resX); - Settings::Manager::setInt("resolution y", "Video", resY); + Settings::video().mResolutionX.set(resX); + Settings::video().mResolutionY.set(resY); apply(); } @@ -506,8 +505,8 @@ namespace MWGui { mResolutionList->setIndexSelected(MyGUI::ITEM_NONE); - int currentX = Settings::Manager::getInt("resolution x", "Video"); - int currentY = Settings::Manager::getInt("resolution y", "Video"); + const int currentX = Settings::video().mResolutionX; + const int currentY = Settings::video().mResolutionY; for (size_t i = 0; i < mResolutionList->getItemCount(); ++i) { @@ -591,23 +590,22 @@ namespace MWGui "#{OMWEngine:ChangeRequiresRestart}", { "#{Interface:OK}" }, true); } - void SettingsWindow::onVSyncModeChanged(MyGUI::ComboBox* _sender, size_t pos) + void SettingsWindow::onVSyncModeChanged(MyGUI::ComboBox* sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; - int index = static_cast(_sender->getIndexSelected()); - Settings::Manager::setInt("vsync mode", "Video", index); + Settings::video().mVsyncMode.set(static_cast(sender->getIndexSelected())); apply(); } - void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* _sender, size_t pos) + void SettingsWindow::onWindowModeChanged(MyGUI::ComboBox* sender, size_t pos) { if (pos == MyGUI::ITEM_NONE) return; - int index = static_cast(_sender->getIndexSelected()); - if (index == static_cast(Settings::WindowMode::WindowedFullscreen)) + const Settings::WindowMode windowMode = static_cast(sender->getIndexSelected()); + if (windowMode == Settings::WindowMode::WindowedFullscreen) { mResolutionList->setEnabled(false); mWindowModeHint->setVisible(true); @@ -618,12 +616,12 @@ namespace MWGui mWindowModeHint->setVisible(false); } - if (index == static_cast(Settings::WindowMode::Windowed)) + if (windowMode == Settings::WindowMode::Windowed) mWindowBorderButton->setEnabled(true); else mWindowBorderButton->setEnabled(false); - Settings::Manager::setInt("window mode", "Video", index); + Settings::video().mWindowMode.set(windowMode); apply(); } @@ -849,14 +847,12 @@ namespace MWGui void SettingsWindow::updateWindowModeSettings() { - size_t index = static_cast(Settings::Manager::getInt("window mode", "Video")); + const Settings::WindowMode windowMode = Settings::video().mWindowMode; + const std::size_t windowModeIndex = static_cast(windowMode); - if (index > static_cast(Settings::WindowMode::Windowed)) - index = MyGUI::ITEM_NONE; + mWindowModeList->setIndexSelected(windowModeIndex); - mWindowModeList->setIndexSelected(index); - - if (index != static_cast(Settings::WindowMode::Windowed) && index != MyGUI::ITEM_NONE) + if (windowMode != Settings::WindowMode::Windowed && windowModeIndex != MyGUI::ITEM_NONE) { // check if this resolution is supported in fullscreen if (mResolutionList->getIndexSelected() != MyGUI::ITEM_NONE) @@ -864,8 +860,8 @@ namespace MWGui const std::string& resStr = mResolutionList->getItemNameAt(mResolutionList->getIndexSelected()); int resX, resY; parseResolution(resX, resY, resStr); - Settings::Manager::setInt("resolution x", "Video", resX); - Settings::Manager::setInt("resolution y", "Video", resY); + Settings::video().mResolutionX.set(resX); + Settings::video().mResolutionY.set(resY); } bool supported = false; @@ -882,8 +878,7 @@ namespace MWGui fallbackY = resY; } - if (resX == Settings::Manager::getInt("resolution x", "Video") - && resY == Settings::Manager::getInt("resolution y", "Video")) + if (resX == Settings::video().mResolutionX && resY == Settings::video().mResolutionY) supported = true; } @@ -891,26 +886,21 @@ namespace MWGui { if (fallbackX != 0 && fallbackY != 0) { - Settings::Manager::setInt("resolution x", "Video", fallbackX); - Settings::Manager::setInt("resolution y", "Video", fallbackY); + Settings::video().mResolutionX.set(fallbackX); + Settings::video().mResolutionY.set(fallbackY); } } mWindowBorderButton->setEnabled(false); } - if (index == static_cast(Settings::WindowMode::WindowedFullscreen)) + if (windowMode == Settings::WindowMode::WindowedFullscreen) mResolutionList->setEnabled(false); } void SettingsWindow::updateVSyncModeSettings() { - int index = static_cast(Settings::Manager::getInt("vsync mode", "Video")); - - if (index < 0 || index > 2) - index = 0; - - mVSyncModeList->setIndexSelected(index); + mVSyncModeList->setIndexSelected(static_cast(Settings::video().mVsyncMode)); } void SettingsWindow::layoutControlsBox() diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 43b37623d5..1d41bab34f 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -294,8 +294,7 @@ namespace MWGui += MyGUI::newDelegate(this, &WindowManager::onClipboardRequested); mVideoWrapper = std::make_unique(window, viewer); - mVideoWrapper->setGammaContrast( - Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); + mVideoWrapper->setGammaContrast(Settings::video().mGamma, Settings::video().mContrast); if (useShaders) mGuiPlatform->getRenderManagerPtr()->enableShaders(mResourceSystem->getSceneManager()->getShaderManager()); @@ -1157,25 +1156,22 @@ namespace MWGui changeRes = true; else if (setting.first == "Video" && setting.second == "vsync mode") - mVideoWrapper->setSyncToVBlank(Settings::Manager::getInt("vsync mode", "Video")); + mVideoWrapper->setSyncToVBlank(Settings::video().mVsyncMode); else if (setting.first == "Video" && (setting.second == "gamma" || setting.second == "contrast")) - mVideoWrapper->setGammaContrast( - Settings::Manager::getFloat("gamma", "Video"), Settings::Manager::getFloat("contrast", "Video")); + mVideoWrapper->setGammaContrast(Settings::video().mGamma, Settings::video().mContrast); } if (changeRes) { - mVideoWrapper->setVideoMode(Settings::Manager::getInt("resolution x", "Video"), - Settings::Manager::getInt("resolution y", "Video"), - static_cast(Settings::Manager::getInt("window mode", "Video")), - Settings::Manager::getBool("window border", "Video")); + mVideoWrapper->setVideoMode(Settings::video().mResolutionX, Settings::video().mResolutionY, + Settings::video().mWindowMode, Settings::video().mWindowBorder); } } void WindowManager::windowResized(int x, int y) { - Settings::Manager::setInt("resolution x", "Video", x); - Settings::Manager::setInt("resolution y", "Video", y); + Settings::video().mResolutionX.set(x); + Settings::video().mResolutionY.set(y); // We only want to process changes to window-size related settings. Settings::CategorySettingVector filter = { { "Video", "resolution x" }, { "Video", "resolution y" } }; diff --git a/apps/openmw/mwinput/actionmanager.cpp b/apps/openmw/mwinput/actionmanager.cpp index eb82c160f9..5f9a3fde85 100644 --- a/apps/openmw/mwinput/actionmanager.cpp +++ b/apps/openmw/mwinput/actionmanager.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" @@ -170,10 +170,9 @@ namespace MWInput void ActionManager::screenshot() { - const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); - bool regularScreenshot = settingStr.empty() || settingStr == "regular"; + const Settings::ScreenshotSettings& settings = Settings::video().mScreenshotType; - if (regularScreenshot) + if (settings.mType == Settings::ScreenshotType::Regular) { mScreenCaptureHandler->setFramesToCapture(1); mScreenCaptureHandler->captureNextFrame(*mViewer); diff --git a/apps/openmw/mwinput/sensormanager.cpp b/apps/openmw/mwinput/sensormanager.cpp index 32e48a008e..298006030a 100644 --- a/apps/openmw/mwinput/sensormanager.cpp +++ b/apps/openmw/mwinput/sensormanager.cpp @@ -42,8 +42,7 @@ namespace MWInput float angle = 0; - SDL_DisplayOrientation currentOrientation - = SDL_GetDisplayOrientation(Settings::Manager::getInt("screen", "Video")); + SDL_DisplayOrientation currentOrientation = SDL_GetDisplayOrientation(Settings::video().mScreen); switch (currentOrientation) { case SDL_ORIENTATION_UNKNOWN: diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index bbdba00ee2..e3470eb853 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -94,8 +94,8 @@ namespace MWLua api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{ camera->getViewMatrix() }; }; api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f { - double width = Settings::Manager::getInt("resolution x", "Video"); - double height = Settings::Manager::getInt("resolution y", "Video"); + const double width = Settings::video().mResolutionX; + const double height = Settings::video().mResolutionY; double aspect = (height == 0.0) ? 1.0 : width / height; double fovTan = std::tan(osg::DegreesToRadians(renderingManager->getFieldOfView()) / 2); osg::Matrixf invertedViewMatrix; @@ -106,8 +106,8 @@ namespace MWLua }; api["worldToViewportVector"] = [camera](osg::Vec3f pos) { - double width = Settings::Manager::getInt("resolution x", "Video"); - double height = Settings::Manager::getInt("resolution y", "Video"); + const double width = Settings::video().mResolutionX; + const double height = Settings::video().mResolutionY; osg::Matrix windowMatrix = osg::Matrix::translate(1.0, 1.0, 1.0) * osg::Matrix::scale(0.5 * width, 0.5 * height, 0.5); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 79f5fac9a1..d42f7b0637 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -239,10 +239,7 @@ namespace MWLua return luaManager->uiResourceManager()->registerTexture(data); }; - api["screenSize"] = []() { - return osg::Vec2f( - Settings::Manager::getInt("resolution x", "Video"), Settings::Manager::getInt("resolution y", "Video")); - }; + api["screenSize"] = []() { return osg::Vec2f(Settings::video().mResolutionX, Settings::video().mResolutionY); }; api["_getAllUiModes"] = [](sol::this_state lua) { sol::table res(lua, sol::create); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index fbf5bf112a..86371dc0bf 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -154,7 +154,7 @@ namespace MWRender public: CharacterPreviewRTTNode(uint32_t sizeX, uint32_t sizeY) - : RTTNode(sizeX, sizeY, Settings::Manager::getInt("antialiasing", "Video"), false, 0, + : RTTNode(sizeX, sizeY, Settings::video().mAntialiasing, false, 0, StereoAwareness::Unaware_MultiViewShaders, shouldAddMSAAIntermediateTarget()) , mAspectRatio(static_cast(sizeX) / static_cast(sizeY)) { diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 9162657e30..a6945de299 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -112,7 +112,7 @@ namespace MWRender : osg::Group() , mEnableLiveReload(false) , mRootNode(rootNode) - , mSamples(Settings::Manager::getInt("antialiasing", "Video")) + , mSamples(Settings::video().mAntialiasing) , mDirty(false) , mDirtyFrameId(0) , mRendering(rendering) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b7685e0cd8..c5d268f561 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1230,8 +1230,8 @@ namespace MWRender if (mViewDistance < mNearClip) throw std::runtime_error("Viewing distance is less than near clip"); - double width = Settings::Manager::getInt("resolution x", "Video"); - double height = Settings::Manager::getInt("resolution y", "Video"); + const double width = Settings::video().mResolutionX; + const double height = Settings::video().mResolutionY; double aspect = (height == 0.0) ? 1.0 : width / height; float fov = mFieldOfView; diff --git a/apps/openmw/mwrender/screenshotmanager.cpp b/apps/openmw/mwrender/screenshotmanager.cpp index 336a321cf0..b4bdda0a28 100644 --- a/apps/openmw/mwrender/screenshotmanager.cpp +++ b/apps/openmw/mwrender/screenshotmanager.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -20,7 +21,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwgui/loadingscreen.hpp" #include "postprocessor.hpp" #include "util.hpp" @@ -29,7 +29,7 @@ namespace MWRender { - enum Screenshot360Type + enum class Screenshot360Type { Spherical, Cylindrical, @@ -161,59 +161,46 @@ namespace MWRender bool ScreenshotManager::screenshot360(osg::Image* image) { - int screenshotW = mViewer->getCamera()->getViewport()->width(); - int screenshotH = mViewer->getCamera()->getViewport()->height(); - Screenshot360Type screenshotMapping = Spherical; + const Settings::ScreenshotSettings& settings = Settings::video().mScreenshotType; - const std::string& settingStr = Settings::Manager::getString("screenshot type", "Video"); - std::vector settingArgs; - Misc::StringUtils::split(settingStr, settingArgs); + Screenshot360Type screenshotMapping = Screenshot360Type::Spherical; - if (settingArgs.size() > 0) + switch (settings.mType) { - std::string_view typeStrings[4] = { "spherical", "cylindrical", "planet", "cubemap" }; - bool found = false; - - for (int i = 0; i < 4; ++i) - { - if (settingArgs[0] == typeStrings[i]) - { - screenshotMapping = static_cast(i); - found = true; - break; - } - } - - if (!found) - { - Log(Debug::Warning) << "Wrong screenshot type: " << settingArgs[0] << "."; + case Settings::ScreenshotType::Regular: + Log(Debug::Warning) << "Wrong screenshot 360 type: regular."; return false; - } + case Settings::ScreenshotType::Cylindrical: + screenshotMapping = Screenshot360Type::Cylindrical; + break; + case Settings::ScreenshotType::Spherical: + screenshotMapping = Screenshot360Type::Spherical; + break; + case Settings::ScreenshotType::Planet: + screenshotMapping = Screenshot360Type::Planet; + break; + case Settings::ScreenshotType::Cubemap: + screenshotMapping = Screenshot360Type::RawCubemap; + break; } + int screenshotW = mViewer->getCamera()->getViewport()->width(); + + if (settings.mWidth.has_value()) + screenshotW = *settings.mWidth; + + int screenshotH = mViewer->getCamera()->getViewport()->height(); + + if (settings.mHeight.has_value()) + screenshotH = *settings.mHeight; + // planet mapping needs higher resolution - int cubeSize = screenshotMapping == Planet ? screenshotW : screenshotW / 2; - - if (settingArgs.size() > 1) - { - screenshotW = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[1], 0)); - } - - if (settingArgs.size() > 2) - { - screenshotH = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[2], 0)); - } - - if (settingArgs.size() > 3) - { - cubeSize = std::min(5000, Misc::StringUtils::toNumeric(settingArgs[3], 0)); - } - - bool rawCubemap = screenshotMapping == RawCubemap; + const int cubeSize = screenshotMapping == Screenshot360Type::Planet ? screenshotW : screenshotW / 2; + const bool rawCubemap = screenshotMapping == Screenshot360Type::RawCubemap; if (rawCubemap) screenshotW = cubeSize * 6; // the image will consist of 6 cube sides in a row - else if (screenshotMapping == Planet) + else if (screenshotMapping == Screenshot360Type::Planet) screenshotH = screenshotW; // use square resolution for planet mapping std::vector> images; @@ -276,7 +263,7 @@ namespace MWRender stateset->setAttributeAndModes(shaderMgr.getProgram("360"), osg::StateAttribute::ON); stateset->addUniform(new osg::Uniform("cubeMap", 0)); - stateset->addUniform(new osg::Uniform("mapping", screenshotMapping)); + stateset->addUniform(new osg::Uniform("mapping", static_cast(screenshotMapping))); stateset->setTextureAttributeAndModes(0, cubeTexture, osg::StateAttribute::ON); screenshotCamera->addChild(quad); diff --git a/apps/openmw/mwrender/util.cpp b/apps/openmw/mwrender/util.cpp index 234b022f5d..cd03784e8c 100644 --- a/apps/openmw/mwrender/util.cpp +++ b/apps/openmw/mwrender/util.cpp @@ -70,7 +70,7 @@ namespace MWRender bool shouldAddMSAAIntermediateTarget() { - return Settings::shaders().mAntialiasAlphaTest && Settings::Manager::getInt("antialiasing", "Video") > 1; + return Settings::shaders().mAntialiasAlphaTest && Settings::video().mAntialiasing > 1; } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 36bd74d217..b2fc46f358 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -87,6 +87,8 @@ add_component_dir (settings settingvalue shadermanager values + screenshotsettings + windowmode ) add_component_dir (bsa @@ -336,7 +338,15 @@ add_component_dir (fontloader ) add_component_dir (sdlutil - gl4es_init sdlgraphicswindow imagetosurface sdlinputwrapper sdlvideowrapper events sdlcursormanager sdlmappings + events + gl4es_init + imagetosurface + sdlcursormanager + sdlgraphicswindow + sdlinputwrapper + sdlmappings + sdlvideowrapper + vsyncmode ) add_component_dir (version diff --git a/components/sdlutil/sdlgraphicswindow.cpp b/components/sdlutil/sdlgraphicswindow.cpp index 3c2efc3728..36947460df 100644 --- a/components/sdlutil/sdlgraphicswindow.cpp +++ b/components/sdlutil/sdlgraphicswindow.cpp @@ -14,22 +14,16 @@ namespace SDLUtil close(true); } - GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, int vsync) + GraphicsWindowSDL2::GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, VSyncMode vsyncMode) : mWindow(nullptr) , mContext(nullptr) , mValid(false) , mRealized(false) , mOwnsWindow(false) + , mVSyncMode(vsyncMode) { _traits = traits; - if (vsync == 2) - mVSyncMode = VSyncMode::Adaptive; - else if (vsync == 1) - mVSyncMode = VSyncMode::Enabled; - else - mVSyncMode = VSyncMode::Disabled; - init(); if (GraphicsWindowSDL2::valid()) { diff --git a/components/sdlutil/sdlgraphicswindow.hpp b/components/sdlutil/sdlgraphicswindow.hpp index 3af6ef9276..238c872fb9 100644 --- a/components/sdlutil/sdlgraphicswindow.hpp +++ b/components/sdlutil/sdlgraphicswindow.hpp @@ -5,14 +5,10 @@ #include +#include "vsyncmode.hpp" + namespace SDLUtil { - enum VSyncMode - { - Disabled = 0, - Enabled = 1, - Adaptive = 2 - }; class GraphicsWindowSDL2 : public osgViewer::GraphicsWindow { @@ -29,7 +25,7 @@ namespace SDLUtil virtual ~GraphicsWindowSDL2(); public: - GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, int vsync); + GraphicsWindowSDL2(osg::GraphicsContext::Traits* traits, VSyncMode vsyncMode); bool isSameKindAs(const Object* object) const override { diff --git a/components/sdlutil/sdlinputwrapper.cpp b/components/sdlutil/sdlinputwrapper.cpp index 07cab33ad3..cc9706732e 100644 --- a/components/sdlutil/sdlinputwrapper.cpp +++ b/components/sdlutil/sdlinputwrapper.cpp @@ -1,7 +1,7 @@ #include "sdlinputwrapper.hpp" #include -#include +#include #include @@ -187,8 +187,10 @@ namespace SDLUtil { case SDL_DISPLAYEVENT_ORIENTATION: if (mSensorListener - && evt.display.display == (unsigned int)Settings::Manager::getInt("screen", "Video")) + && evt.display.display == static_cast(Settings::video().mScreen)) + { mSensorListener->displayOrientationChanged(); + } break; default: break; diff --git a/components/sdlutil/sdlvideowrapper.cpp b/components/sdlutil/sdlvideowrapper.cpp index 3b612214b8..d93c16aace 100644 --- a/components/sdlutil/sdlvideowrapper.cpp +++ b/components/sdlutil/sdlvideowrapper.cpp @@ -30,14 +30,8 @@ namespace SDLUtil SDL_SetWindowGammaRamp(mWindow, mOldSystemGammaRamp, &mOldSystemGammaRamp[256], &mOldSystemGammaRamp[512]); } - void VideoWrapper::setSyncToVBlank(int mode) + void VideoWrapper::setSyncToVBlank(VSyncMode vsyncMode) { - VSyncMode vsyncMode = VSyncMode::Disabled; - if (mode == 1) - vsyncMode = VSyncMode::Enabled; - else if (mode == 2) - vsyncMode = VSyncMode::Adaptive; - osgViewer::Viewer::Windows windows; mViewer->getWindows(windows); mViewer->stopThreading(); @@ -47,7 +41,7 @@ namespace SDLUtil if (GraphicsWindowSDL2* sdl2win = dynamic_cast(win)) sdl2win->setSyncToVBlank(vsyncMode); else - win->setSyncToVBlank(static_cast(mode)); + win->setSyncToVBlank(vsyncMode != VSyncMode::Disabled); } mViewer->startThreading(); } diff --git a/components/sdlutil/sdlvideowrapper.hpp b/components/sdlutil/sdlvideowrapper.hpp index 9ed6ff1252..7977de40a7 100644 --- a/components/sdlutil/sdlvideowrapper.hpp +++ b/components/sdlutil/sdlvideowrapper.hpp @@ -5,6 +5,8 @@ #include +#include "vsyncmode.hpp" + struct SDL_Window; namespace osgViewer @@ -26,7 +28,7 @@ namespace SDLUtil VideoWrapper(SDL_Window* window, osg::ref_ptr viewer); ~VideoWrapper(); - void setSyncToVBlank(int mode); + void setSyncToVBlank(VSyncMode vsyncMode); void setGammaContrast(float gamma, float contrast); diff --git a/components/sdlutil/vsyncmode.hpp b/components/sdlutil/vsyncmode.hpp new file mode 100644 index 0000000000..5156addc40 --- /dev/null +++ b/components/sdlutil/vsyncmode.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_SDLUTIL_VSYNCMODE_H +#define OPENMW_COMPONENTS_SDLUTIL_VSYNCMODE_H + +namespace SDLUtil +{ + enum VSyncMode + { + Disabled = 0, + Enabled = 1, + Adaptive = 2 + }; +} + +#endif diff --git a/components/settings/categories/video.hpp b/components/settings/categories/video.hpp index fd6bb0018a..0e0f7a75bb 100644 --- a/components/settings/categories/video.hpp +++ b/components/settings/categories/video.hpp @@ -1,8 +1,12 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include +#include + +#include #include #include @@ -20,16 +24,16 @@ namespace Settings SettingValue mResolutionX{ mIndex, "Video", "resolution x", makeMaxSanitizerInt(1) }; SettingValue mResolutionY{ mIndex, "Video", "resolution y", makeMaxSanitizerInt(1) }; - SettingValue mWindowMode{ mIndex, "Video", "window mode", makeEnumSanitizerInt({ 0, 1, 2 }) }; + SettingValue mWindowMode{ mIndex, "Video", "window mode" }; SettingValue mScreen{ mIndex, "Video", "screen", makeMaxSanitizerInt(0) }; SettingValue mMinimizeOnFocusLoss{ mIndex, "Video", "minimize on focus loss" }; SettingValue mWindowBorder{ mIndex, "Video", "window border" }; SettingValue mAntialiasing{ mIndex, "Video", "antialiasing", makeMaxSanitizerInt(0) }; - SettingValue mVsyncMode{ mIndex, "Video", "vsync mode", makeEnumSanitizerInt({ 0, 1, 2 }) }; + SettingValue mVsyncMode{ mIndex, "Video", "vsync mode" }; SettingValue mFramerateLimit{ mIndex, "Video", "framerate limit", makeMaxSanitizerFloat(0) }; SettingValue mContrast{ mIndex, "Video", "contrast", makeMaxStrictSanitizerFloat(0) }; SettingValue mGamma{ mIndex, "Video", "gamma", makeMaxStrictSanitizerFloat(0) }; - SettingValue mScreenshotType{ mIndex, "Video", "screenshot type" }; + SettingValue mScreenshotType{ mIndex, "Video", "screenshot type" }; }; } diff --git a/components/settings/screenshotsettings.hpp b/components/settings/screenshotsettings.hpp new file mode 100644 index 0000000000..6475ace005 --- /dev/null +++ b/components/settings/screenshotsettings.hpp @@ -0,0 +1,29 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_SCREENSHOTSETTINGS_H +#define OPENMW_COMPONENTS_SETTINGS_SCREENSHOTSETTINGS_H + +#include +#include + +namespace Settings +{ + enum class ScreenshotType + { + Regular, + Cylindrical, + Spherical, + Planet, + Cubemap, + }; + + struct ScreenshotSettings + { + ScreenshotType mType; + std::optional mWidth; + std::optional mHeight; + std::optional mCubeSize; + + auto operator<=>(const ScreenshotSettings& value) const = default; + }; +} + +#endif diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 7b31bf6aad..c64e179efd 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -119,6 +119,23 @@ namespace Settings Log(Debug::Warning) << "Invalid HRTF mode value: " << static_cast(value) << ", fallback to auto (-1)"; return -1; } + + ScreenshotType parseScreenshotType(std::string_view value) + { + if (value == "regular") + return ScreenshotType::Regular; + if (value == "spherical") + return ScreenshotType::Spherical; + if (value == "cylindrical") + return ScreenshotType::Cylindrical; + if (value == "planet") + return ScreenshotType::Planet; + if (value == "cubemap") + return ScreenshotType::Cubemap; + + Log(Debug::Warning) << "Invalid screenshot type: " << value << ", fallback to regular"; + return ScreenshotType::Regular; + } } CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); @@ -501,6 +518,16 @@ namespace Settings setInt(setting, category, toInt(value)); } + void Manager::set(std::string_view setting, std::string_view category, WindowMode value) + { + setInt(setting, category, static_cast(value)); + } + + void Manager::set(std::string_view setting, std::string_view category, SDLUtil::VSyncMode value) + { + setInt(setting, category, static_cast(value)); + } + void Manager::recordInit(std::string_view setting, std::string_view category) { sInitialized.emplace(category, setting); @@ -547,4 +574,28 @@ namespace Settings Log(Debug::Warning) << "Unknown lighting method '" << value << "', returning fallback '" << fallback << "'"; return SceneUtil::LightingMethod::PerObjectUniform; } + + ScreenshotSettings parseScreenshotSettings(std::string_view value) + { + std::vector settingArgs; + Misc::StringUtils::split(value, settingArgs); + + ScreenshotSettings result; + + if (settingArgs.size() > 0) + result.mType = parseScreenshotType(settingArgs[0]); + else + result.mType = ScreenshotType::Regular; + + if (settingArgs.size() > 1) + result.mWidth = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[1], 0)); + + if (settingArgs.size() > 2) + result.mHeight = std::min(10000, Misc::StringUtils::toNumeric(settingArgs[2], 0)); + + if (settingArgs.size() > 3) + result.mCubeSize = std::min(5000, Misc::StringUtils::toNumeric(settingArgs[3], 0)); + + return result; + } } diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index a5b75fd445..c061755bc1 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -5,9 +5,12 @@ #include "gyroscopeaxis.hpp" #include "hrtfmode.hpp" #include "navmeshrendermode.hpp" +#include "screenshotsettings.hpp" +#include "windowmode.hpp" #include #include +#include #include #include @@ -27,13 +30,6 @@ namespace Files namespace Settings { - enum class WindowMode - { - Fullscreen = 0, - WindowedFullscreen, - Windowed - }; - /// /// \brief Settings management (can change during runtime) /// @@ -114,6 +110,8 @@ namespace Settings static void set(std::string_view setting, std::string_view category, const MyGUI::Colour& value); static void set(std::string_view setting, std::string_view category, SceneUtil::LightingMethod value); static void set(std::string_view setting, std::string_view category, HrtfMode value); + static void set(std::string_view setting, std::string_view category, WindowMode value); + static void set(std::string_view setting, std::string_view category, SDLUtil::VSyncMode value); private: static std::set> sInitialized; @@ -239,6 +237,32 @@ namespace Settings return HrtfMode::Enable; return HrtfMode::Disable; } + + template <> + inline WindowMode Manager::getImpl(std::string_view setting, std::string_view category) + { + const int value = getInt(setting, category); + if (value < 0 || 2 < value) + return WindowMode::Fullscreen; + return static_cast(value); + } + + template <> + inline SDLUtil::VSyncMode Manager::getImpl(std::string_view setting, std::string_view category) + { + const int value = getInt(setting, category); + if (value < 0 || 2 < value) + return SDLUtil::VSyncMode::Disabled; + return static_cast(value); + } + + ScreenshotSettings parseScreenshotSettings(std::string_view value); + + template <> + inline ScreenshotSettings Manager::getImpl(std::string_view setting, std::string_view category) + { + return parseScreenshotSettings(getString(setting, category)); + } } #endif // COMPONENTS_SETTINGS_H diff --git a/components/settings/settingvalue.hpp b/components/settings/settingvalue.hpp index 7b49c9bda2..2f239a4ebd 100644 --- a/components/settings/settingvalue.hpp +++ b/components/settings/settingvalue.hpp @@ -42,6 +42,9 @@ namespace Settings NavMeshRenderMode, LightingMethod, HrtfMode, + WindowMode, + VSyncMode, + ScreenshotSettings, }; template @@ -161,6 +164,24 @@ namespace Settings return SettingValueType::HrtfMode; } + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::WindowMode; + } + + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::VSyncMode; + } + + template <> + inline constexpr SettingValueType getSettingValueType() + { + return SettingValueType::ScreenshotSettings; + } + inline constexpr std::string_view getSettingValueTypeName(SettingValueType type) { switch (type) @@ -203,6 +224,12 @@ namespace Settings return "lighting method"; case SettingValueType::HrtfMode: return "hrtf mode"; + case SettingValueType::WindowMode: + return "window mode"; + case SettingValueType::VSyncMode: + return "vsync mode"; + case SettingValueType::ScreenshotSettings: + return "screenshot settings"; } return "unsupported"; } @@ -361,6 +388,17 @@ namespace Settings } return stream; } + else if constexpr (std::is_same_v) + { + stream << "ScreenshotSettings{ .mType = " << static_cast(value.mValue.mType); + if (value.mValue.mWidth.has_value()) + stream << ", .mWidth = " << *value.mValue.mWidth; + if (value.mValue.mHeight.has_value()) + stream << ", .mHeight = " << *value.mValue.mHeight; + if (value.mValue.mCubeSize.has_value()) + stream << ", .mCubeSize = " << *value.mValue.mCubeSize; + return stream << " }"; + } else return stream << value.mValue; } diff --git a/components/settings/windowmode.hpp b/components/settings/windowmode.hpp new file mode 100644 index 0000000000..96a81059f4 --- /dev/null +++ b/components/settings/windowmode.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_SETTINGS_WINDOWMODE_H +#define OPENMW_COMPONENTS_SETTINGS_WINDOWMODE_H + +namespace Settings +{ + enum class WindowMode + { + Fullscreen = 0, + WindowedFullscreen = 1, + Windowed = 2, + }; +} + +#endif diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index bf82d52078..29660e3580 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -273,7 +273,7 @@ namespace Stereo void Manager::updateStereoFramebuffer() { // VR-TODO: in VR, still need to have this framebuffer attached before the postprocessor is created - // auto samples = Settings::Manager::getInt("antialiasing", "Video"); + // auto samples = /*do not use Settings here*/; // auto eyeRes = eyeResolution(); // if (mMultiviewFramebuffer) diff --git a/docs/source/reference/modding/settings/video.rst b/docs/source/reference/modding/settings/video.rst index 4cb5ba1842..801cf63d5b 100644 --- a/docs/source/reference/modding/settings/video.rst +++ b/docs/source/reference/modding/settings/video.rst @@ -194,3 +194,12 @@ Gamma is an exponent that makes colors brighter if greater than 1.0 and darker i This setting can be changed in the Detail tab of the Video panel of the Options menu. It has been reported to not work on some Linux systems, and therefore the in-game setting in the Options menu has been disabled on Linux systems. + +screenshot type +--------------- + +:Type: screenshot settings +:Default: regular + +Type of screenshot to take (regular, cylindrical, spherical, planet or cubemap), optionally followed by +screenshot width, height and cubemap resolution in pixels (e.g. spherical 1600 1000 1200). diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ece773a677..bbc6b4d1c8 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -640,7 +640,7 @@ contrast = 1.0 # Video gamma setting. (>0.0). No effect in Linux. gamma = 1.0 -# Type of screenshot to take (regular, cylindrical, spherical or planet), optionally followed by +# Type of screenshot to take (regular, cylindrical, spherical, planet or cubemap), optionally followed by # screenshot width, height and cubemap resolution in pixels. (e.g. spherical 1600 1000 1200) screenshot type = regular From 3f4591eb3b3e4e6ec0e27cf51f8df07c6ea19607 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 27 Oct 2023 21:22:43 +0300 Subject: [PATCH 11/36] Add movable static and ESM4 land texture stores --- apps/openmw/mwclass/classes.cpp | 2 ++ apps/openmw/mwlua/cellbindings.cpp | 4 ++++ apps/openmw/mwlua/types/types.cpp | 3 +++ apps/openmw/mwworld/cellstore.cpp | 1 + apps/openmw/mwworld/cellstore.hpp | 6 ++++-- apps/openmw/mwworld/esmstore.cpp | 1 + apps/openmw/mwworld/esmstore.hpp | 10 ++++++---- apps/openmw/mwworld/store.cpp | 2 ++ components/esm/records.hpp | 2 ++ files/lua_api/openmw/types.lua | 3 +++ 10 files changed, 28 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index 1668799eba..5017b1b272 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -85,6 +86,7 @@ namespace MWClass ESM4Named::registerSelf(); ESM4Light::registerSelf(); ESM4Named::registerSelf(); + ESM4Named::registerSelf(); ESM4Npc::registerSelf(); ESM4Named::registerSelf(); ESM4Static::registerSelf(); diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 46a72c0848..7c3b31c7b6 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -240,6 +241,9 @@ namespace MWLua case ESM::REC_MISC4: cell.mStore->template forEachType(visitor); break; + case ESM::REC_MSTT4: + cell.mStore->template forEachType(visitor); + break; case ESM::REC_ALCH4: cell.mStore->template forEachType(visitor); break; diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index eeb7061cc3..bd8b592f7a 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -47,6 +47,7 @@ namespace MWLua constexpr std::string_view ESM4Ingredient = "ESM4Ingredient"; constexpr std::string_view ESM4Light = "ESM4Light"; constexpr std::string_view ESM4MiscItem = "ESM4Miscellaneous"; + constexpr std::string_view ESM4MovableStatic = "ESM4MovableStatic"; constexpr std::string_view ESM4Potion = "ESM4Potion"; constexpr std::string_view ESM4Static = "ESM4Static"; constexpr std::string_view ESM4Terminal = "ESM4Terminal"; @@ -91,6 +92,7 @@ namespace MWLua { ESM::REC_INGR4, ObjectTypeName::ESM4Ingredient }, { ESM::REC_LIGH4, ObjectTypeName::ESM4Light }, { ESM::REC_MISC4, ObjectTypeName::ESM4MiscItem }, + { ESM::REC_MSTT4, ObjectTypeName::ESM4MovableStatic }, { ESM::REC_ALCH4, ObjectTypeName::ESM4Potion }, { ESM::REC_STAT4, ObjectTypeName::ESM4Static }, { ESM::REC_TERM4, ObjectTypeName::ESM4Terminal }, @@ -230,6 +232,7 @@ namespace MWLua addType(ObjectTypeName::ESM4Ingredient, { ESM::REC_INGR4 }); addType(ObjectTypeName::ESM4Light, { ESM::REC_LIGH4 }); addType(ObjectTypeName::ESM4MiscItem, { ESM::REC_MISC4 }); + addType(ObjectTypeName::ESM4MovableStatic, { ESM::REC_MSTT4 }); addType(ObjectTypeName::ESM4Potion, { ESM::REC_ALCH4 }); addType(ObjectTypeName::ESM4Static, { ESM::REC_STAT4 }); addESM4TerminalBindings(addType(ObjectTypeName::ESM4Terminal, { ESM::REC_TERM4 }), context); diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index bad2181a06..ff79940540 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 13409ab169..debd80a97b 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -73,6 +73,7 @@ namespace ESM4 struct Flora; struct Ingredient; struct MiscItem; + struct MovableStatic; struct Terminal; struct Tree; struct Weapon; @@ -95,8 +96,9 @@ namespace MWWorld CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList, CellRefList>; + CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList>; /// \brief Mutable state of a cell class CellStore diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index ad3d1f8d43..12fbc0d4df 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -301,6 +301,7 @@ namespace MWWorld case ESM::REC_LVLC4: case ESM::REC_LVLN4: case ESM::REC_MISC4: + case ESM::REC_MSTT4: case ESM::REC_NPC_4: case ESM::REC_STAT4: case ESM::REC_TERM4: diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 821ca6f488..ceba4a26ef 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -92,11 +92,13 @@ namespace ESM4 struct HeadPart; struct Ingredient; struct Land; + struct LandTexture; struct LevelledCreature; struct LevelledItem; struct LevelledNpc; struct Light; struct MiscItem; + struct MovableStatic; struct Npc; struct Outfit; struct Potion; @@ -139,10 +141,10 @@ namespace MWWorld Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, - Store, Store, Store, Store, - Store, Store, Store, Store, Store, - Store, Store, Store, Store, Store, - Store>; + Store, Store, Store, + Store, Store, Store, Store, + Store, Store, Store, Store, Store, + Store, Store, Store, Store, Store>; private: template diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 22f6857a24..e42675c66e 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1363,11 +1363,13 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; diff --git a/components/esm/records.hpp b/components/esm/records.hpp index ccd03a05b9..4473e0290f 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -62,10 +62,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 3df689ce0e..69ce5fbaf2 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -1881,6 +1881,9 @@ --- Functions for @{#ESM4Miscellaneous} objects -- @field [parent=#types] #ESM4Miscellaneous ESM4Miscellaneous +--- Functions for @{#ESM4MovableStatic} objects +-- @field [parent=#types] #ESM4MovableStatic ESM4MovableStatic + --- Functions for @{#ESM4Potion} objects -- @field [parent=#types] #ESM4Potion ESM4Potion From 84cad3de01340c97d9abf2f52c4a3b6f752addcc Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 27 Oct 2023 22:03:08 +0300 Subject: [PATCH 12/36] Don't show tooltips for unnamed ESM4Named --- apps/openmw/mwclass/esm4base.hpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index 5c05d81692..9c02af963d 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -138,17 +138,17 @@ namespace MWClass { } - bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return true; } - - MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override - { - return ESM4Impl::getToolTipInfo(ptr.get()->mBase->mFullName, count); - } - std::string_view getName(const MWWorld::ConstPtr& ptr) const override { return ptr.get()->mBase->mFullName; } + + MWGui::ToolTipInfo getToolTipInfo(const MWWorld::ConstPtr& ptr, int count) const override + { + return ESM4Impl::getToolTipInfo(getName(ptr), count); + } + + bool hasToolTip(const MWWorld::ConstPtr& ptr) const override { return !getName(ptr).empty(); } }; } From 7e5a1cec04dc86bc6e05f4391980b0fb1740494c Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 27 Oct 2023 22:29:34 +0200 Subject: [PATCH 13/36] Use settings values for Terrain settings --- apps/openmw/mwgui/mapwindow.cpp | 2 +- apps/openmw/mwgui/settingswindow.cpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 12 ++++++------ apps/openmw/mwrender/renderingmanager.cpp | 20 +++++++++----------- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 088024ac63..6d79b5f9d7 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -89,7 +89,7 @@ namespace { if (!Settings::map().mAllowZooming) return Constants::CellGridRadius; - if (!Settings::Manager::getBool("distant terrain", "Terrain")) + if (!Settings::terrain().mDistantTerrain) return Constants::CellGridRadius; const int viewingDistanceInCells = Settings::camera().mViewingDistance / Constants::CellSizeInUnits; return std::clamp( diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 1a41f9bb55..71bf276a34 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -258,7 +258,7 @@ namespace MWGui , mKeyboardMode(true) , mCurrentPage(-1) { - bool terrain = Settings::Manager::getBool("distant terrain", "Terrain"); + const bool terrain = Settings::terrain().mDistantTerrain; const std::string_view widgetName = terrain ? "RenderingDistanceSlider" : "LargeRenderingDistanceSlider"; MyGUI::Widget* unusedSlider; getWidget(unusedSlider, widgetName); diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 385ce46fff..99ebb94647 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -457,14 +457,14 @@ namespace MWRender : GenericResourceManager(nullptr, Settings::cells().mCacheExpiryDelay) , Terrain::QuadTreeWorld::ChunkManager(worldspace) , mSceneManager(sceneManager) + , mActiveGrid(Settings::terrain().mObjectPagingActiveGrid) + , mDebugBatches(Settings::terrain().mDebugChunks) + , mMergeFactor(Settings::terrain().mObjectPagingMergeFactor) + , mMinSize(Settings::terrain().mObjectPagingMinSize) + , mMinSizeMergeFactor(Settings::terrain().mObjectPagingMinSizeMergeFactor) + , mMinSizeCostMultiplier(Settings::terrain().mObjectPagingMinSizeCostMultiplier) , mRefTrackerLocked(false) { - mActiveGrid = Settings::Manager::getBool("object paging active grid", "Terrain"); - mDebugBatches = Settings::Manager::getBool("debug chunks", "Terrain"); - mMergeFactor = Settings::Manager::getFloat("object paging merge factor", "Terrain"); - mMinSize = Settings::Manager::getFloat("object paging min size", "Terrain"); - mMinSizeMergeFactor = Settings::Manager::getFloat("object paging min size merge factor", "Terrain"); - mMinSizeCostMultiplier = Settings::Manager::getFloat("object paging min size cost multiplier", "Terrain"); } std::map ObjectPaging::collectESM3References( diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b7685e0cd8..23508e70bd 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1316,23 +1316,21 @@ namespace MWRender return existingChunkMgr->second; RenderingManager::WorldspaceChunkMgr newChunkMgr; - const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain"); + const float lodFactor = Settings::terrain().mLodFactor; const bool groundcover = Settings::groundcover().mEnabled; - bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); + const bool distantTerrain = Settings::terrain().mDistantTerrain; if (distantTerrain || groundcover) { - const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain"); - int compMapPower = Settings::Manager::getInt("composite map level", "Terrain"); - compMapPower = std::max(-3, compMapPower); - float compMapLevel = pow(2, compMapPower); - const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain"); - float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain"); - maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f); - bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain"); + const int compMapResolution = Settings::terrain().mCompositeMapResolution; + const int compMapPower = Settings::terrain().mCompositeMapLevel; + const float compMapLevel = std::pow(2, compMapPower); + const int vertexLodMod = Settings::terrain().mVertexLodMod; + const float maxCompGeometrySize = Settings::terrain().mMaxCompositeGeometrySize; + const bool debugChunks = Settings::terrain().mDebugChunks; auto quadTreeWorld = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace); - if (Settings::Manager::getBool("object paging", "Terrain")) + if (Settings::terrain().mObjectPaging) { newChunkMgr.mObjectPaging = std::make_unique(mResourceSystem->getSceneManager(), worldspace); From 561a6bf854d9b5b9486eab1bb471fbf928f220a1 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 27 Oct 2023 23:26:50 +0200 Subject: [PATCH 14/36] Avoid using camera settings from stereo manager --- apps/openmw/engine.cpp | 5 ++++- components/stereo/stereomanager.cpp | 17 ++++++++--------- components/stereo/stereomanager.hpp | 12 +++++++++++- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 7393562cfb..06f180310e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -313,6 +313,8 @@ bool OMW::Engine::frame(float frametime) mLuaManager->reportStats(frameNumber, *stats); } + mStereoManager->updateSettings(Settings::camera().mNearClip, Settings::camera().mViewingDistance); + mViewer->eventTraversal(); mViewer->updateTraversal(); @@ -634,7 +636,8 @@ void OMW::Engine::prepareEngine() bool stereoEnabled = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); - mStereoManager = std::make_unique(mViewer, stereoEnabled); + mStereoManager = std::make_unique( + mViewer, stereoEnabled, Settings::camera().mNearClip, Settings::camera().mViewingDistance); osg::ref_ptr rootNode(new osg::Group); mViewer->setSceneData(rootNode); diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index bf82d52078..b7023095d2 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -114,13 +114,15 @@ namespace Stereo return *sInstance; } - Manager::Manager(osgViewer::Viewer* viewer, bool enableStereo) + Manager::Manager(osgViewer::Viewer* viewer, bool enableStereo, double near, double far) : mViewer(viewer) , mMainCamera(mViewer->getCamera()) , mUpdateCallback(new StereoUpdateCallback(this)) , mMasterProjectionMatrix(osg::Matrixd::identity()) , mEyeResolutionOverriden(false) , mEyeResolutionOverride(0, 0) + , mNear(near) + , mFar(far) , mFrustumManager(nullptr) , mUpdateViewCallback(nullptr) { @@ -289,20 +291,17 @@ namespace Stereo void Manager::update() { - const double near_ = Settings::camera().mNearClip; - const double far_ = Settings::camera().mViewingDistance; - if (mUpdateViewCallback) { mUpdateViewCallback->updateView(mView[0], mView[1]); mViewOffsetMatrix[0] = mView[0].viewMatrix(true); mViewOffsetMatrix[1] = mView[1].viewMatrix(true); - mProjectionMatrix[0] = mView[0].perspectiveMatrix(near_, far_, false); - mProjectionMatrix[1] = mView[1].perspectiveMatrix(near_, far_, false); + mProjectionMatrix[0] = mView[0].perspectiveMatrix(mNear, mFar, false); + mProjectionMatrix[1] = mView[1].perspectiveMatrix(mNear, mFar, false); if (SceneUtil::AutoDepth::isReversed()) { - mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(near_, far_, true); - mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(near_, far_, true); + mProjectionMatrixReverseZ[0] = mView[0].perspectiveMatrix(mNear, mFar, true); + mProjectionMatrixReverseZ[1] = mView[1].perspectiveMatrix(mNear, mFar, true); } View masterView; @@ -310,7 +309,7 @@ namespace Stereo masterView.fov.angleUp = std::max(mView[0].fov.angleUp, mView[1].fov.angleUp); masterView.fov.angleLeft = std::min(mView[0].fov.angleLeft, mView[1].fov.angleLeft); masterView.fov.angleRight = std::max(mView[0].fov.angleRight, mView[1].fov.angleRight); - auto projectionMatrix = masterView.perspectiveMatrix(near_, far_, false); + auto projectionMatrix = masterView.perspectiveMatrix(mNear, mFar, false); mMainCamera->setProjectionMatrix(projectionMatrix); } else diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp index 6f4e971718..40bb9fd10c 100644 --- a/components/stereo/stereomanager.hpp +++ b/components/stereo/stereomanager.hpp @@ -76,12 +76,20 @@ namespace Stereo //! @Param viewer the osg viewer whose stereo should be managed. //! @Param enableStereo whether or not stereo should be enabled. //! @Param enableMultiview whether or not to make use of the GL_OVR_Multiview extension, if supported. - Manager(osgViewer::Viewer* viewer, bool enableStereo); + //! @Param near defines distance to near camera clipping plane from view point. + //! @Param far defines distance to far camera clipping plane from view point. + explicit Manager(osgViewer::Viewer* viewer, bool enableStereo, double near, double far); ~Manager(); //! Called during update traversal void update(); + void updateSettings(double near, double far) + { + mNear = near; + mFar = far; + } + //! Initializes all details of stereo if applicable. If the constructor was called with enableMultiview=true, //! and the GL_OVR_Multiview extension is supported, Stereo::getMultiview() will return true after this call. void initializeStereo(osg::GraphicsContext* gc, bool enableMultiview); @@ -138,6 +146,8 @@ namespace Stereo std::shared_ptr mMultiviewFramebuffer; bool mEyeResolutionOverriden; osg::Vec2i mEyeResolutionOverride; + double mNear; + double mFar; std::array mView; std::array mViewOffsetMatrix; From bb7ac64f194b0807f475330ac24a0357375605b8 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 27 Oct 2023 22:42:43 +0200 Subject: [PATCH 15/36] Use settings values for Stereo and Stereo View settings --- apps/openmw/engine.cpp | 61 ++++++++++++++++++- components/settings/categories/stereoview.hpp | 32 +++++----- components/stereo/frustum.cpp | 4 +- components/stereo/frustum.hpp | 2 +- components/stereo/multiview.cpp | 4 +- components/stereo/multiview.hpp | 2 +- components/stereo/stereomanager.cpp | 58 +++++------------- components/stereo/stereomanager.hpp | 25 +++++++- 8 files changed, 117 insertions(+), 71 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 06f180310e..990db6ac38 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -595,7 +595,63 @@ void OMW::Engine::createWindow() realizeOperations->add(mSelectColorFormatOperation); if (Stereo::getStereo()) - realizeOperations->add(new Stereo::InitializeStereoOperation()); + { + Stereo::Settings settings; + + settings.mMultiview = Settings::stereo().mMultiview; + settings.mAllowDisplayListsForMultiview = Settings::stereo().mAllowDisplayListsForMultiview; + settings.mSharedShadowMaps = Settings::stereo().mSharedShadowMaps; + + if (Settings::stereo().mUseCustomView) + { + const osg::Vec3 leftEyeOffset(Settings::stereoView().mLeftEyeOffsetX, + Settings::stereoView().mLeftEyeOffsetY, Settings::stereoView().mLeftEyeOffsetZ); + + const osg::Quat leftEyeOrientation(Settings::stereoView().mLeftEyeOrientationX, + Settings::stereoView().mLeftEyeOrientationY, Settings::stereoView().mLeftEyeOrientationZ, + Settings::stereoView().mLeftEyeOrientationW); + + const osg::Vec3 rightEyeOffset(Settings::stereoView().mRightEyeOffsetX, + Settings::stereoView().mRightEyeOffsetY, Settings::stereoView().mRightEyeOffsetZ); + + const osg::Quat rightEyeOrientation(Settings::stereoView().mRightEyeOrientationX, + Settings::stereoView().mRightEyeOrientationY, Settings::stereoView().mRightEyeOrientationZ, + Settings::stereoView().mRightEyeOrientationW); + + settings.mCustomView = Stereo::CustomView{ + .mLeft = Stereo::View{ + .pose = Stereo::Pose{ + .position = leftEyeOffset, + .orientation = leftEyeOrientation, + }, + .fov = Stereo::FieldOfView{ + .angleLeft = Settings::stereoView().mLeftEyeFovLeft, + .angleRight = Settings::stereoView().mLeftEyeFovRight, + .angleUp = Settings::stereoView().mLeftEyeFovUp, + .angleDown = Settings::stereoView().mLeftEyeFovDown, + }, + }, + .mRight = Stereo::View{ + .pose = Stereo::Pose{ + .position = rightEyeOffset, + .orientation = rightEyeOrientation, + }, + .fov = Stereo::FieldOfView{ + .angleLeft = Settings::stereoView().mRightEyeFovLeft, + .angleRight = Settings::stereoView().mRightEyeFovRight, + .angleUp = Settings::stereoView().mRightEyeFovUp, + .angleDown = Settings::stereoView().mRightEyeFovDown, + }, + }, + }; + } + + if (Settings::stereo().mUseCustomEyeResolution) + settings.mEyeResolution + = osg::Vec2i(Settings::stereoView().mEyeResolutionX, Settings::stereoView().mEyeResolutionY); + + realizeOperations->add(new Stereo::InitializeStereoOperation(settings)); + } mViewer->realize(); mGlMaxTextureImageUnits = identifyOp->getMaxTextureImageUnits(); @@ -634,8 +690,7 @@ void OMW::Engine::prepareEngine() mStateManager = std::make_unique(mCfgMgr.getUserDataPath() / "saves", mContentFiles); mEnvironment.setStateManager(*mStateManager); - bool stereoEnabled - = Settings::Manager::getBool("stereo enabled", "Stereo") || osg::DisplaySettings::instance().get()->getStereo(); + const bool stereoEnabled = Settings::stereo().mStereoEnabled || osg::DisplaySettings::instance().get()->getStereo(); mStereoManager = std::make_unique( mViewer, stereoEnabled, Settings::camera().mNearClip, Settings::camera().mViewingDistance); diff --git a/components/settings/categories/stereoview.hpp b/components/settings/categories/stereoview.hpp index 1e9d35ace8..bcd0f57abc 100644 --- a/components/settings/categories/stereoview.hpp +++ b/components/settings/categories/stereoview.hpp @@ -31,14 +31,14 @@ namespace Settings makeClampSanitizerDouble(-1, 1) }; SettingValue mLeftEyeOrientationW{ mIndex, "Stereo View", "left eye orientation w", makeClampSanitizerDouble(-1, 1) }; - SettingValue mLeftEyeFovLeft{ mIndex, "Stereo View", "left eye fov left", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mLeftEyeFovRight{ mIndex, "Stereo View", "left eye fov right", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mLeftEyeFovUp{ mIndex, "Stereo View", "left eye fov up", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mLeftEyeFovDown{ mIndex, "Stereo View", "left eye fov down", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; + SettingValue mLeftEyeFovLeft{ mIndex, "Stereo View", "left eye fov left", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mLeftEyeFovRight{ mIndex, "Stereo View", "left eye fov right", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mLeftEyeFovUp{ mIndex, "Stereo View", "left eye fov up", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mLeftEyeFovDown{ mIndex, "Stereo View", "left eye fov down", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; SettingValue mRightEyeOffsetX{ mIndex, "Stereo View", "right eye offset x" }; SettingValue mRightEyeOffsetY{ mIndex, "Stereo View", "right eye offset y" }; SettingValue mRightEyeOffsetZ{ mIndex, "Stereo View", "right eye offset z" }; @@ -50,14 +50,14 @@ namespace Settings makeClampSanitizerDouble(-1, 1) }; SettingValue mRightEyeOrientationW{ mIndex, "Stereo View", "right eye orientation w", makeClampSanitizerDouble(-1, 1) }; - SettingValue mRightEyeFovLeft{ mIndex, "Stereo View", "right eye fov left", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mRightEyeFovRight{ mIndex, "Stereo View", "right eye fov right", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mRightEyeFovUp{ mIndex, "Stereo View", "right eye fov up", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; - SettingValue mRightEyeFovDown{ mIndex, "Stereo View", "right eye fov down", - makeClampSanitizerDouble(-osg::PI, osg::PI) }; + SettingValue mRightEyeFovLeft{ mIndex, "Stereo View", "right eye fov left", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mRightEyeFovRight{ mIndex, "Stereo View", "right eye fov right", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mRightEyeFovUp{ mIndex, "Stereo View", "right eye fov up", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; + SettingValue mRightEyeFovDown{ mIndex, "Stereo View", "right eye fov down", + makeClampSanitizerFloat(-osg::PIf, osg::PIf) }; }; } diff --git a/components/stereo/frustum.cpp b/components/stereo/frustum.cpp index cf8a7e8c30..a35beba7dd 100644 --- a/components/stereo/frustum.cpp +++ b/components/stereo/frustum.cpp @@ -85,7 +85,7 @@ namespace Stereo } } - StereoFrustumManager::StereoFrustumManager(osg::Camera* camera) + StereoFrustumManager::StereoFrustumManager(bool sharedShadowMaps, osg::Camera* camera) : mCamera(camera) , mShadowTechnique(nullptr) , mShadowFrustumCallback(nullptr) @@ -95,7 +95,7 @@ namespace Stereo mMultiviewFrustumCallback = std::make_unique(this, camera); } - if (Settings::Manager::getBool("shared shadow maps", "Stereo")) + if (sharedShadowMaps) { mShadowFrustumCallback = new ShadowFrustumCallback(this); auto* renderer = static_cast(mCamera->getRenderer()); diff --git a/components/stereo/frustum.hpp b/components/stereo/frustum.hpp index c3abdd87d2..35e3adf95a 100644 --- a/components/stereo/frustum.hpp +++ b/components/stereo/frustum.hpp @@ -45,7 +45,7 @@ namespace Stereo class StereoFrustumManager { public: - StereoFrustumManager(osg::Camera* camera); + StereoFrustumManager(bool sharedShadowMaps, osg::Camera* camera); ~StereoFrustumManager(); void update(std::array projections); diff --git a/components/stereo/multiview.cpp b/components/stereo/multiview.cpp index 047a52747b..a2f6cfd626 100644 --- a/components/stereo/multiview.cpp +++ b/components/stereo/multiview.cpp @@ -124,12 +124,12 @@ namespace Stereo } } - void setVertexBufferHint(bool enableMultiview) + void setVertexBufferHint(bool enableMultiview, bool allowDisplayListsForMultiview) { if (getStereo() && enableMultiview) { auto* ds = osg::DisplaySettings::instance().get(); - if (!Settings::Manager::getBool("allow display lists for multiview", "Stereo") + if (!allowDisplayListsForMultiview && ds->getVertexBufferHint() == osg::DisplaySettings::VertexBufferHint::NO_PREFERENCE) { // Note that this only works if this code is executed before realize() is called on the viewer. diff --git a/components/stereo/multiview.hpp b/components/stereo/multiview.hpp index fa69afc7a1..a9d84eae85 100644 --- a/components/stereo/multiview.hpp +++ b/components/stereo/multiview.hpp @@ -37,7 +37,7 @@ namespace Stereo void configureExtensions(unsigned int contextID, bool enableMultiview); //! Sets the appropriate vertex buffer hint on OSG's display settings if needed - void setVertexBufferHint(bool enableMultiview); + void setVertexBufferHint(bool enableMultiview, bool allowDisplayListsForMultiview); //! Creates a Texture2D as a texture view into a Texture2DArray osg::ref_ptr createTextureView_Texture2DFromTexture2DArray( diff --git a/components/stereo/stereomanager.cpp b/components/stereo/stereomanager.cpp index b7023095d2..29de9d0bb0 100644 --- a/components/stereo/stereomanager.cpp +++ b/components/stereo/stereomanager.cpp @@ -134,13 +134,13 @@ namespace Stereo Manager::~Manager() {} - void Manager::initializeStereo(osg::GraphicsContext* gc, bool enableMultiview) + void Manager::initializeStereo(osg::GraphicsContext* gc, bool enableMultiview, bool sharedShadowMaps) { auto ci = gc->getState()->getContextID(); configureExtensions(ci, enableMultiview); mMainCamera->addUpdateCallback(mUpdateCallback); - mFrustumManager = std::make_unique(mViewer->getCamera()); + mFrustumManager = std::make_unique(sharedShadowMaps, mViewer->getCamera()); if (getMultiview()) setupOVRMultiView2Technique(); @@ -393,60 +393,30 @@ namespace Stereo right = mRight; } - InitializeStereoOperation::InitializeStereoOperation() + InitializeStereoOperation::InitializeStereoOperation(const Settings& settings) : GraphicsOperation("InitializeStereoOperation", false) + , mMultiview(settings.mMultiview) + , mSharedShadowMaps(settings.mSharedShadowMaps) + , mCustomView(settings.mCustomView) + , mEyeResolution(settings.mEyeResolution) { // Ideally, this would have belonged to the operator(). But the vertex buffer // hint has to be set before realize is called on the osg viewer, and so has to // be done here instead. - Stereo::setVertexBufferHint(Settings::Manager::getBool("multiview", "Stereo")); + Stereo::setVertexBufferHint(settings.mMultiview, settings.mAllowDisplayListsForMultiview); } void InitializeStereoOperation::operator()(osg::GraphicsContext* graphicsContext) { auto& sm = Stereo::Manager::instance(); - if (Settings::Manager::getBool("use custom view", "Stereo")) - { - Stereo::View left; - Stereo::View right; + if (mCustomView.has_value()) + sm.setUpdateViewCallback( + std::make_shared(mCustomView->mLeft, mCustomView->mRight)); - left.pose.position.x() = Settings::Manager::getDouble("left eye offset x", "Stereo View"); - left.pose.position.y() = Settings::Manager::getDouble("left eye offset y", "Stereo View"); - left.pose.position.z() = Settings::Manager::getDouble("left eye offset z", "Stereo View"); - left.pose.orientation.x() = Settings::Manager::getDouble("left eye orientation x", "Stereo View"); - left.pose.orientation.y() = Settings::Manager::getDouble("left eye orientation y", "Stereo View"); - left.pose.orientation.z() = Settings::Manager::getDouble("left eye orientation z", "Stereo View"); - left.pose.orientation.w() = Settings::Manager::getDouble("left eye orientation w", "Stereo View"); - left.fov.angleLeft = Settings::Manager::getDouble("left eye fov left", "Stereo View"); - left.fov.angleRight = Settings::Manager::getDouble("left eye fov right", "Stereo View"); - left.fov.angleUp = Settings::Manager::getDouble("left eye fov up", "Stereo View"); - left.fov.angleDown = Settings::Manager::getDouble("left eye fov down", "Stereo View"); + if (mEyeResolution.has_value()) + sm.overrideEyeResolution(*mEyeResolution); - right.pose.position.x() = Settings::Manager::getDouble("right eye offset x", "Stereo View"); - right.pose.position.y() = Settings::Manager::getDouble("right eye offset y", "Stereo View"); - right.pose.position.z() = Settings::Manager::getDouble("right eye offset z", "Stereo View"); - right.pose.orientation.x() = Settings::Manager::getDouble("right eye orientation x", "Stereo View"); - right.pose.orientation.y() = Settings::Manager::getDouble("right eye orientation y", "Stereo View"); - right.pose.orientation.z() = Settings::Manager::getDouble("right eye orientation z", "Stereo View"); - right.pose.orientation.w() = Settings::Manager::getDouble("right eye orientation w", "Stereo View"); - right.fov.angleLeft = Settings::Manager::getDouble("right eye fov left", "Stereo View"); - right.fov.angleRight = Settings::Manager::getDouble("right eye fov right", "Stereo View"); - right.fov.angleUp = Settings::Manager::getDouble("right eye fov up", "Stereo View"); - right.fov.angleDown = Settings::Manager::getDouble("right eye fov down", "Stereo View"); - - auto customViewCallback = std::make_shared(left, right); - sm.setUpdateViewCallback(customViewCallback); - } - - if (Settings::Manager::getBool("use custom eye resolution", "Stereo")) - { - osg::Vec2i eyeResolution = osg::Vec2i(); - eyeResolution.x() = Settings::Manager::getInt("eye resolution x", "Stereo View"); - eyeResolution.y() = Settings::Manager::getInt("eye resolution y", "Stereo View"); - sm.overrideEyeResolution(eyeResolution); - } - - sm.initializeStereo(graphicsContext, Settings::Manager::getBool("multiview", "Stereo")); + sm.initializeStereo(graphicsContext, mMultiview, mSharedShadowMaps); } } diff --git a/components/stereo/stereomanager.hpp b/components/stereo/stereomanager.hpp index 40bb9fd10c..8ed88da550 100644 --- a/components/stereo/stereomanager.hpp +++ b/components/stereo/stereomanager.hpp @@ -92,7 +92,7 @@ namespace Stereo //! Initializes all details of stereo if applicable. If the constructor was called with enableMultiview=true, //! and the GL_OVR_Multiview extension is supported, Stereo::getMultiview() will return true after this call. - void initializeStereo(osg::GraphicsContext* gc, bool enableMultiview); + void initializeStereo(osg::GraphicsContext* gc, bool enableMultiview, bool sharedShadowMaps); //! Callback that updates stereo configuration during the update pass void setUpdateViewCallback(std::shared_ptr cb); @@ -163,13 +163,34 @@ namespace Stereo osg::ref_ptr mIdentifierRight = new Identifier(); }; + struct CustomView + { + Stereo::View mLeft; + Stereo::View mRight; + }; + + struct Settings + { + bool mMultiview; + bool mAllowDisplayListsForMultiview; + bool mSharedShadowMaps; + std::optional mCustomView; + std::optional mEyeResolution; + }; + //! Performs stereo-specific initialization operations. class InitializeStereoOperation final : public osg::GraphicsOperation { public: - InitializeStereoOperation(); + explicit InitializeStereoOperation(const Settings& settings); void operator()(osg::GraphicsContext* graphicsContext) override; + + private: + bool mMultiview; + bool mSharedShadowMaps; + std::optional mCustomView; + std::optional mEyeResolution; }; } From 6cd5734fb33abfc37746dc5de44184836265e632 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 21:29:37 +0200 Subject: [PATCH 16/36] Rate weapons as useless if the actor's skill is 0 --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/weaponpriority.cpp | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e97693047b..7e9d31a19e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ Bug #7611: Beast races' idle animations slide after turning or jumping in place Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing + Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat 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/mwmechanics/weaponpriority.cpp b/apps/openmw/mwmechanics/weaponpriority.cpp index e0584afcd4..dd83db286f 100644 --- a/apps/openmw/mwmechanics/weaponpriority.cpp +++ b/apps/openmw/mwmechanics/weaponpriority.cpp @@ -118,13 +118,17 @@ namespace MWMechanics } int value = 50.f; - if (actor.getClass().isNpc()) - { - ESM::RefId skill = item.getClass().getEquipmentSkill(item); - if (!skill.empty()) - value = actor.getClass().getSkill(actor, skill); - } - else + ESM::RefId skill = item.getClass().getEquipmentSkill(item); + if (!skill.empty()) + value = actor.getClass().getSkill(actor, skill); + // Prefer hand-to-hand if our skill is 0 (presumably due to magic) + if (value <= 0.f) + return 0.f; + // Note that a creature with a dagger and 0 Stealth will forgo the weapon despite using Combat for hit chance. + // The same creature will use a sword provided its Combat stat isn't 0. We're using the "skill" value here to + // decide whether to use the weapon at all, but adjusting the final rating based on actual hit chance - i.e. the + // Combat stat. + if (!actor.getClass().isNpc()) { MWWorld::LiveCellRef* ref = actor.get(); value = ref->mBase->mData.mCombat; From 09928ba2653a1e6a7726d1068c7362328cc90550 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Sat, 28 Oct 2023 10:23:48 -0700 Subject: [PATCH 17/36] use a dynamic falloff range for soft effect and use shader flags --- components/nifosg/nifloader.cpp | 3 ++- components/sceneutil/extradata.cpp | 11 ++++++++--- components/sceneutil/extradata.hpp | 2 +- .../reference/modding/custom-shader-effects.rst | 17 ++++++++++------- files/shaders/compatibility/bs/nolighting.frag | 4 +++- files/shaders/compatibility/objects.frag | 4 +++- files/shaders/lib/particle/soft.glsl | 6 +++--- 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index a5f1032c69..9945a8c40e 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2708,7 +2708,8 @@ namespace NifOsg stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON); } if (shaderprop->softEffect()) - SceneUtil::setupSoftEffect(*node, shaderprop->mFalloffDepth, true); + SceneUtil::setupSoftEffect( + *node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth); break; } default: diff --git a/components/sceneutil/extradata.cpp b/components/sceneutil/extradata.cpp index d8d3e871dc..720e032a61 100644 --- a/components/sceneutil/extradata.cpp +++ b/components/sceneutil/extradata.cpp @@ -15,7 +15,7 @@ namespace SceneUtil { - void setupSoftEffect(osg::Node& node, float size, bool falloff) + void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth) { static const osg::ref_ptr depth = new SceneUtil::AutoDepth(osg::Depth::LESS, 0, 1, false); @@ -23,6 +23,7 @@ namespace SceneUtil stateset->addUniform(new osg::Uniform("particleSize", size)); stateset->addUniform(new osg::Uniform("particleFade", falloff)); + stateset->addUniform(new osg::Uniform("softFalloffDepth", falloffDepth)); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); node.setUserValue(Misc::OsgUserValues::sXSoftEffect, true); @@ -35,6 +36,8 @@ namespace SceneUtil std::string source; + constexpr float defaultFalloffDepth = 300.f; // arbitrary value that simply looks good with common cases + if (node.getUserValue(Misc::OsgUserValues::sExtraData, source) && !source.empty()) { YAML::Node root = YAML::Load(source); @@ -47,8 +50,9 @@ namespace SceneUtil { auto size = it.second["size"].as(45.f); auto falloff = it.second["falloff"].as(false); + auto falloffDepth = it.second["falloffDepth"].as(defaultFalloffDepth); - setupSoftEffect(node, size, falloff); + setupSoftEffect(node, size, falloff, falloffDepth); } } @@ -56,7 +60,8 @@ namespace SceneUtil } else if (osgParticle::ParticleSystem* partsys = dynamic_cast(&node)) { - setupSoftEffect(node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false); + setupSoftEffect( + node, partsys->getDefaultParticleTemplate().getSizeRange().maximum, false, defaultFalloffDepth); } traverse(node); diff --git a/components/sceneutil/extradata.hpp b/components/sceneutil/extradata.hpp index ddcb9b6c5c..9b1563b78a 100644 --- a/components/sceneutil/extradata.hpp +++ b/components/sceneutil/extradata.hpp @@ -15,7 +15,7 @@ namespace osg namespace SceneUtil { - void setupSoftEffect(osg::Node& node, float size, bool falloff); + void setupSoftEffect(osg::Node& node, float size, bool falloff, float falloffDepth); 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 340ed5ffed..5ea711953d 100644 --- a/docs/source/reference/modding/custom-shader-effects.rst +++ b/docs/source/reference/modding/custom-shader-effects.rst @@ -31,13 +31,15 @@ This setting can either be activated in the OpenMW launcher or changed in `setti Variables. -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ -| Name | Description | Type | Default | -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ -| size | Scaling ratio. Larger values will make a softer fade effect. Larger geometry requires higher values. | integer | 45 | -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ -| falloff | Fades away geometry as camera gets closer. Geometry full fades when parallel to camera. | boolean | false | -+---------+--------------------------------------------------------------------------------------------------------+---------+---------+ ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| Name | Description | Type | Default | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| size | Scaling ratio. Larger values will make a softer fade effect. Larger geometry requires higher values. | integer | 45 | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| falloff | Fades away geometry as camera gets closer. Geometry full fades when parallel to camera. | boolean | false | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ +| falloffDepth | The units at which geometry starts to fade. | float | 300 | ++--------------+--------------------------------------------------------------------------------------------------------+---------+---------+ Example usage. @@ -48,6 +50,7 @@ Example usage. "soft_effect" : { "size": 250, "falloff" : false, + "falloffDepth": 5, } } } diff --git a/files/shaders/compatibility/bs/nolighting.frag b/files/shaders/compatibility/bs/nolighting.frag index c5393d4732..c9e3ca4e13 100644 --- a/files/shaders/compatibility/bs/nolighting.frag +++ b/files/shaders/compatibility/bs/nolighting.frag @@ -38,6 +38,7 @@ uniform float alphaRef; uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; +uniform float softFalloffDepth; #endif void main() @@ -71,7 +72,8 @@ void main() far, texture2D(opaqueDepthTex, screenCoords).x, particleSize, - particleFade + particleFade, + softFalloffDepth ); #endif diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 4ef463cb34..4caf6c97e2 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -98,6 +98,7 @@ varying vec3 passNormal; uniform sampler2D opaqueDepthTex; uniform float particleSize; uniform bool particleFade; +uniform float softFalloffDepth; #endif #if @particleOcclusion @@ -256,7 +257,8 @@ vec3 viewNormal = normalize(gl_NormalMatrix * normal); far, texture2D(opaqueDepthTex, screenCoords).x, particleSize, - particleFade + particleFade, + softFalloffDepth ); #endif diff --git a/files/shaders/lib/particle/soft.glsl b/files/shaders/lib/particle/soft.glsl index c415d8402b..99336bc039 100644 --- a/files/shaders/lib/particle/soft.glsl +++ b/files/shaders/lib/particle/soft.glsl @@ -19,7 +19,8 @@ float calcSoftParticleFade( float far, float depth, float size, - bool fade + bool fade, + float softFalloffDepth ) { float euclidianDepth = length(viewPos); @@ -32,13 +33,12 @@ float calcSoftParticleFade( float falloff = size * falloffMultiplier; float delta = particleDepth - sceneDepth; - const float nearMult = 300.0; float viewBias = 1.0; if (fade) { float VdotN = dot(viewDir, viewNormal); - viewBias = abs(VdotN) * quickstep(euclidianDepth / nearMult) * (1.0 - pow(1.0 + VdotN, 1.3)); + viewBias = abs(VdotN) * quickstep(euclidianDepth / softFalloffDepth) * (1.0 - pow(1.0 - abs(VdotN), 1.3)); } const float shift = 0.845; From 24bac3ebd4d2fc24874fd4b393f43f4d7e6d7fb7 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 27 Oct 2023 18:22:13 +0200 Subject: [PATCH 18/36] AiWander must update mIsWanderingManually when resuming wander. --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/aiwander.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e9d31a19e..fd1e0ce39e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat + Bug #7647: NPC walk cycle bugs after greeting player 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/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index 30756ade35..30aad2e89a 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -229,7 +229,7 @@ namespace MWMechanics } if (mPathFinder.isPathConstructed()) - storage.setState(AiWanderStorage::Wander_Walking); + storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid); } if (!cStats.getMovementFlag(CreatureStats::Flag_ForceJump) @@ -499,7 +499,7 @@ namespace MWMechanics if (!checkIdle(actor, storage.mIdleAnimation) && (greetingState == Greet_Done || greetingState == Greet_None)) { if (mPathFinder.isPathConstructed()) - storage.setState(AiWanderStorage::Wander_Walking); + storage.setState(AiWanderStorage::Wander_Walking, !mUsePathgrid); else storage.setState(AiWanderStorage::Wander_ChooseAction); } From ec81d99f210ce480dc09527025866f7f55e0047a Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 27 Oct 2023 18:31:02 +0200 Subject: [PATCH 19/36] Add me to authors --- AUTHORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.md b/AUTHORS.md index 99080fdebd..9791171b9c 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -140,6 +140,7 @@ Programmers Lordrea Łukasz Gołębiewski (lukago) Lukasz Gromanowski (lgro) + Mads Sandvei (Foal) Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) From cdb325f19ac55f66109769c6fa64aae8567f3b34 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 18:00:26 +0200 Subject: [PATCH 20/36] Rewrite CharacterController::updateAnimQueue() to properly maintain the animation queue and clean up finished animations. --- apps/openmw/mwmechanics/character.cpp | 30 +++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 48abac8b06..42611e1f9f 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1852,17 +1852,39 @@ namespace MWMechanics void CharacterController::updateAnimQueue() { - if (mAnimQueue.size() > 1) - { + if (mAnimQueue.empty()) + return; + if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { + if (mAnimQueue.size() > 1) { mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } + else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mPersist) + { + // The last animation stopped playing when it shouldn't have. + // This is caused by a rebuild of the animation object, so we should restart the animation here + // Subtract 1 from mLoopCount to consider the current loop done. + bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount - 1, loopfallback); + } + else + { + // Animation is done, remove it from the queue. + mAnimation->disable(mAnimQueue.front().mGroup); + mAnimQueue.pop_front(); + } + } + else + { + mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup); } if (!mAnimQueue.empty()) From 8792e76f5d06b6c19c498e58ee17a0a40228f581 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 18:35:00 +0200 Subject: [PATCH 21/36] explicitly prevent movement whenever playing scripted animations --- apps/openmw/mwmechanics/character.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 42611e1f9f..c83e72cef8 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2366,7 +2366,7 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled()) + if (!isMovementAnimationControlled() && mAnimQueue.empty()) world->queueMovement(mPtr, vec); } @@ -2435,7 +2435,7 @@ namespace MWMechanics } // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor()) + if (isMovementAnimationControlled() && mPtr.getClass().isActor() && mAnimQueue.empty()) world->queueMovement(mPtr, moved); mSkipAnim = false; From 24890a729bfa736d806154f40f62d859ba06e72f Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 18:59:05 +0200 Subject: [PATCH 22/36] clang'd --- apps/openmw/mwmechanics/character.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c83e72cef8..9c39b5b60d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1855,16 +1855,16 @@ namespace MWMechanics if (mAnimQueue.empty()) return; - if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) - { + if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + { if (mAnimQueue.size() > 1) { mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, + 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mPersist) { @@ -1881,7 +1881,7 @@ namespace MWMechanics mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); } - } + } else { mAnimQueue.front().mLoopCount = mAnimation->getCurrentLoopCount(mAnimQueue.front().mGroup); From 92a5e524070a88d2cf972c4191b2cee9993914ea Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 19:53:34 +0200 Subject: [PATCH 23/36] rename "persisted" animations to "scripted", because that is what they actually are. And begin teaching the animation system to distinguish between a scripted and an unscripted animation. --- apps/openmw/mwmechanics/actors.cpp | 4 +- apps/openmw/mwmechanics/actors.hpp | 2 +- apps/openmw/mwmechanics/character.cpp | 59 +++++++++++-------- apps/openmw/mwmechanics/character.hpp | 8 +-- .../mwmechanics/mechanicsmanagerimp.cpp | 6 +- .../mwmechanics/mechanicsmanagerimp.hpp | 2 +- apps/openmw/mwmechanics/objects.cpp | 4 +- apps/openmw/mwmechanics/objects.hpp | 2 +- apps/openmw/mwrender/animation.cpp | 12 +++- apps/openmw/mwrender/animation.hpp | 3 + 10 files changed, 60 insertions(+), 42 deletions(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 743c5d5ab5..3e7b075e62 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1998,12 +1998,12 @@ namespace MWMechanics } bool Actors::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) const + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) const { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { - return iter->second->getCharacterController().playGroup(groupName, mode, number, persist); + return iter->second->getCharacterController().playGroup(groupName, mode, number, scripted); } else { diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 98c64397ab..15a39136a6 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -113,7 +113,7 @@ namespace MWMechanics void forceStateUpdate(const MWWorld::Ptr& ptr) const; bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) const; + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) const; void skipAnimation(const MWWorld::Ptr& ptr) const; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) const; void persistAnimationStates() const; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 9c39b5b60d..0d9cda9263 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -538,7 +538,7 @@ namespace MWMechanics if (mAnimation->isPlaying("containerclose")) return false; - mAnimation->play("containeropen", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + mAnimation->play("containeropen", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.f, 0); if (mAnimation->isPlaying("containeropen")) return false; @@ -559,7 +559,7 @@ namespace MWMechanics if (animPlaying) startPoint = 1.f - complete; - mAnimation->play("containerclose", Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, + mAnimation->play("containerclose", Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", startPoint, 0); } } @@ -828,7 +828,7 @@ namespace MWMechanics CharacterState idle, CharacterState movement, JumpingState jump, bool force) { // If the current animation is persistent, do not touch it - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return; refreshHitRecoilAnims(); @@ -882,7 +882,7 @@ namespace MWMechanics mDeathState = chooseRandomDeathState(); // Do not interrupt scripted animation by death - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return; playDeath(startpoint, mDeathState); @@ -1482,7 +1482,7 @@ namespace MWMechanics } // Combat for actors with persistent animations obviously will be buggy - if (isPersistentAnimPlaying()) + if (isScriptedAnimPlaying()) return forcestateupdate; float complete = 0.f; @@ -1855,10 +1855,18 @@ namespace MWMechanics if (mAnimQueue.empty()) return; - if (mAnimation->isPlaying(mAnimQueue.front().mGroup) == false) + bool isFrontAnimPlaying = false; + if (mAnimQueue.front().mScripted) + isFrontAnimPlaying = mAnimation->isPlayingScripted(mAnimQueue.front().mGroup); + else + isFrontAnimPlaying = mAnimation->isPlaying(mAnimQueue.front().mGroup); + + + if (!isFrontAnimPlaying) { if (mAnimQueue.size() > 1) { + // Curren animation finished, move to the next in queue mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); @@ -1866,14 +1874,13 @@ namespace MWMechanics mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } - else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mPersist) + else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mScripted) { - // The last animation stopped playing when it shouldn't have. - // This is caused by a rebuild of the animation object, so we should restart the animation here - // Subtract 1 from mLoopCount to consider the current loop done. + // A scripted animation stopped playing before time. + // This happens when the animation object is rebuilt, so we should restart the animation here. bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, - false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount - 1, loopfallback); + mAnimation->play(mAnimQueue.front().mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, + false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } else { @@ -2393,7 +2400,7 @@ namespace MWMechanics } } - bool isPersist = isPersistentAnimPlaying(); + bool isPersist = isScriptedAnimPlaying(); osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); if (duration > 0.0f) moved /= duration; @@ -2450,7 +2457,7 @@ namespace MWMechanics state.mScriptedAnims.clear(); for (AnimationQueue::const_iterator iter = mAnimQueue.begin(); iter != mAnimQueue.end(); ++iter) { - if (!iter->mPersist) + if (!iter->mScripted) continue; ESM::AnimationState::ScriptedAnimation anim; @@ -2486,7 +2493,7 @@ namespace MWMechanics AnimationQueueEntry entry; entry.mGroup = iter->mGroup; entry.mLoopCount = iter->mLoopCount; - entry.mPersist = true; + entry.mScripted = true; mAnimQueue.push_back(entry); } @@ -2505,18 +2512,18 @@ namespace MWMechanics mIdleState = CharState_SpecialIdle; bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(anim.mGroup, Priority_Persistent, MWRender::Animation::BlendMask_All, false, 1.0f, "start", + mAnimation->play(anim.mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", complete, anim.mLoopCount, loopfallback); } } - bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool persist) + bool CharacterController::playGroup(std::string_view groupname, int mode, int count, bool scripted) { if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; // We should not interrupt persistent animations by non-persistent ones - if (isPersistentAnimPlaying() && !persist) + if (isScriptedAnimPlaying() && !scripted) return true; // If this animation is a looped animation (has a "loop start" key) that is already playing @@ -2545,17 +2552,17 @@ namespace MWMechanics AnimationQueueEntry entry; entry.mGroup = groupname; entry.mLoopCount = count - 1; - entry.mPersist = persist; + entry.mScripted = scripted; if (mode != 0 || mAnimQueue.empty() || !isAnimPlaying(mAnimQueue.front().mGroup)) { - clearAnimQueue(persist); + clearAnimQueue(scripted); clearStateAnimation(mCurrentIdle); mIdleState = CharState_SpecialIdle; bool loopfallback = entry.mGroup.starts_with("idle"); - mAnimation->play(groupname, persist && groupname != "idle" ? Priority_Persistent : Priority_Default, + 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); } @@ -2566,7 +2573,7 @@ namespace MWMechanics // "PlayGroup idle" is a special case, used to remove to stop scripted animations playing if (groupname == "idle") - entry.mPersist = false; + entry.mScripted = false; mAnimQueue.push_back(entry); @@ -2578,12 +2585,12 @@ namespace MWMechanics mSkipAnim = true; } - bool CharacterController::isPersistentAnimPlaying() const + bool CharacterController::isScriptedAnimPlaying() const { if (!mAnimQueue.empty()) { const AnimationQueueEntry& first = mAnimQueue.front(); - return first.mPersist && isAnimPlaying(first.mGroup); + return first.mScripted && isAnimPlaying(first.mGroup); } return false; @@ -2609,7 +2616,7 @@ namespace MWMechanics void CharacterController::clearAnimQueue(bool clearPersistAnims) { // Do not interrupt scripted animations, if we want to keep them - if ((!isPersistentAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) + if ((!isScriptedAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); if (clearPersistAnims) @@ -2620,7 +2627,7 @@ namespace MWMechanics for (AnimationQueue::iterator it = mAnimQueue.begin(); it != mAnimQueue.end();) { - if (!it->mPersist) + if (!it->mScripted) it = mAnimQueue.erase(it); else ++it; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 316a1cff0e..79bbe73538 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -40,7 +40,7 @@ namespace MWMechanics Priority_Torch, Priority_Storm, Priority_Death, - Priority_Persistent, + Priority_Scripted, Num_Priorities }; @@ -135,7 +135,7 @@ namespace MWMechanics { std::string mGroup; size_t mLoopCount; - bool mPersist; + bool mScripted; }; typedef std::deque AnimationQueue; AnimationQueue mAnimQueue; @@ -215,7 +215,7 @@ namespace MWMechanics std::string chooseRandomAttackAnimation() const; static bool isRandomAttackAnimation(std::string_view group); - bool isPersistentAnimPlaying() const; + bool isScriptedAnimPlaying() const; bool isMovementAnimationControlled() const; void updateAnimQueue(); @@ -270,7 +270,7 @@ namespace MWMechanics void persistAnimationState() const; void unpersistAnimationState(); - bool playGroup(std::string_view groupname, int mode, int count, bool persist = false); + bool playGroup(std::string_view groupname, int mode, int count, bool scripted = false); void skipAnim(); bool isAnimPlaying(std::string_view groupName) const; diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index 071ac164f3..f95df16855 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -760,12 +760,12 @@ namespace MWMechanics } bool MechanicsManager::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) { if (ptr.getClass().isActor()) - return mActors.playAnimationGroup(ptr, groupName, mode, number, persist); + return mActors.playAnimationGroup(ptr, groupName, mode, number, scripted); else - return mObjects.playAnimationGroup(ptr, groupName, mode, number, persist); + return mObjects.playAnimationGroup(ptr, groupName, mode, number, scripted); } void MechanicsManager::skipAnimation(const MWWorld::Ptr& ptr) { diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 36bb18e022..997636522e 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -142,7 +142,7 @@ namespace MWMechanics /// Attempt to play an animation group /// @return Success or error bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false) override; + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false) override; void skipAnimation(const MWWorld::Ptr& ptr) override; bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) override; void persistAnimationStates() override; diff --git a/apps/openmw/mwmechanics/objects.cpp b/apps/openmw/mwmechanics/objects.cpp index ab981dd459..5bdfc91ac7 100644 --- a/apps/openmw/mwmechanics/objects.cpp +++ b/apps/openmw/mwmechanics/objects.cpp @@ -99,12 +99,12 @@ namespace MWMechanics } bool Objects::playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist) + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted) { const auto iter = mIndex.find(ptr.mRef); if (iter != mIndex.end()) { - return iter->second->playGroup(groupName, mode, number, persist); + return iter->second->playGroup(groupName, mode, number, scripted); } else { diff --git a/apps/openmw/mwmechanics/objects.hpp b/apps/openmw/mwmechanics/objects.hpp index 8b5962109c..296f454e4f 100644 --- a/apps/openmw/mwmechanics/objects.hpp +++ b/apps/openmw/mwmechanics/objects.hpp @@ -46,7 +46,7 @@ namespace MWMechanics void onClose(const MWWorld::Ptr& ptr); bool playAnimationGroup( - const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool persist = false); + const MWWorld::Ptr& ptr, std::string_view groupName, int mode, int number, bool scripted = false); void skipAnimation(const MWWorld::Ptr& ptr); void persistAnimationStates(); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index b49f382f66..cae3687afb 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1020,6 +1020,14 @@ namespace MWRender return false; } + bool Animation::isPlayingScripted(std::string_view groupname) const + { + AnimStateMap::const_iterator state(mStates.find(groupname)); + if (state != mStates.end()) + return state->second.mPlaying && state->second.mPriority.contains(MWMechanics::Priority::Priority_Scripted); + return false; + } + bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const { AnimStateMap::const_iterator iter = mStates.find(groupname); @@ -1145,7 +1153,7 @@ namespace MWRender bool hasScriptedAnims = false; for (AnimStateMap::iterator stateiter = mStates.begin(); stateiter != mStates.end(); stateiter++) { - if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Persistent)) + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying) { hasScriptedAnims = true; @@ -1158,7 +1166,7 @@ namespace MWRender while (stateiter != mStates.end()) { AnimState& state = stateiter->second; - if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Persistent))) + if (hasScriptedAnims && !state.mPriority.contains(int(MWMechanics::Priority_Scripted))) { ++stateiter; continue; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 8615811cc3..9a55c91fc4 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -432,6 +432,9 @@ namespace MWRender /** Returns true if the named animation group is playing. */ bool isPlaying(std::string_view groupname) const; + /** Returns true if the named, scripted animation group is playing. */ + bool isPlayingScripted(std::string_view groupname) const; + /// Returns true if no important animations are currently playing on the upper body. bool upperBodyReady() const; From a9ade184d8f2dda41db4bb51fc2df4d5cdfead60 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 19:55:10 +0200 Subject: [PATCH 24/36] mLoopCount is the number of *remaining* loops. So we should be checking for >0, not >1. --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 0d9cda9263..ad201997bd 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1874,7 +1874,7 @@ namespace MWMechanics mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); } - else if (mAnimQueue.front().mLoopCount > 1 && mAnimQueue.front().mScripted) + else if (mAnimQueue.front().mLoopCount > 0 && mAnimQueue.front().mScripted) { // A scripted animation stopped playing before time. // This happens when the animation object is rebuilt, so we should restart the animation here. From 426f5952a5bfbdf3c6d2114164a4176b85c6db3e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 21:02:17 +0200 Subject: [PATCH 25/36] Prevent movement only when *scripted* animations are playing. Otherwise idle animations of wandering NPCs will slide. --- apps/openmw/mwmechanics/character.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ad201997bd..08c035ce31 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1861,7 +1861,6 @@ namespace MWMechanics else isFrontAnimPlaying = mAnimation->isPlaying(mAnimQueue.front().mGroup); - if (!isFrontAnimPlaying) { if (mAnimQueue.size() > 1) @@ -2373,7 +2372,7 @@ namespace MWMechanics } } - if (!isMovementAnimationControlled() && mAnimQueue.empty()) + if (!isMovementAnimationControlled() && !isScriptedAnimPlaying()) world->queueMovement(mPtr, vec); } @@ -2442,7 +2441,7 @@ namespace MWMechanics } // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor() && mAnimQueue.empty()) + if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying()) world->queueMovement(mPtr, moved); mSkipAnim = false; @@ -2522,7 +2521,7 @@ namespace MWMechanics if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; - // We should not interrupt persistent animations by non-persistent ones + // We should not interrupt persistent animations by non-scripted ones if (isScriptedAnimPlaying() && !scripted) return true; From cf5b782c761a0cbded399f3ef97b451f166bece0 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 21:05:07 +0200 Subject: [PATCH 26/36] More persist -> script language. --- apps/openmw/mwmechanics/character.cpp | 15 +++++++-------- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 08c035ce31..b3661c8f96 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -827,7 +827,7 @@ namespace MWMechanics void CharacterController::refreshCurrentAnims( CharacterState idle, CharacterState movement, JumpingState jump, bool force) { - // If the current animation is persistent, do not touch it + // If the current animation is scripted, do not touch it if (isScriptedAnimPlaying()) return; @@ -1481,7 +1481,7 @@ namespace MWMechanics sndMgr->stopSound3D(mPtr, wolfRun); } - // Combat for actors with persistent animations obviously will be buggy + // Combat for actors with scripted animations obviously will be buggy if (isScriptedAnimPlaying()) return forcestateupdate; @@ -2399,8 +2399,7 @@ namespace MWMechanics } } - bool isPersist = isScriptedAnimPlaying(); - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isPersist ? 0.f : duration); + osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); if (duration > 0.0f) moved /= duration; else @@ -2521,7 +2520,7 @@ namespace MWMechanics if (!mAnimation || !mAnimation->hasAnimation(groupname)) return false; - // We should not interrupt persistent animations by non-scripted ones + // We should not interrupt scripted animations with non-scripted ones if (isScriptedAnimPlaying() && !scripted) return true; @@ -2612,13 +2611,13 @@ namespace MWMechanics return movementAnimationControlled; } - void CharacterController::clearAnimQueue(bool clearPersistAnims) + void CharacterController::clearAnimQueue(bool clearScriptedAnims) { // Do not interrupt scripted animations, if we want to keep them - if ((!isScriptedAnimPlaying() || clearPersistAnims) && !mAnimQueue.empty()) + if ((!isScriptedAnimPlaying() || clearScriptedAnims) && !mAnimQueue.empty()) mAnimation->disable(mAnimQueue.front().mGroup); - if (clearPersistAnims) + if (clearScriptedAnims) { mAnimQueue.clear(); return; diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 79bbe73538..c3d45fe0fb 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -207,7 +207,7 @@ namespace MWMechanics void refreshMovementAnims(CharacterState movement, bool force = false); void refreshIdleAnims(CharacterState idle, bool force = false); - void clearAnimQueue(bool clearPersistAnims = false); + void clearAnimQueue(bool clearScriptedAnims = false); bool updateWeaponState(); void updateIdleStormState(bool inwater) const; From 70f41b22b657aba178e747320535e251efdaa684 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 21:25:12 +0200 Subject: [PATCH 27/36] Based on the reason for closing !1845, we *should* let a scripted animation get stuck forever if the animation was already playing. --- apps/openmw/mwmechanics/character.cpp | 8 +------- apps/openmw/mwrender/animation.cpp | 8 -------- apps/openmw/mwrender/animation.hpp | 3 --- 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b3661c8f96..aa045edff6 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1855,13 +1855,7 @@ namespace MWMechanics if (mAnimQueue.empty()) return; - bool isFrontAnimPlaying = false; - if (mAnimQueue.front().mScripted) - isFrontAnimPlaying = mAnimation->isPlayingScripted(mAnimQueue.front().mGroup); - else - isFrontAnimPlaying = mAnimation->isPlaying(mAnimQueue.front().mGroup); - - if (!isFrontAnimPlaying) + if (!mAnimation->isPlaying(mAnimQueue.front().mGroup)) { if (mAnimQueue.size() > 1) { diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index cae3687afb..4ce589f8b9 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1020,14 +1020,6 @@ namespace MWRender return false; } - bool Animation::isPlayingScripted(std::string_view groupname) const - { - AnimStateMap::const_iterator state(mStates.find(groupname)); - if (state != mStates.end()) - return state->second.mPlaying && state->second.mPriority.contains(MWMechanics::Priority::Priority_Scripted); - return false; - } - bool Animation::getInfo(std::string_view groupname, float* complete, float* speedmult) const { AnimStateMap::const_iterator iter = mStates.find(groupname); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 9a55c91fc4..8615811cc3 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -432,9 +432,6 @@ namespace MWRender /** Returns true if the named animation group is playing. */ bool isPlaying(std::string_view groupname) const; - /** Returns true if the named, scripted animation group is playing. */ - bool isPlayingScripted(std::string_view groupname) const; - /// Returns true if no important animations are currently playing on the upper body. bool upperBodyReady() const; From c25c6872b03b4ba0c0df3ded2779b01cc1ca5d67 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 22:20:37 +0200 Subject: [PATCH 28/36] changelog entries --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e122f0a9..6641f41ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,8 @@ Bug #7611: Beast races' idle animations slide after turning or jumping in place 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 + 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 #7647: NPC walk cycle bugs after greeting player Feature #3537: Shader-based water ripples From afb9fa06d7279ce90890f3ceba4b9be555412ddf Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 25 Oct 2023 22:23:29 +0200 Subject: [PATCH 29/36] clang format --- apps/openmw/mwrender/animation.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 4ce589f8b9..0741e24a69 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1145,8 +1145,7 @@ namespace MWRender 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) + if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Scripted)) && stateiter->second.mPlaying) { hasScriptedAnims = true; break; From 85f104fefe0f4bf4a29bca465ae05621f3531e6a Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 29 Oct 2023 15:33:07 +0100 Subject: [PATCH 30/36] Comments from Capo --- apps/openmw/mwmechanics/character.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index aa045edff6..a21e5fbf36 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1857,30 +1857,22 @@ namespace MWMechanics if (!mAnimation->isPlaying(mAnimQueue.front().mGroup)) { - if (mAnimQueue.size() > 1) + // 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) { - // Curren animation finished, move to the next in queue mAnimation->disable(mAnimQueue.front().mGroup); mAnimQueue.pop_front(); + } - bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Default, MWRender::Animation::BlendMask_All, false, - 1.0f, "start", "stop", 0.0f, mAnimQueue.front().mLoopCount, loopfallback); - } - else if (mAnimQueue.front().mLoopCount > 0 && mAnimQueue.front().mScripted) + if (!mAnimQueue.empty()) { - // A scripted animation stopped playing before time. - // This happens when the animation object is rebuilt, so we should restart the animation here. + // Move on to the remaining items of the queue bool loopfallback = mAnimQueue.front().mGroup.starts_with("idle"); - mAnimation->play(mAnimQueue.front().mGroup, Priority_Scripted, MWRender::Animation::BlendMask_All, + 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); } - else - { - // Animation is done, remove it from the queue. - mAnimation->disable(mAnimQueue.front().mGroup); - mAnimQueue.pop_front(); - } } else { From 58e3fdac3661d2f796bfd11e388301eea524435d Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 29 Oct 2023 16:32:35 +0100 Subject: [PATCH 31/36] Clang format --- apps/openmw/mwmechanics/character.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index a21e5fbf36..068d91ab42 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -1869,9 +1869,10 @@ namespace MWMechanics { // 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); + 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); } } else From d2836748fd708feba645def1b9f7c43ee98672e9 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 29 Oct 2023 20:27:02 +0100 Subject: [PATCH 32/36] Changelog entry for #4742 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6641f41ac5..c7756fd437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Bug #4207: RestoreHealth/Fatigue spells have a huge priority even if a success chance is near 0 Bug #4382: Sound output device does not change when it should 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 #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 From e51dfca4883890dbdef21fa3a1368e5263480d72 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 29 Oct 2023 22:41:53 +0100 Subject: [PATCH 33/36] Stop trying to save ESM4 objects in omwsave files because they currently can not be saved and only lead to errors in logs. --- apps/openmw/mwworld/cellstore.cpp | 45 ++++++++++++++++--------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index ff79940540..d3c9d73c7c 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -161,31 +161,32 @@ namespace template void writeReferenceCollection(ESM::ESMWriter& writer, const MWWorld::CellRefList& collection) { - if (!collection.mList.empty()) + // references + for (const MWWorld::LiveCellRef& liveCellRef : collection.mList) { - // references - for (typename MWWorld::CellRefList::List::const_iterator iter(collection.mList.begin()); - iter != collection.mList.end(); ++iter) + if (ESM::isESM4Rec(T::sRecordId)) { - if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.hasContentFile()) - { - // Reference that came from a content file and has not been changed -> ignore - continue; - } - if (iter->mData.getCount() == 0 && !iter->mRef.hasContentFile()) - { - // Deleted reference that did not come from a content file -> ignore - continue; - } - using StateType = typename RecordToState::StateType; - StateType state; - iter->save(state); - - // recordId currently unused - writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId); - - state.save(writer); + // TODO: Implement loading/saving of REFR4 and ACHR4 with ESM3 reader/writer. + continue; } + if (!liveCellRef.mData.hasChanged() && !liveCellRef.mRef.hasChanged() && liveCellRef.mRef.hasContentFile()) + { + // Reference that came from a content file and has not been changed -> ignore + continue; + } + if (liveCellRef.mData.getCount() == 0 && !liveCellRef.mRef.hasContentFile()) + { + // Deleted reference that did not come from a content file -> ignore + continue; + } + using StateType = typename RecordToState::StateType; + StateType state; + liveCellRef.save(state); + + // recordId currently unused + writer.writeHNT("OBJE", collection.mList.front().mBase->sRecordId); + + state.save(writer); } } From 3d5b06a78e0dd85276137baf7ad1052f02ae5bcf Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 29 Oct 2023 03:13:24 +0300 Subject: [PATCH 34/36] Sort inactive shader list (#7652) --- CHANGELOG.md | 1 + apps/openmw/mwgui/postprocessorhud.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e122f0a9..39da12eac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ 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 Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector diff --git a/apps/openmw/mwgui/postprocessorhud.cpp b/apps/openmw/mwgui/postprocessorhud.cpp index e54704d6d4..ab5bdf791d 100644 --- a/apps/openmw/mwgui/postprocessorhud.cpp +++ b/apps/openmw/mwgui/postprocessorhud.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -421,7 +422,12 @@ namespace MWGui auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor(); + std::vector techniques; for (const auto& [name, _] : processor->getTechniqueMap()) + techniques.push_back(name); + std::sort(techniques.begin(), techniques.end(), Misc::StringUtils::ciLess); + + for (const std::string& name : techniques) { auto technique = processor->loadTechnique(name); From 54441624a12b62395f1fa68482074371dede94db Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Tue, 31 Oct 2023 19:59:06 +0100 Subject: [PATCH 35/36] Changelog entry for 4508 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39da12eac3..1ff2a50047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug #4204: Dead slaughterfish doesn't float to water surface after loading saved game Bug #4207: RestoreHealth/Fatigue spells have a huge priority even if a success chance is near 0 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 #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached From 733a6e01adc2d89df9bfa5391b2469329ec56199 Mon Sep 17 00:00:00 2001 From: Kindi <2538602-Kuyondo@users.noreply.gitlab.com> Date: Tue, 31 Oct 2023 21:27:42 +0000 Subject: [PATCH 36/36] Sort repair and recharge menu list alphabetically (bug #7642) --- CHANGELOG.md | 1 + apps/openmw/mwgui/itemchargeview.cpp | 5 ++++ apps/openmw/mwgui/merchantrepair.cpp | 39 ++++++++++++++++++---------- apps/openmw/mwgui/repair.cpp | 2 +- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39da12eac3..9d5ce61418 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ Bug #7630: Charm can be cast on creatures Bug #7631: Cannot trade with/talk to Creeper or Mudcrab Merchant when they're fleeing Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat + Bug #7642: Items in repair and recharge menus aren't sorted alphabetically Bug #7647: NPC walk cycle bugs after greeting player Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics diff --git a/apps/openmw/mwgui/itemchargeview.cpp b/apps/openmw/mwgui/itemchargeview.cpp index 92fff6f873..ba74eadc7a 100644 --- a/apps/openmw/mwgui/itemchargeview.cpp +++ b/apps/openmw/mwgui/itemchargeview.cpp @@ -128,6 +128,11 @@ namespace MWGui mLines.swap(lines); + std::stable_sort(mLines.begin(), mLines.end(), + [](const MWGui::ItemChargeView::Line& a, const MWGui::ItemChargeView::Line& b) { + return Misc::StringUtils::ciLess(a.mText->getCaption().asUTF8(), b.mText->getCaption().asUTF8()); + }); + layoutWidgets(); } diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index c5393fbfb7..3be0bb1c06 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -47,6 +47,8 @@ namespace MWGui MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor; + std::vector> items; + for (MWWorld::ContainerStoreIterator iter(store.begin(categories)); iter != store.end(); ++iter) { if (iter->getClass().hasItemHealth(*iter)) @@ -76,22 +78,31 @@ namespace MWGui name += " - " + MyGUI::utility::toString(price) + MWBase::Environment::get().getESMStore()->get().find("sgp")->mValue.getString(); - MyGUI::Button* button = mList->createWidget(price <= playerGold - ? "SandTextButton" - : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip - 0, currentY, 0, lineHeight, MyGUI::Align::Default); - - currentY += lineHeight; - - button->setUserString("Price", MyGUI::utility::toString(price)); - button->setUserData(MWWorld::Ptr(*iter)); - button->setCaptionWithReplacing(name); - button->setSize(mList->getWidth(), lineHeight); - button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); - button->setUserString("ToolTipType", "ItemPtr"); - button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); + items.emplace_back(name, price, *iter); } } + + std::stable_sort(items.begin(), items.end(), + [](const auto& a, const auto& b) { return Misc::StringUtils::ciLess(std::get<0>(a), std::get<0>(b)); }); + + for (const auto& [name, price, ptr] : items) + { + MyGUI::Button* button = mList->createWidget(price <= playerGold + ? "SandTextButton" + : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip + 0, currentY, 0, lineHeight, MyGUI::Align::Default); + + currentY += lineHeight; + + button->setUserString("Price", MyGUI::utility::toString(price)); + button->setUserData(MWWorld::Ptr(ptr)); + button->setCaptionWithReplacing(name); + button->setSize(mList->getWidth(), lineHeight); + button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel); + button->setUserString("ToolTipType", "ItemPtr"); + button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick); + } + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the // scrollbar is hidden mList->setVisibleVScroll(false); diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index aabd3d46ab..63b51d7d2c 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -49,7 +49,7 @@ namespace MWGui = new SortFilterItemModel(std::make_unique(MWMechanics::getPlayer())); model->setFilter(SortFilterItemModel::Filter_OnlyRepairable); mRepairBox->setModel(model); - + mRepairBox->update(); // Reset scrollbars mRepairBox->resetScrollbars(); }